From 36717c6796b48662f4d05f5cebd1a08bb5bb77b9 Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Feb 2022 13:20:20 -0600 Subject: [PATCH 1/4] get it to work --- .../commandDetectionCapability.ts | 12 ++- .../terminal/browser/xterm/decorationAddon.ts | 76 ++++++++++++++----- .../common/capabilities/capabilities.ts | 1 + 3 files changed, 68 insertions(+), 21 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/capabilities/commandDetectionCapability.ts b/src/vs/workbench/contrib/terminal/browser/capabilities/commandDetectionCapability.ts index 82a538cc0deab..c728d1bd92359 100644 --- a/src/vs/workbench/contrib/terminal/browser/capabilities/commandDetectionCapability.ts +++ b/src/vs/workbench/contrib/terminal/browser/capabilities/commandDetectionCapability.ts @@ -8,7 +8,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { ICommandDetectionCapability, TerminalCapability, ITerminalCommand } from 'vs/workbench/contrib/terminal/common/capabilities/capabilities'; import { IBuffer, IDisposable, IMarker, Terminal } from 'xterm'; -interface ICurrentPartialCommand { +export interface ICurrentPartialCommand { previousCommandMarker?: IMarker; promptStartMarker?: IMarker; @@ -39,6 +39,8 @@ export class CommandDetectionCapability implements ICommandDetectionCapability { get commands(): readonly ITerminalCommand[] { return this._commands; } + private readonly _onCommandStarted = new Emitter(); + readonly onCommandStarted = this._onCommandStarted.event; private readonly _onCommandFinished = new Emitter(); readonly onCommandFinished = this._onCommandFinished.event; @@ -70,6 +72,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability { handleCommandStart(): void { this._currentCommand.commandStartX = this._terminal.buffer.active.cursorX; this._currentCommand.commandStartMarker = this._terminal.registerMarker(0); + this._currentCommand.commandStartMarker!.onDispose((c) => console.log('disposed of ', this._currentCommand.commandStartMarker!.id)); // On Windows track all cursor movements after the command start sequence if (this._isWindowsPty) { this._commandMarkers.length = 0; @@ -82,6 +85,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability { } }); } + this._onCommandStarted.fire({ marker: this._currentCommand.promptStartMarker! } as ITerminalCommand); this._logService.debug('CommandDetectionCapability#handleCommandStart', this._currentCommand.commandStartX, this._currentCommand.commandStartMarker?.line); } @@ -139,7 +143,10 @@ export class CommandDetectionCapability implements ICommandDetectionCapability { if (this._currentCommand.commandStartMarker === undefined || !this._terminal.buffer.active) { return; + } else if (this._currentCommand.commandStartMarker.isDisposed) { + console.log(`marker is already disposed of ${this._currentCommand.commandStartMarker!.id.toString()}`); } + console.log('decoration with marker ', this._currentCommand.commandStartMarker!.id); if (command !== undefined && !command.startsWith('\\')) { const buffer = this._terminal.buffer.active; const clonedPartialCommand = { ...this._currentCommand }; @@ -158,6 +165,9 @@ export class CommandDetectionCapability implements ICommandDetectionCapability { this._logService.debug('CommandDetectionCapability#onCommandFinished', newCommand); this._onCommandFinished.fire(newCommand); } + if (this._currentCommand.previousCommandMarker) { + console.log('disposing of previous marker', this._currentCommand.previousCommandMarker.id); + } this._currentCommand.previousCommandMarker?.dispose(); this._currentCommand.previousCommandMarker = this._currentCommand.commandStartMarker; this._currentCommand = {}; diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index 37b897010232f..d94bcbaf65348 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -37,9 +37,11 @@ interface IDisposableDecoration { decoration: IDecoration; disposables: IDisposa export class DecorationAddon extends Disposable implements ITerminalAddon { protected _terminal: Terminal | undefined; private _hoverDelayer: Delayer; - private _commandListener: IDisposable | undefined; + private _commandStartedListener: IDisposable | undefined; + private _commandFinishedListener: IDisposable | undefined; private _contextMenuVisible: boolean = false; private _decorations: Map = new Map(); + private _placeholderDecoration: IDecoration | undefined; private readonly _onDidRequestRunCommand = this._register(new Emitter()); readonly onDidRequestRunCommand = this._onDidRequestRunCommand.event; @@ -54,7 +56,8 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { super(); this._register({ dispose: () => { - this._commandListener?.dispose(); + this._commandStartedListener?.dispose(); + this._commandFinishedListener?.dispose(); this._clearDecorations(); } }); @@ -74,55 +77,83 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { private _attachToCommandCapability(): void { if (this._capabilities.has(TerminalCapability.CommandDetection)) { - this._addCommandListener(); + this._addCommandFinishedListener(); } else { this._register(this._capabilities.onDidAddCapability(c => { if (c === TerminalCapability.CommandDetection) { - this._addCommandListener(); + this._addCommandStartedListener(); + this._addCommandFinishedListener(); } })); } this._register(this._capabilities.onDidRemoveCapability(c => { if (c === TerminalCapability.CommandDetection) { - this._commandListener?.dispose(); + this._commandStartedListener?.dispose(); + this._commandFinishedListener?.dispose(); } })); } - private _addCommandListener(): void { - if (this._commandListener) { + private _addCommandStartedListener(): void { + if (this._commandStartedListener) { return; } const capability = this._capabilities.get(TerminalCapability.CommandDetection); if (!capability) { return; } - this._commandListener = capability.onCommandFinished(c => this.registerCommandDecoration(c)); + this._commandStartedListener = capability.onCommandStarted(command => this.registerCommandDecoration(command)); + } + + + private _addCommandFinishedListener(): void { + if (this._commandFinishedListener) { + return; + } + const capability = this._capabilities.get(TerminalCapability.CommandDetection); + if (!capability) { + return; + } + this._commandFinishedListener = capability.onCommandFinished(command => { + this._placeholderDecoration?.dispose(); + this.registerCommandDecoration(command); + }); } activate(terminal: Terminal): void { this._terminal = terminal; } - registerCommandDecoration(command: ITerminalCommand): IDecoration | undefined { + registerCommandDecoration(command: ITerminalCommand, beforeCommandExecution?: boolean): IDecoration | undefined { + if (!this._terminal) { + return undefined; + } if (!command.marker) { throw new Error(`cannot add a decoration for a command ${JSON.stringify(command)} with no marker`); } - if (!this._terminal) { - return undefined; + if (beforeCommandExecution) { + const decoration = this._terminal.registerDecoration({ marker: command.marker }); + if (!decoration) { + return undefined; + } + decoration.onRender(target => { + this._applyStyles(target); + target.classList.add(DecorationSelector.SkippedColor); + }); + this._placeholderDecoration = decoration; + return decoration; } - const decoration = this._terminal.registerDecoration({ marker: command.marker }); + const decoration = this._terminal.registerDecoration({ marker: command.marker! }); if (!decoration) { return undefined; } decoration.onRender(target => { - if (decoration.element && !this._decorations.get(decoration.marker.id)) { - const disposables = command.exitCode === undefined ? [] : [this._createContextMenu(decoration.element, command), ...this._createHover(decoration.element, command)]; + if (target && !this._decorations.get(decoration.marker.id)) { + const disposables = command.exitCode === undefined ? [] : [this._createContextMenu(target, command), ...this._createHover(target, command)]; this._decorations.set(decoration.marker.id, { decoration, disposables }); } - if (decoration.element?.clientWidth! > 0) { - target.classList.add(DecorationSelector.CommandDecoration); - target.classList.add(DecorationSelector.Codicon); + if (target.clientWidth! > 0) { + this._applyStyles(target); if (command.exitCode === undefined) { target.classList.add(DecorationSelector.SkippedColor); target.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationCommandIconSkipped)}`); @@ -132,14 +163,19 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { } else { target.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationCommandIcon)}`); } - // must be inlined to override the inlined styles from xterm - decoration.element!.style.width = '16px'; - decoration.element!.style.height = '16px'; } }); return decoration; } + private _applyStyles(target: HTMLElement): void { + target.classList.add(DecorationSelector.CommandDecoration); + target.classList.add(DecorationSelector.Codicon); + // must be inlined to override the inlined styles from xterm + target.style.width = '16px'; + target.style.height = '16px'; + } + private _createContextMenu(target: HTMLElement, command: ITerminalCommand): IDisposable { // When the xterm Decoration gets disposed of, its element gets removed from the dom // along with its listeners diff --git a/src/vs/workbench/contrib/terminal/common/capabilities/capabilities.ts b/src/vs/workbench/contrib/terminal/common/capabilities/capabilities.ts index 2801fe320ef18..fb99b3a437cc3 100644 --- a/src/vs/workbench/contrib/terminal/common/capabilities/capabilities.ts +++ b/src/vs/workbench/contrib/terminal/common/capabilities/capabilities.ts @@ -78,6 +78,7 @@ export interface ITerminalCapabilityImplMap { export interface ICommandDetectionCapability { readonly type: TerminalCapability.CommandDetection; readonly commands: readonly ITerminalCommand[]; + readonly onCommandStarted: Event; readonly onCommandFinished: Event; setCwd(value: string): void; setIsWindowsPty(value: boolean): void; From a1a4d22d4b8e8f2c1a767c510fb324bd07ec1d8e Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Feb 2022 13:28:43 -0600 Subject: [PATCH 2/4] fix #143402 and change skipped -> default --- src/vs/platform/terminal/common/terminal.ts | 2 +- .../terminal/browser/media/terminal.css | 4 +-- .../terminal/browser/xterm/decorationAddon.ts | 32 +++++++++++-------- .../terminal/common/terminalColorRegistry.ts | 4 +-- .../terminal/common/terminalConfiguration.ts | 6 ++-- 5 files changed, 27 insertions(+), 21 deletions(-) diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 772820d716ff5..4873c5198e4d7 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -106,7 +106,7 @@ export const enum TerminalSettingId { ShellIntegrationShowWelcome = 'terminal.integrated.shellIntegration.showWelcome', ShellIntegrationCommandIcon = 'terminal.integrated.shellIntegration.commandIcon', ShellIntegrationCommandIconError = 'terminal.integrated.shellIntegration.commandIconError', - ShellIntegrationCommandIconSkipped = 'terminal.integrated.shellIntegration.commandIconSkipped', + ShellIntegrationCommandIconDefault = 'terminal.integrated.shellIntegration.commandIconDefault', ShellIntegrationCommandHistory = 'terminal.integrated.shellIntegration.history' } diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 4131b30743ce8..40fe5ceb2ba64 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -423,11 +423,11 @@ top: 50%; } -.terminal-command-decoration:not(.skipped):hover { +.terminal-command-decoration:not(.default):hover { cursor: pointer; border-radius: 5px; } -.terminal-command-decoration.skipped { +.terminal-command-decoration.default { pointer-events: none; } diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index d94bcbaf65348..a4c0c8214ef2c 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -22,12 +22,12 @@ import { fromNow } from 'vs/base/common/date'; import { toolbarHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { editorGutterDeletedBackground, editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator'; -import { TERMINAL_COMMAND_DECORATION_SKIPPED_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; +import { TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; const enum DecorationSelector { CommandDecoration = 'terminal-command-decoration', ErrorColor = 'error', - SkippedColor = 'skipped', + DefaultColor = 'default', Codicon = 'codicon', XtermScreen = 'xterm-screen' } @@ -136,7 +136,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { } decoration.onRender(target => { this._applyStyles(target); - target.classList.add(DecorationSelector.SkippedColor); + target.classList.add(DecorationSelector.DefaultColor); }); this._placeholderDecoration = decoration; return decoration; @@ -155,8 +155,8 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { if (target.clientWidth! > 0) { this._applyStyles(target); if (command.exitCode === undefined) { - target.classList.add(DecorationSelector.SkippedColor); - target.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationCommandIconSkipped)}`); + target.classList.add(DecorationSelector.DefaultColor); + target.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationCommandIconDefault)}`); } else if (command.exitCode) { target.classList.add(DecorationSelector.ErrorColor); target.classList.add(`codicon-${this._configurationService.getValue(TerminalSettingId.ShellIntegrationCommandIconError)}`); @@ -234,15 +234,21 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { } registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => { - const defaultColor = theme.getColor(editorGutterModifiedBackground); + const successColor = theme.getColor(editorGutterModifiedBackground); const errorColor = theme.getColor(editorGutterDeletedBackground); - const skippedColor = theme.getColor(TERMINAL_COMMAND_DECORATION_SKIPPED_BACKGROUND_COLOR); + const defaultColor = theme.getColor(TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR); const hoverBackgroundColor = theme.getColor(toolbarHoverBackground); - if (!defaultColor || !errorColor || !skippedColor || !hoverBackgroundColor) { - return; + + if (successColor) { + collector.addRule(`.${DecorationSelector.CommandDecoration} { color: ${successColor.toString()}; } `); + } + if (errorColor) { + collector.addRule(`.${DecorationSelector.CommandDecoration}.${DecorationSelector.ErrorColor} { color: ${errorColor.toString()}; } `); + } + if (defaultColor) { + collector.addRule(`.${DecorationSelector.CommandDecoration}.${DecorationSelector.DefaultColor} { color: ${defaultColor.toString()};} `); + } + if (hoverBackgroundColor) { + collector.addRule(`.${DecorationSelector.CommandDecoration}:not(.${DecorationSelector.DefaultColor}):hover { background-color: ${hoverBackgroundColor.toString()}; }`); } - collector.addRule(`.${DecorationSelector.CommandDecoration} { color: ${defaultColor.toString()}; } `); - collector.addRule(`.${DecorationSelector.CommandDecoration}.${DecorationSelector.ErrorColor} { color: ${errorColor.toString()}; } `); - collector.addRule(`.${DecorationSelector.CommandDecoration}.${DecorationSelector.SkippedColor} { color: ${skippedColor.toString()};} `); - collector.addRule(`.${DecorationSelector.CommandDecoration}: not(.${DecorationSelector.SkippedColor}): hover { background-color: ${hoverBackgroundColor.toString()}; }`); }); diff --git a/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts index 3527f0d2588d3..b93911f10504a 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts @@ -27,11 +27,11 @@ export const TERMINAL_SELECTION_BACKGROUND_COLOR = registerColor('terminal.selec dark: '#FFFFFF40', hc: '#FFFFFF80' }, nls.localize('terminal.selectionBackground', 'The selection background color of the terminal.')); -export const TERMINAL_COMMAND_DECORATION_SKIPPED_BACKGROUND_COLOR = registerColor('terminalCommandDecoration.skippedBackground', { +export const TERMINAL_COMMAND_DECORATION_DEFAULT_BACKGROUND_COLOR = registerColor('terminalCommandDecoration.defaultBackground', { light: '#00000040', dark: '#ffffff40', hc: '#ffffff80' -}, nls.localize('terminalCommandDecoration.skippedBackground', 'The terminal command decoration background color when the command was skipped (undefined exit code).')); +}, nls.localize('terminalCommandDecoration.defaultBackground', 'The default terminal command decoration background color.')); export const TERMINAL_BORDER_COLOR = registerColor('terminal.border', { dark: PANEL_BORDER, light: PANEL_BORDER, diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index e221966b3c611..4645183849bfc 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -106,17 +106,17 @@ const terminalConfiguration: IConfigurationNode = { [TerminalSettingId.ShellIntegrationCommandIcon]: { type: 'string', default: 'primitive-dot', - description: localize('terminal.integrated.shellIntegration.commandIcon', "Controls the icon that will be used for each command in terminals with shell integration enabled that do not have an associated exit code. Set to '' to hide the icon.") + description: localize('terminal.integrated.shellIntegration.commandIconSuccess', "Controls the icon that will be used for each command in terminals with shell integration enabled that do not have an associated exit code. Set to '' to hide the icon.") }, [TerminalSettingId.ShellIntegrationCommandIconError]: { type: 'string', default: 'error-small', description: localize('terminal.integrated.shellIntegration.commandIconError', "Controls the icon that will be used for each command in terminals with shell integration enabled that do have an associated exit code. Set to '' to hide the icon.") }, - [TerminalSettingId.ShellIntegrationCommandIconSkipped]: { + [TerminalSettingId.ShellIntegrationCommandIconDefault]: { type: 'string', default: 'circle-outline', - description: localize('terminal.integrated.shellIntegration.commandIconSkipped', "Controls the icon that will be used for skipped/empty commands. Set to '' to hide the icon.") + description: localize('terminal.integrated.shellIntegration.commandIconDefault', "Controls the icon that will be used for skipped/empty commands. Set to '' to hide the icon.") }, [TerminalSettingId.TabsFocusMode]: { type: 'string', From 2c9ef0cb3d981d9b2f39298cabbe4e1838d9f1ef Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Feb 2022 13:32:21 -0600 Subject: [PATCH 3/4] remove console.logs --- .../browser/capabilities/commandDetectionCapability.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/vs/workbench/contrib/terminal/browser/capabilities/commandDetectionCapability.ts b/src/vs/workbench/contrib/terminal/browser/capabilities/commandDetectionCapability.ts index c728d1bd92359..80305b29c450c 100644 --- a/src/vs/workbench/contrib/terminal/browser/capabilities/commandDetectionCapability.ts +++ b/src/vs/workbench/contrib/terminal/browser/capabilities/commandDetectionCapability.ts @@ -72,7 +72,7 @@ export class CommandDetectionCapability implements ICommandDetectionCapability { handleCommandStart(): void { this._currentCommand.commandStartX = this._terminal.buffer.active.cursorX; this._currentCommand.commandStartMarker = this._terminal.registerMarker(0); - this._currentCommand.commandStartMarker!.onDispose((c) => console.log('disposed of ', this._currentCommand.commandStartMarker!.id)); + // On Windows track all cursor movements after the command start sequence if (this._isWindowsPty) { this._commandMarkers.length = 0; @@ -143,10 +143,8 @@ export class CommandDetectionCapability implements ICommandDetectionCapability { if (this._currentCommand.commandStartMarker === undefined || !this._terminal.buffer.active) { return; - } else if (this._currentCommand.commandStartMarker.isDisposed) { - console.log(`marker is already disposed of ${this._currentCommand.commandStartMarker!.id.toString()}`); } - console.log('decoration with marker ', this._currentCommand.commandStartMarker!.id); + if (command !== undefined && !command.startsWith('\\')) { const buffer = this._terminal.buffer.active; const clonedPartialCommand = { ...this._currentCommand }; @@ -165,9 +163,6 @@ export class CommandDetectionCapability implements ICommandDetectionCapability { this._logService.debug('CommandDetectionCapability#onCommandFinished', newCommand); this._onCommandFinished.fire(newCommand); } - if (this._currentCommand.previousCommandMarker) { - console.log('disposing of previous marker', this._currentCommand.previousCommandMarker.id); - } this._currentCommand.previousCommandMarker?.dispose(); this._currentCommand.previousCommandMarker = this._currentCommand.commandStartMarker; this._currentCommand = {}; From 3ff4dd72dd1f25a2c66fc11e3b40b0d73e472e8c Mon Sep 17 00:00:00 2001 From: meganrogge Date: Fri, 18 Feb 2022 13:34:27 -0600 Subject: [PATCH 4/4] delete excl --- .../workbench/contrib/terminal/browser/xterm/decorationAddon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts index a4c0c8214ef2c..7690451636431 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/decorationAddon.ts @@ -142,7 +142,7 @@ export class DecorationAddon extends Disposable implements ITerminalAddon { return decoration; } - const decoration = this._terminal.registerDecoration({ marker: command.marker! }); + const decoration = this._terminal.registerDecoration({ marker: command.marker }); if (!decoration) { return undefined; }