From 83e608a3c143811e2782a39726051033f785aefc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 16 Nov 2023 09:19:52 -0800 Subject: [PATCH 1/6] Add alternating colors to devmode decorations to help differentiate --- .../developer/browser/media/developer.css | 6 ++++++ .../browser/terminal.developer.contribution.ts | 11 +++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/developer/browser/media/developer.css b/src/vs/workbench/contrib/terminalContrib/developer/browser/media/developer.css index bf20da05443e1..3873d8423f482 100644 --- a/src/vs/workbench/contrib/terminalContrib/developer/browser/media/developer.css +++ b/src/vs/workbench/contrib/terminalContrib/developer/browser/media/developer.css @@ -43,3 +43,9 @@ .monaco-workbench .xterm.dev-mode .xterm-sequence-decoration.bottom.right { transform: scale(.5) translate(50%, 50%); } +.monaco-workbench .xterm.dev-mode .xterm-sequence-decoration.color-0 { + color: #FF4444; +} +.monaco-workbench .xterm.dev-mode .xterm-sequence-decoration.color-1 { + color: #44FFFF; +} diff --git a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts index 2761345f57205..a313b288fb4b7 100644 --- a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts @@ -119,6 +119,7 @@ class DevModeContribution extends Disposable implements ITerminalContribution { private _xterm: IXtermTerminal & { raw: Terminal } | undefined; private _activeDevModeDisposables = new MutableDisposable(); + private _currentColor = 0; constructor( private readonly _instance: ITerminalInstance, @@ -156,13 +157,14 @@ class DevModeContribution extends Disposable implements ITerminalContribution { if (devMode) { if (commandDetection) { this._activeDevModeDisposables.value = commandDetection.onCommandFinished(command => { + const colorClass = `color-${this._currentColor}`; if (command.promptStartMarker) { const d = this._instance.xterm!.raw?.registerDecoration({ marker: command.promptStartMarker }); d?.onRender(e => { e.textContent = 'A'; - e.classList.add('xterm-sequence-decoration', 'top', 'left'); + e.classList.add('xterm-sequence-decoration', 'top', 'left', colorClass); }); } if (command.marker) { @@ -172,7 +174,7 @@ class DevModeContribution extends Disposable implements ITerminalContribution { }); d?.onRender(e => { e.textContent = 'B'; - e.classList.add('xterm-sequence-decoration', 'top', 'right'); + e.classList.add('xterm-sequence-decoration', 'top', 'right', colorClass); }); } if (command.executedMarker) { @@ -182,7 +184,7 @@ class DevModeContribution extends Disposable implements ITerminalContribution { }); d?.onRender(e => { e.textContent = 'C'; - e.classList.add('xterm-sequence-decoration', 'bottom', 'left'); + e.classList.add('xterm-sequence-decoration', 'bottom', 'left', colorClass); }); } if (command.endMarker) { @@ -191,9 +193,10 @@ class DevModeContribution extends Disposable implements ITerminalContribution { }); d?.onRender(e => { e.textContent = 'D'; - e.classList.add('xterm-sequence-decoration', 'bottom', 'right'); + e.classList.add('xterm-sequence-decoration', 'bottom', 'right', colorClass); }); } + this._currentColor = (this._currentColor + 1) % 2; }); } else { this._activeDevModeDisposables.value = this._instance.capabilities.onDidAddCapabilityType(e => { From 6b8bbf8b6f41cacf76243f5741dbd3068d5fe7c4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 16 Nov 2023 09:21:07 -0800 Subject: [PATCH 2/6] Ensure end is after executed and prompt start is after end Part of #198278 --- .../common/capabilities/capabilities.ts | 10 ++-- .../commandDetection/terminalCommand.ts | 5 +- .../commandDetectionCapability.ts | 53 ++++++++++++++----- 3 files changed, 45 insertions(+), 23 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts index 88f61d2ba35f8..cf9e74a75d212 100644 --- a/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -254,12 +254,12 @@ interface IBaseTerminalCommand { export interface ITerminalCommand extends IBaseTerminalCommand { // Optional non-serializable - promptStartMarker?: IMarker; - marker?: IXtermMarker; + readonly promptStartMarker?: IMarker; + readonly marker?: IXtermMarker; endMarker?: IXtermMarker; - executedMarker?: IXtermMarker; - aliases?: string[][]; - wasReplayed?: boolean; + readonly executedMarker?: IXtermMarker; + readonly aliases?: string[][]; + readonly wasReplayed?: boolean; getOutput(): string | undefined; getOutputMatch(outputMatcher: ITerminalOutputMatcher): ITerminalOutputMatch | undefined; diff --git a/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts b/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts index eb5eb17cd8c3e..f792df9a3d2c9 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetection/terminalCommand.ts @@ -37,6 +37,7 @@ export class TerminalCommand implements ITerminalCommand { get promptStartMarker() { return this._properties.promptStartMarker; } get marker() { return this._properties.marker; } get endMarker() { return this._properties.endMarker; } + set endMarker(value: IXtermMarker | undefined) { this._properties.endMarker = value; } get executedMarker() { return this._properties.executedMarker; } get aliases() { return this._properties.aliases; } get wasReplayed() { return this._properties.wasReplayed; } @@ -199,8 +200,6 @@ export class TerminalCommand implements ITerminalCommand { } export interface ICurrentPartialCommand { - previousCommandMarker?: IMarker; - promptStartMarker?: IMarker; commandStartMarker?: IMarker; @@ -238,8 +237,6 @@ export interface ICurrentPartialCommand { } export class PartialTerminalCommand implements ICurrentPartialCommand { - previousCommandMarker?: IMarker; - promptStartMarker?: IMarker; commandStartMarker?: IMarker; diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index d64ac4b71ca3d..7a779fa3f528d 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -8,7 +8,7 @@ import { debounce } from 'vs/base/common/decorators'; import { Emitter } from 'vs/base/common/event'; import { Disposable, MandatoryMutableDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; -import { CommandInvalidationReason, ICommandDetectionCapability, ICommandInvalidationRequest, IHandleCommandOptions, ISerializedCommandDetectionCapability, ISerializedTerminalCommand, ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { CommandInvalidationReason, ICommandDetectionCapability, ICommandInvalidationRequest, IHandleCommandOptions, ISerializedCommandDetectionCapability, ISerializedTerminalCommand, ITerminalCommand, IXtermMarker, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { ITerminalOutputMatcher } from 'vs/platform/terminal/common/terminal'; // Importing types is safe in any layer @@ -32,6 +32,9 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe private __isCommandStorageDisabled: boolean = false; private _handleCommandStartOptions?: IHandleCommandOptions; + + private _commitCommandFinished?: RunOnceScheduler; + private _ptyHeuristicsHooks: ICommandDetectionHeuristicsHooks; private _ptyHeuristics: MandatoryMutableDisposable; @@ -224,7 +227,19 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe } handlePromptStart(options?: IHandleCommandOptions): void { - this._currentCommand.promptStartMarker = options?.marker || this._terminal.registerMarker(0); + // Adjust the last command's finished marker when needed. The standard position for the + // finished marker `D` to appear is at the same position as the following prompt started + // `A`. + const lastCommand = this.commands.at(-1); + if (lastCommand?.endMarker && lastCommand?.executedMarker && lastCommand.endMarker.line === lastCommand.executedMarker.line) { + this._logService.debug('CommandDetectionCapability#handlePromptStart adjusted commandFinished', `${lastCommand.endMarker.line} -> ${lastCommand.executedMarker.line + 1}`); + lastCommand.endMarker = cloneMarker(this._terminal, lastCommand.executedMarker, 1); + } + this._commitCommandFinished?.flush(); + this._commitCommandFinished = undefined; + + this._currentCommand.promptStartMarker = options?.marker || (lastCommand?.endMarker ? cloneMarker(this._terminal, lastCommand.endMarker) : this._terminal.registerMarker(0)); + this._logService.debug('CommandDetectionCapability#handlePromptStart', this._terminal.buffer.active.cursorX, this._currentCommand.promptStartMarker?.line); } @@ -286,7 +301,7 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe } handleCommandFinished(exitCode: number | undefined, options?: IHandleCommandOptions): void { - this._ptyHeuristics.value?.preHandleCommandFinished?.(); + this._ptyHeuristics.value.preHandleCommandFinished?.(); this._logService.debug('CommandDetectionCapability#handleCommandFinished', this._terminal.buffer.active.cursorX, options?.marker?.line, this._currentCommand.command, this._currentCommand); @@ -306,21 +321,23 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe return; } - this._ptyHeuristics.value?.postHandleCommandFinished?.(); - this._currentCommand.commandFinishedMarker = options?.marker || this._terminal.registerMarker(0); + + this._ptyHeuristics.value.postHandleCommandFinished?.(); + const newCommand = this._currentCommand.promoteToFullCommand(this._cwd, exitCode, this._handleCommandStartOptions?.ignoreCommandLine ?? false, options?.markProperties); if (newCommand) { this._commands.push(newCommand); - this._logService.debug('CommandDetectionCapability#onCommandFinished', newCommand); - - this._onBeforeCommandFinished.fire(newCommand); - if (!this._currentCommand.isInvalid) { - this._onCommandFinished.fire(newCommand); - } + this._commitCommandFinished = new RunOnceScheduler(() => { + this._onBeforeCommandFinished.fire(newCommand); + if (!this._currentCommand.isInvalid) { + this._logService.debug('CommandDetectionCapability#onCommandFinished', newCommand); + this._onCommandFinished.fire(newCommand); + } + }, 50); + this._commitCommandFinished.schedule(); } - this._currentCommand.previousCommandMarker = this._currentCommand.commandStartMarker; this._currentCommand = new PartialTerminalCommand(this._terminal); this._handleCommandStartOptions = undefined; } @@ -572,7 +589,11 @@ class WindowsPtyHeuristics extends Disposable { // On Windows track all cursor movements after the command start sequence this._hooks.commandMarkers.length = 0; - const initialCommandStartMarker = this._capability.currentCommand.commandStartMarker = this._terminal.registerMarker(0)!; + const initialCommandStartMarker = this._capability.currentCommand.commandStartMarker = ( + this._capability.currentCommand.promptStartMarker + ? cloneMarker(this._terminal, this._capability.currentCommand.promptStartMarker) + : this._terminal.registerMarker(0) + )!; this._capability.currentCommand.commandStartX = 0; // DEBUG: Add a decoration for the original unadjusted command start position @@ -624,7 +645,7 @@ class WindowsPtyHeuristics extends Disposable { this._capability.currentCommand.commandStartMarker = this._terminal.registerMarker(0)!; // use the regex to set the position as it's possible input has occurred this._capability.currentCommand.commandStartX = prompt.length; - this._logService.debug('CommandDetectionCapability#_tryAdjustCommandStartMarker successfully adjusted', `${start.line} -> ${this._capability.currentCommand.commandStartMarker}:${this._capability.currentCommand.commandStartX}`); + this._logService.debug('CommandDetectionCapability#_tryAdjustCommandStartMarker adjusted commandStart', `${start.line} -> ${this._capability.currentCommand.commandStartMarker.line}:${this._capability.currentCommand.commandStartX}`); this._flushPendingHandleCommandStartTask(); return; } @@ -918,3 +939,7 @@ function getXtermLineContent(buffer: IBuffer, lineStart: number, lineEnd: number } return content; } + +function cloneMarker(xterm: Terminal, marker: IXtermMarker, offset: number = 0): IXtermMarker | undefined { + return xterm.registerMarker(marker.line - (xterm.buffer.active.baseY + xterm.buffer.active.cursorY) + offset); +} From bc42a84c91279c1093620e4a0df20e16850b9386 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 16 Nov 2023 09:57:40 -0800 Subject: [PATCH 3/6] Adjust prompt start based on a likely single line prompt (pwsh default) --- .../commandDetectionCapability.ts | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index 7a779fa3f528d..4293052257a49 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -203,7 +203,7 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe // Iterate backwards through commands to find the right one for (let i = this.commands.length - 1; i >= 0; i--) { - if ((this.commands[i].promptStartMarker ?? this.commands[i].marker!).line <= line - 1) { + if ((this.commands[i].promptStartMarker ?? this.commands[i].marker!).line <= line) { return this.commands[i]; } } @@ -400,8 +400,8 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe */ interface ICommandDetectionHeuristicsHooks { readonly onCurrentCommandInvalidatedEmitter: Emitter; - readonly onCommandExecutedEmitter: Emitter; readonly onCommandStartedEmitter: Emitter; + readonly onCommandExecutedEmitter: Emitter; readonly dimensions: ITerminalDimensions; readonly isCommandStorageDisabled: boolean; @@ -642,9 +642,15 @@ class WindowsPtyHeuristics extends Disposable { if (this._cursorOnNextLine()) { const prompt = this._getWindowsPrompt(start.line + scannedLineCount); if (prompt) { + const adjustedPrompt = typeof prompt === 'string' ? prompt : prompt.prompt; this._capability.currentCommand.commandStartMarker = this._terminal.registerMarker(0)!; + if (typeof prompt === 'object' && prompt.likelySingleLine) { + this._logService.debug('CommandDetectionCapability#_tryAdjustCommandStartMarker adjusted promptStart', `${this._capability.currentCommand.promptStartMarker?.line} -> ${this._capability.currentCommand.commandStartMarker.line}`); + this._capability.currentCommand.promptStartMarker?.dispose(); + this._capability.currentCommand.promptStartMarker = cloneMarker(this._terminal, this._capability.currentCommand.commandStartMarker); + } // use the regex to set the position as it's possible input has occurred - this._capability.currentCommand.commandStartX = prompt.length; + this._capability.currentCommand.commandStartX = adjustedPrompt.length; this._logService.debug('CommandDetectionCapability#_tryAdjustCommandStartMarker adjusted commandStart', `${start.line} -> ${this._capability.currentCommand.commandStartMarker.line}:${this._capability.currentCommand.commandStartX}`); this._flushPendingHandleCommandStartTask(); return; @@ -833,7 +839,7 @@ class WindowsPtyHeuristics extends Disposable { }); } - private _getWindowsPrompt(y: number = this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY): string | undefined { + private _getWindowsPrompt(y: number = this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY): string | { prompt: string; likelySingleLine: true } | undefined { const line = this._terminal.buffer.active.getLine(y); if (!line) { return; @@ -849,7 +855,10 @@ class WindowsPtyHeuristics extends Disposable { if (pwshPrompt) { const adjustedPrompt = this._adjustPrompt(pwshPrompt, lineText, '>'); if (adjustedPrompt) { - return adjustedPrompt; + return { + prompt: adjustedPrompt, + likelySingleLine: true + }; } } @@ -864,7 +873,10 @@ class WindowsPtyHeuristics extends Disposable { // Command Prompt const cmdMatch = lineText.match(/^(?(\(.+\)\s)?(?:[A-Z]:\\.*>))/); - return cmdMatch?.groups?.prompt; + return cmdMatch?.groups?.prompt ? { + prompt: cmdMatch.groups.prompt, + likelySingleLine: true + } : undefined; } private _adjustPrompt(prompt: string | undefined, lineText: string, char: string): string | undefined { From 8aa62740fd1b8183fe4999ce556b78a1cb59b222 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 16 Nov 2023 10:37:49 -0800 Subject: [PATCH 4/6] Invalidate commands in devMode --- .../terminal.developer.contribution.ts | 114 +++++++++++------- 1 file changed, 70 insertions(+), 44 deletions(-) diff --git a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts index a313b288fb4b7..66bc030e4766a 100644 --- a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/developer'; import { VSBuffer } from 'vs/base/common/buffer'; -import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, MutableDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; @@ -23,7 +23,7 @@ import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/wid import { ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import type { Terminal } from '@xterm/xterm'; -import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { getWindow } from 'vs/base/browser/dom'; registerTerminalAction({ @@ -156,48 +156,74 @@ class DevModeContribution extends Disposable implements ITerminalContribution { const commandDetection = this._instance.capabilities.get(TerminalCapability.CommandDetection); if (devMode) { if (commandDetection) { - this._activeDevModeDisposables.value = commandDetection.onCommandFinished(command => { - const colorClass = `color-${this._currentColor}`; - if (command.promptStartMarker) { - const d = this._instance.xterm!.raw?.registerDecoration({ - marker: command.promptStartMarker - }); - d?.onRender(e => { - e.textContent = 'A'; - e.classList.add('xterm-sequence-decoration', 'top', 'left', colorClass); - }); - } - if (command.marker) { - const d = this._instance.xterm!.raw?.registerDecoration({ - marker: command.marker, - x: command.startX - }); - d?.onRender(e => { - e.textContent = 'B'; - e.classList.add('xterm-sequence-decoration', 'top', 'right', colorClass); - }); - } - if (command.executedMarker) { - const d = this._instance.xterm!.raw?.registerDecoration({ - marker: command.executedMarker, - x: command.executedX - }); - d?.onRender(e => { - e.textContent = 'C'; - e.classList.add('xterm-sequence-decoration', 'bottom', 'left', colorClass); - }); - } - if (command.endMarker) { - const d = this._instance.xterm!.raw?.registerDecoration({ - marker: command.endMarker - }); - d?.onRender(e => { - e.textContent = 'D'; - e.classList.add('xterm-sequence-decoration', 'bottom', 'right', colorClass); - }); - } - this._currentColor = (this._currentColor + 1) % 2; - }); + const commandDecorations = new Map(); + this._activeDevModeDisposables.value = combinedDisposable( + commandDetection.onCommandFinished(command => { + const colorClass = `color-${this._currentColor}`; + const decorations: IDisposable[] = []; + commandDecorations.set(command, decorations); + if (command.promptStartMarker) { + const d = this._instance.xterm!.raw?.registerDecoration({ + marker: command.promptStartMarker + }); + if (d) { + decorations.push(d); + d.onRender(e => { + e.textContent = 'A'; + e.classList.add('xterm-sequence-decoration', 'top', 'left', colorClass); + }); + } + } + if (command.marker) { + const d = this._instance.xterm!.raw?.registerDecoration({ + marker: command.marker, + x: command.startX + }); + if (d) { + decorations.push(d); + d.onRender(e => { + e.textContent = 'B'; + e.classList.add('xterm-sequence-decoration', 'top', 'right', colorClass); + }); + } + } + if (command.executedMarker) { + const d = this._instance.xterm!.raw?.registerDecoration({ + marker: command.executedMarker, + x: command.executedX + }); + if (d) { + decorations.push(d); + d.onRender(e => { + e.textContent = 'C'; + e.classList.add('xterm-sequence-decoration', 'bottom', 'left', colorClass); + }); + } + } + if (command.endMarker) { + const d = this._instance.xterm!.raw?.registerDecoration({ + marker: command.endMarker + }); + if (d) { + decorations.push(d); + d.onRender(e => { + e.textContent = 'D'; + e.classList.add('xterm-sequence-decoration', 'bottom', 'right', colorClass); + }); + } + } + this._currentColor = (this._currentColor + 1) % 2; + }), + commandDetection.onCommandInvalidated(commands => { + for (const c of commands) { + const decorations = commandDecorations.get(c); + if (decorations) { + dispose(decorations); + } + commandDecorations.delete(c); + } + }) + ); } else { this._activeDevModeDisposables.value = this._instance.capabilities.onDidAddCapabilityType(e => { if (e === TerminalCapability.CommandDetection) { From 3fa7cc6d1493ca5e6ea5b28c0e8097f9f2598dfd Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 16 Nov 2023 10:38:12 -0800 Subject: [PATCH 5/6] Improve cls/clear SI behavior on Windows --- .../commandDetectionCapability.ts | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index 4293052257a49..d4d2892df167a 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -171,7 +171,7 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe get isCommandStorageDisabled() { return that.__isCommandStorageDisabled; } get commandMarkers() { return that._commandMarkers; } set commandMarkers(value) { that._commandMarkers = value; } - get clearCommandsInViewport() { return that._clearCommandsInViewport; } + get clearCommandsInViewport() { return that._clearCommandsInViewport.bind(that); } }, this._logService ); @@ -504,7 +504,8 @@ const enum AdjustCommandStartMarkerConstants { class WindowsPtyHeuristics extends Disposable { private _onCursorMoveListener = this._register(new MutableDisposable()); - private _windowsPromptPollingInProcess: boolean = false; + + private _recentlyPerformedCsiJ = false; constructor( private readonly _terminal: Terminal, @@ -514,11 +515,27 @@ class WindowsPtyHeuristics extends Disposable { ) { super(); + this._register(_terminal.parser.registerCsiHandler({ final: 'J' }, params => { + if (params.length >= 1 && (params[0] === 2 || params[0] === 3)) { + this._recentlyPerformedCsiJ = true; + this._hooks.clearCommandsInViewport(); + } + // We don't want to override xterm.js' default behavior, just augment it + return false; + })); + this._register(this._capability.onBeforeCommandFinished(command => { - // For a Windows backend we cannot listen to CSI J, instead we assume running clear or - // cls will clear all commands in the viewport. This is not perfect but it's right most - // of the time. + if (this._recentlyPerformedCsiJ) { + this._recentlyPerformedCsiJ = false; + return; + } + + // For older Windows backends we cannot listen to CSI J, instead we assume running clear + // or cls will clear all commands in the viewport. This is not perfect but it's right + // most of the time. if (command.command.trim().toLowerCase() === 'clear' || command.command.trim().toLowerCase() === 'cls') { + this._tryAdjustCommandStartMarkerScheduler?.cancel(); + this._tryAdjustCommandStartMarkerScheduler = undefined; this._hooks.clearCommandsInViewport(); this._capability.currentCommand.isInvalid = true; this._hooks.onCurrentCommandInvalidatedEmitter.fire({ reason: CommandInvalidationReason.Windows }); @@ -580,10 +597,6 @@ class WindowsPtyHeuristics extends Disposable { private _tryAdjustCommandStartMarkerPollCount: number = 0; async handleCommandStart() { - - if (this._windowsPromptPollingInProcess) { - this._windowsPromptPollingInProcess = false; - } this._capability.currentCommand.commandStartX = this._terminal.buffer.active.cursorX; // On Windows track all cursor movements after the command start sequence From 1c7839a55a9a65b0d55b863ac4561985b8befa30 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Thu, 16 Nov 2023 13:05:21 -0800 Subject: [PATCH 6/6] Start next command in tests to finish previous command --- .../capabilities/commandDetectionCapability.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/vs/workbench/contrib/terminal/test/browser/capabilities/commandDetectionCapability.test.ts b/src/vs/workbench/contrib/terminal/test/browser/capabilities/commandDetectionCapability.test.ts index 778cb44e2b239..aa656c1a43120 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/capabilities/commandDetectionCapability.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/capabilities/commandDetectionCapability.test.ts @@ -60,6 +60,12 @@ suite('CommandDetectionCapability', () => { capability.handleCommandFinished(exitCode); } + async function printCommandStart(prompt: string) { + capability.handlePromptStart(); + await writeP(xterm, `\r${prompt}`); + } + + setup(async () => { disposables = new DisposableStore(); const TerminalCtor = (await importAMDNodeModule('@xterm/xterm', 'lib/xterm.js')).Terminal; @@ -86,6 +92,7 @@ suite('CommandDetectionCapability', () => { test('should add commands for expected capability method calls', async () => { await printStandardCommand('$ ', 'echo foo', 'foo', undefined, 0); + await printCommandStart('$ '); assertCommands([{ command: 'echo foo', exitCode: 0, @@ -96,6 +103,7 @@ suite('CommandDetectionCapability', () => { test('should trim the command when command executed appears on the following line', async () => { await printStandardCommand('$ ', 'echo foo\r\n', 'foo', undefined, 0); + await printCommandStart('$ '); assertCommands([{ command: 'echo foo', exitCode: 0, @@ -108,6 +116,7 @@ suite('CommandDetectionCapability', () => { test('should add cwd to commands when it\'s set', async () => { await printStandardCommand('$ ', 'echo foo', 'foo', '/home', 0); await printStandardCommand('$ ', 'echo bar', 'bar', '/home/second', 0); + await printCommandStart('$ '); assertCommands([ { command: 'echo foo', exitCode: 0, cwd: '/home', marker: { line: 0 } }, { command: 'echo bar', exitCode: 0, cwd: '/home/second', marker: { line: 2 } } @@ -116,6 +125,7 @@ suite('CommandDetectionCapability', () => { test('should add old cwd to commands if no cwd sequence is output', async () => { await printStandardCommand('$ ', 'echo foo', 'foo', '/home', 0); await printStandardCommand('$ ', 'echo bar', 'bar', undefined, 0); + await printCommandStart('$ '); assertCommands([ { command: 'echo foo', exitCode: 0, cwd: '/home', marker: { line: 0 } }, { command: 'echo bar', exitCode: 0, cwd: '/home', marker: { line: 2 } } @@ -124,6 +134,7 @@ suite('CommandDetectionCapability', () => { test('should use an undefined cwd if it\'s not set initially', async () => { await printStandardCommand('$ ', 'echo foo', 'foo', undefined, 0); await printStandardCommand('$ ', 'echo bar', 'bar', '/home', 0); + await printCommandStart('$ '); assertCommands([ { command: 'echo foo', exitCode: 0, cwd: undefined, marker: { line: 0 } }, { command: 'echo bar', exitCode: 0, cwd: '/home', marker: { line: 2 } }