microsoft / vscode

Visual Studio Code
https://code.visualstudio.com
MIT License
164.12k stars 29.27k forks source link

Merge Editor: Provide a 4-editor view which also shows the base editor #155277

Closed joaomoreno closed 2 years ago

joaomoreno commented 2 years ago

Verification steps:

{
    "languageId": "typescript",
    "base": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { toErrorMessage } from 'vs/base/common/errorMessage';\nimport { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';\nimport { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel';\nimport { ICommandService } from 'vs/platform/commands/common/commands';\nimport { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';\nimport { IStatusbarEntry, ShowTooltipCommand } from 'vs/workbench/services/statusbar/browser/statusbar';\nimport { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';\nimport { IThemeService, ThemeColor } from 'vs/platform/theme/common/themeService';\nimport { isThemeColor } from 'vs/editor/common/editorCommon';\nimport { addDisposableListener, EventType, hide, show, append, EventHelper } from 'vs/base/browser/dom';\nimport { INotificationService } from 'vs/platform/notification/common/notification';\nimport { assertIsDefined } from 'vs/base/common/types';\nimport { Command } from 'vs/editor/common/languages';\nimport { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';\nimport { KeyCode } from 'vs/base/common/keyCodes';\nimport { renderIcon, renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';\nimport { spinningLoading, syncing } from 'vs/platform/theme/common/iconRegistry';\nimport { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';\nimport { isMarkdownString, markdownStringEqual } from 'vs/base/common/htmlContent';\nimport { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';\nimport { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch';\n\nexport class StatusbarEntryItem extends Disposable {\n\n\tprivate readonly label: StatusBarCodiconLabel;\n\n\tprivate entry: IStatusbarEntry | undefined = undefined;\n\n\tprivate readonly foregroundListener = this._register(new MutableDisposable());\n\tprivate readonly backgroundListener = this._register(new MutableDisposable());\n\n\tprivate readonly commandMouseListener = this._register(new MutableDisposable());\n\tprivate readonly commandTouchListener = this._register(new MutableDisposable());\n\tprivate readonly commandKeyboardListener = this._register(new MutableDisposable());\n\n\tprivate hover: ICustomHover | undefined = undefined;\n\n\treadonly labelContainer: HTMLElement;\n\n\tget name(): string {\n\t\treturn assertIsDefined(this.entry).name;\n\t}\n\n\tget hasCommand(): boolean {\n\t\treturn typeof this.entry?.command !== 'undefined';\n\t}\n\n\tconstructor(\n\t\tprivate container: HTMLElement,\n\t\tentry: IStatusbarEntry,\n\t\tprivate readonly hoverDelegate: IHoverDelegate,\n\t\t@ICommandService private readonly commandService: ICommandService,\n\t\t@INotificationService private readonly notificationService: INotificationService,\n\t\t@ITelemetryService private readonly telemetryService: ITelemetryService,\n\t\t@IThemeService private readonly themeService: IThemeService\n\t) {\n\t\tsuper();\n\n\t\t// Label Container\n\t\tthis.labelContainer = document.createElement('a');\n\t\tthis.labelContainer.tabIndex = -1; // allows screen readers to read title, but still prevents tab focus.\n\t\tthis.labelContainer.setAttribute('role', 'button');\n\t\tthis._register(Gesture.addTarget(this.labelContainer)); // enable touch\n\n\t\t// Label (with support for progress)\n\t\tthis.label = new StatusBarCodiconLabel(this.labelContainer);\n\n\t\t// Add to parent\n\t\tthis.container.appendChild(this.labelContainer);\n\n\t\tthis.update(entry);\n\t}\n\n\tupdate(entry: IStatusbarEntry): void {\n\n\t\t// Update: Progress\n\t\tthis.label.showProgress = entry.showProgress ?? false;\n\n\t\t// Update: Text\n\t\tif (!this.entry || entry.text !== this.entry.text) {\n\t\t\tthis.label.text = entry.text;\n\n\t\t\tif (entry.text) {\n\t\t\t\tshow(this.labelContainer);\n\t\t\t} else {\n\t\t\t\thide(this.labelContainer);\n\t\t\t}\n\t\t}\n\n\t\t// Update: ARIA label\n\t\t//\n\t\t// Set the aria label on both elements so screen readers would read\n\t\t// the correct thing without duplication #96210\n\n\t\tif (!this.entry || entry.ariaLabel !== this.entry.ariaLabel) {\n\t\t\tthis.container.setAttribute('aria-label', entry.ariaLabel);\n\t\t\tthis.labelContainer.setAttribute('aria-label', entry.ariaLabel);\n\t\t}\n\n\t\tif (!this.entry || entry.role !== this.entry.role) {\n\t\t\tthis.labelContainer.setAttribute('role', entry.role || 'button');\n\t\t}\n\n\t\t// Update: Hover\n\t\tif (!this.entry || !this.isEqualTooltip(this.entry, entry)) {\n\t\t\tconst hoverContents = isMarkdownString(entry.tooltip) ? { markdown: entry.tooltip, markdownNotSupportedFallback: undefined } : entry.tooltip;\n\t\t\tif (this.hover) {\n\t\t\t\tthis.hover.update(hoverContents);\n\t\t\t} else {\n\t\t\t\tthis.hover = this._register(setupCustomHover(this.hoverDelegate, this.container, hoverContents));\n\t\t\t}\n\t\t}\n\n\t\t// Update: Command\n\t\tif (!this.entry || entry.command !== this.entry.command) {\n\t\t\tthis.commandMouseListener.clear();\n\t\t\tthis.commandTouchListener.clear();\n\t\t\tthis.commandKeyboardListener.clear();\n\n\t\t\tconst command = entry.command;\n\t\t\tif (command && (command !== ShowTooltipCommand || this.hover) /* \"Show Hover\" is only valid when we have a hover */) {\n\t\t\t\tthis.commandMouseListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command));\n\t\t\t\tthis.commandTouchListener.value = addDisposableListener(this.labelContainer, TouchEventType.Tap, () => this.executeCommand(command));\n\t\t\t\tthis.commandKeyboardListener.value = addDisposableListener(this.labelContainer, EventType.KEY_DOWN, e => {\n\t\t\t\t\tconst event = new StandardKeyboardEvent(e);\n\t\t\t\t\tif (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) {\n\t\t\t\t\t\tEventHelper.stop(e);\n\n\t\t\t\t\t\tthis.executeCommand(command);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tthis.labelContainer.classList.remove('disabled');\n\t\t\t} else {\n\t\t\t\tthis.labelContainer.classList.add('disabled');\n\t\t\t}\n\t\t}\n\n\t\t// Update: Beak\n\t\tif (!this.entry || entry.showBeak !== this.entry.showBeak) {\n\t\t\tif (entry.showBeak) {\n\t\t\t\tthis.container.classList.add('has-beak');\n\t\t\t} else {\n\t\t\t\tthis.container.classList.remove('has-beak');\n\t\t\t}\n\t\t}\n\n\t\t// Update: Foreground\n\t\tif (!this.entry || entry.color !== this.entry.color) {\n\t\t\tthis.applyColor(this.labelContainer, entry.color);\n\t\t}\n\n\t\t// Update: Background\n\t\tif (!this.entry || entry.backgroundColor !== this.entry.backgroundColor) {\n\t\t\tthis.container.classList.toggle('has-background-color', !!entry.backgroundColor);\n\t\t\tthis.applyColor(this.container, entry.backgroundColor, true);\n\t\t}\n\n\t\t// Remember for next round\n\t\tthis.entry = entry;\n\t}\n\n\tprivate isEqualTooltip({ tooltip }: IStatusbarEntry, { tooltip: otherTooltip }: IStatusbarEntry) {\n\t\tif (tooltip === undefined) {\n\t\t\treturn otherTooltip === undefined;\n\t\t}\n\n\t\tif (isMarkdownString(tooltip)) {\n\t\t\treturn isMarkdownString(otherTooltip) && markdownStringEqual(tooltip, otherTooltip);\n\t\t}\n\n\t\treturn tooltip === otherTooltip;\n\t}\n\n\tprivate async executeCommand(command: string | Command): Promise<void> {\n\n\t\t// Custom command from us: Show tooltip\n\t\tif (command === ShowTooltipCommand) {\n\t\t\tthis.hover?.show(true /* focus */);\n\t\t}\n\n\t\t// Any other command is going through command service\n\t\telse {\n\t\t\tconst id = typeof command === 'string' ? command : command.id;\n\t\t\tconst args = typeof command === 'string' ? [] : command.arguments ?? [];\n\n\t\t\tthis.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id, from: 'status bar' });\n\t\t\ttry {\n\t\t\t\tawait this.commandService.executeCommand(id, ...args);\n\t\t\t} catch (error) {\n\t\t\t\tthis.notificationService.error(toErrorMessage(error));\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate applyColor(container: HTMLElement, color: string | ThemeColor | undefined, isBackground?: boolean): void {\n\t\tlet colorResult: string | undefined = undefined;\n\n\t\tif (isBackground) {\n\t\t\tthis.backgroundListener.clear();\n\t\t} else {\n\t\t\tthis.foregroundListener.clear();\n\t\t}\n\n\t\tif (color) {\n\t\t\tif (isThemeColor(color)) {\n\t\t\t\tcolorResult = this.themeService.getColorTheme().getColor(color.id)?.toString();\n\n\t\t\t\tconst listener = this.themeService.onDidColorThemeChange(theme => {\n\t\t\t\t\tconst colorValue = theme.getColor(color.id)?.toString();\n\n\t\t\t\t\tif (isBackground) {\n\t\t\t\t\t\tcontainer.style.backgroundColor = colorValue ?? '';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontainer.style.color = colorValue ?? '';\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tif (isBackground) {\n\t\t\t\t\tthis.backgroundListener.value = listener;\n\t\t\t\t} else {\n\t\t\t\t\tthis.foregroundListener.value = listener;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcolorResult = color;\n\t\t\t}\n\t\t}\n\n\t\tif (isBackground) {\n\t\t\tcontainer.style.backgroundColor = colorResult ?? '';\n\t\t} else {\n\t\t\tcontainer.style.color = colorResult ?? '';\n\t\t}\n\t}\n}\n\nclass StatusBarCodiconLabel extends SimpleIconLabel {\n\n\tprivate progressCodicon = renderIcon(syncing);\n\n\tprivate currentText = '';\n\tprivate currentShowProgress = false;\n\n\tconstructor(\n\t\tprivate readonly container: HTMLElement\n\t) {\n\t\tsuper(container);\n\t}\n\n\tset showProgress(showProgress: boolean | 'syncing' | 'loading') {\n\t\tif (this.currentShowProgress !== showProgress) {\n\t\t\tthis.currentShowProgress = !!showProgress;\n\t\t\tthis.progressCodicon = renderIcon(showProgress === 'loading' ? spinningLoading : syncing);\n\t\t\tthis.text = this.currentText;\n\t\t}\n\t}\n\n\toverride set text(text: string) {\n\n\t\t// Progress: insert progress codicon as first element as needed\n\t\t// but keep it stable so that the animation does not reset\n\t\tif (this.currentShowProgress) {\n\n\t\t\t// Append as needed\n\t\t\tif (this.container.firstChild !== this.progressCodicon) {\n\t\t\t\tthis.container.appendChild(this.progressCodicon);\n\t\t\t}\n\n\t\t\t// Remove others\n\t\t\tfor (const node of Array.from(this.container.childNodes)) {\n\t\t\t\tif (node !== this.progressCodicon) {\n\t\t\t\t\tnode.remove();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If we have text to show, add a space to separate from progress\n\t\t\tlet textContent = text ?? '';\n\t\t\tif (textContent) {\n\t\t\t\ttextContent = ` ${textContent}`;\n\t\t\t}\n\n\t\t\t// Append new elements\n\t\t\tappend(this.container, ...renderLabelWithIcons(textContent));\n\t\t}\n\n\t\t// No Progress: no special handling\n\t\telse {\n\t\t\tsuper.text = text;\n\t\t}\n\t}\n}\n",
    "input1": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { toErrorMessage } from 'vs/base/common/errorMessage';\nimport { toErrorMessage2 } from 'vs/base/common/errorMessage2';\nimport { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';\nimport { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel';\nimport { ICommandService } from 'vs/platform/commands/common/commands';\nimport { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';\nimport { IStatusbarEntry, ShowTooltipCommand } from 'vs/workbench/services/statusbar/browser/statusbar';\nimport { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';\nimport { IThemeService, ThemeColor } from 'vs/platform/theme/common/themeService';\nimport { isThemeColor } from 'vs/editor/common/editorCommon';\nimport { addDisposableListener, EventType, hide, show, append, EventHelper } from 'vs/base/browser/dom';\nimport { INotificationService } from 'vs/platform/notification/common/notification';\nimport { assertIsDefined } from 'vs/base/common/types';\nimport { Command } from 'vs/editor/common/languages';\nimport { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';\nimport { KeyCode } from 'vs/base/common/keyCodes';\nimport { renderIcon, renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';\nimport { spinningLoading, syncing } from 'vs/platform/theme/common/iconRegistry';\nimport { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';\nimport { isMarkdownString, markdownStringEqual } from 'vs/base/common/htmlContent';\nimport { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';\nimport { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch';\n\nexport class StatusbarEntryItem extends Disposable {\n\n\tprivate readonly label: StatusBarCodiconLabel;\n\n\tprivate entry: IStatusbarEntry | undefined = undefined;\n\n\tprivate readonly foregroundListener = this._register(new MutableDisposable());\n\tprivate readonly backgroundListener = this._register(new MutableDisposable());\n\n\tprivate readonly commandMouseListener = this._register(new MutableDisposable());\n\tprivate readonly commandTouchListener = this._register(new MutableDisposable());\n\tprivate readonly commandKeyboardListener = this._register(new MutableDisposable());\n\n\tprivate hover: ICustomHover | undefined = undefined;\n\n\treadonly labelContainer: HTMLElement;\n\treadonly beakContainer: HTMLElement;\n\n\tget name(): string {\n\t\treturn assertIsDefined(this.entry).name;\n\t}\n\n\tget hasCommand(): boolean {\n\t\treturn typeof this.entry?.command !== 'undefined';\n\t}\n\n\tconstructor(\n\t\tprivate container: HTMLElement,\n\t\tentry: IStatusbarEntry,\n\t\tprivate readonly hoverDelegate: IHoverDelegate,\n\t\t@ICommandService private readonly commandService: ICommandService,\n\t\t@INotificationService private readonly notificationService: INotificationService,\n\t\t@ITelemetryService private readonly telemetryService: ITelemetryService,\n\t\t@IThemeService private readonly themeService: IThemeService\n\t) {\n\t\tsuper();\n\n\t\t// Label Container\n\t\tthis.labelContainer = document.createElement('a');\n\t\tthis.labelContainer.tabIndex = -1; // allows screen readers to read title, but still prevents tab focus.\n\t\tthis.labelContainer.setAttribute('role', 'button');\n\t\tthis._register(Gesture.addTarget(this.labelContainer)); // enable touch\n\n\t\t// Label (with support for progress)\n\t\tthis.label = new StatusBarCodiconLabel(this.labelContainer);\n\t\tthis.container.appendChild(this.labelContainer);\n\n\t\t// Beak Container\n\t\tthis.beakContainer = document.createElement('div');\n\t\tthis.beakContainer.className = 'status-bar-item-beak-container';\n\t\tthis.container.appendChild(this.beakContainer);\n\n\t\tthis.update(entry);\n\t}\n\n\tupdate(entry: IStatusbarEntry): void {\n\n\t\t// Update: Progress\n\t\tthis.label.showProgress = entry.showProgress ?? false;\n\n\t\t// Update: Text\n\t\tif (!this.entry || entry.text !== this.entry.text) {\n\t\t\tthis.label.text = entry.text;\n\n\t\t\tif (entry.text) {\n\t\t\t\tshow(this.labelContainer);\n\t\t\t} else {\n\t\t\t\thide(this.labelContainer);\n\t\t\t}\n\t\t}\n\n\t\t// Update: ARIA label\n\t\t//\n\t\t// Set the aria label on both elements so screen readers would read\n\t\t// the correct thing without duplication #96210\n\n\t\tif (!this.entry || entry.ariaLabel !== this.entry.ariaLabel) {\n\t\t\tthis.container.setAttribute('aria-label', entry.ariaLabel);\n\t\t\tthis.labelContainer.setAttribute('aria-label', entry.ariaLabel);\n\t\t}\n\n\t\tif (!this.entry || entry.role !== this.entry.role) {\n\t\t\tthis.labelContainer.setAttribute('role', entry.role || 'button');\n\t\t}\n\n\t\t// Update: Hover\n\t\tif (!this.entry || !this.isEqualTooltip(this.entry, entry)) {\n\t\t\tconst hoverContents = isMarkdownString(entry.tooltip) ? { markdown: entry.tooltip, markdownNotSupportedFallback: undefined } : entry.tooltip;\n\t\t\tif (this.hover) {\n\t\t\t\tthis.hover.update(hoverContents);\n\t\t\t} else {\n\t\t\t\tthis.hover = this._register(setupCustomHover(this.hoverDelegate, this.container, hoverContents));\n\t\t\t}\n\t\t}\n\n\t\t// Update: Command\n\t\tif (!this.entry || entry.command !== this.entry.command) {\n\t\t\tthis.commandMouseListener.clear();\n\t\t\tthis.commandTouchListener.clear();\n\t\t\tthis.commandKeyboardListener.clear();\n\n\t\t\tconst command = entry.command;\n\t\t\tif (command && (command !== ShowTooltipCommand || this.hover) /* \"Show Hover\" is only valid when we have a hover */) {\n\t\t\t\tthis.commandMouseListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command));\n\t\t\t\tthis.commandTouchListener.value = addDisposableListener(this.labelContainer, TouchEventType.Tap, () => this.executeCommand(command));\n\t\t\t\tthis.commandKeyboardListener.value = addDisposableListener(this.labelContainer, EventType.KEY_DOWN, e => {\n\t\t\t\t\tconst event = new StandardKeyboardEvent(e);\n\t\t\t\t\tif (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) {\n\t\t\t\t\t\tEventHelper.stop(e);\n\n\t\t\t\t\t\tthis.executeCommand(command);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tthis.labelContainer.classList.remove('disabled');\n\t\t\t} else {\n\t\t\t\tthis.labelContainer.classList.add('disabled');\n\t\t\t}\n\t\t}\n\n\t\t// Update: Beak\n\t\tif (!this.entry || entry.showBeak !== this.entry.showBeak) {\n\t\t\tif (entry.showBeak) {\n\t\t\t\tthis.container.classList.add('has-beak');\n\t\t\t} else {\n\t\t\t\tthis.container.classList.remove('has-beak');\n\t\t\t}\n\t\t}\n\n\t\t// Update: Foreground\n\t\tif (!this.entry || entry.color !== this.entry.color) {\n\t\t\tthis.applyColor(this.labelContainer, entry.color);\n\t\t}\n\n\t\t// Update: Background\n\t\tif (!this.entry || entry.backgroundColor !== this.entry.backgroundColor) {\n\t\t\tthis.container.classList.toggle('has-background-color', !!entry.backgroundColor);\n\t\t\tthis.applyColor(this.container, entry.backgroundColor, true);\n\t\t}\n\n\t\t// Remember for next round\n\t\tthis.entry = entry;\n\t}\n\n\tprivate isEqualTooltip({ tooltip }: IStatusbarEntry, { tooltip: otherTooltip }: IStatusbarEntry) {\n\t\tif (tooltip === undefined) {\n\t\t\treturn otherTooltip === undefined;\n\t\t}\n\n\t\tif (isMarkdownString(tooltip)) {\n\t\t\treturn isMarkdownString(otherTooltip) && markdownStringEqual(tooltip, otherTooltip);\n\t\t}\n\n\t\treturn tooltip === otherTooltip;\n\t}\n\n\tprivate async executeCommand(command: string | Command): Promise<void> {\n\n\t\t// Custom command from us: Show tooltip\n\t\tif (command === ShowTooltipCommand) {\n\t\t\tthis.hover?.show(true /* focus */);\n\t\t}\n\n\t\t// Any other command is going through command service\n\t\telse {\n\t\t\tconst id = typeof command === 'string' ? command : command.id;\n\t\t\tconst args = typeof command === 'string' ? [] : command.arguments ?? [];\n\n\t\t\tthis.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id, from: 'status bar' });\n\t\t\ttry {\n\t\t\t\tawait this.commandService.executeCommand(id, ...args);\n\t\t\t} catch (error) {\n\t\t\t\tthis.notificationService.error(toErrorMessage(error));\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate applyColor(container: HTMLElement, color: string | ThemeColor | undefined, isBackground?: boolean): void {\n\t\tlet colorResult: string | undefined = undefined;\n\n\t\tif (isBackground) {\n\t\t\tthis.backgroundListener.clear();\n\t\t} else {\n\t\t\tthis.foregroundListener.clear();\n\t\t}\n\n\t\tif (color) {\n\t\t\tif (isThemeColor(color)) {\n\t\t\t\tcolorResult = this.themeService.getColorTheme().getColor(color.id)?.toString();\n\n\t\t\t\tconst listener = this.themeService.onDidColorThemeChange(theme => {\n\t\t\t\t\tconst colorValue = theme.getColor(color.id)?.toString();\n\n\t\t\t\t\tif (isBackground) {\n\t\t\t\t\t\tcontainer.style.backgroundColor = colorValue ?? '';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontainer.style.color = colorValue ?? '';\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tif (isBackground) {\n\t\t\t\t\tthis.backgroundListener.value = listener;\n\t\t\t\t} else {\n\t\t\t\t\tthis.foregroundListener.value = listener;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcolorResult = color;\n\t\t\t}\n\t\t}\n\n\t\tif (isBackground) {\n\t\t\tcontainer.style.backgroundColor = colorResult ?? '';\n\t\t} else {\n\t\t\tcontainer.style.color = colorResult ?? '';\n\t\t}\n\t}\n}\n\nclass StatusBarCodiconLabel extends SimpleIconLabel {\n\n\tprivate progressCodicon = renderIcon(syncing);\n\n\tprivate currentText = '';\n\tprivate currentShowProgress = false;\n\n\tconstructor(\n\t\tprivate readonly container: HTMLElement\n\t) {\n\t\tsuper(container);\n\t}\n\n\tset showProgress(showProgress: boolean | 'syncing' | 'loading') {\n\t\tif (this.currentShowProgress !== showProgress) {\n\t\t\tthis.currentShowProgress = !!showProgress;\n\t\t\tthis.progressCodicon = renderIcon(showProgress === 'loading' ? spinningLoading : syncing);\n\t\t\tthis.text = this.currentText;\n\t\t}\n\t}\n\n\toverride set text(text: string) {\n\n\t\t// Progress: insert progress codicon as first element as needed\n\t\t// but keep it stable so that the animation does not reset\n\t\tif (this.currentShowProgress) {\n\n\t\t\t// Append as needed\n\t\t\tif (this.container.firstChild !== this.progressCodicon) {\n\t\t\t\tthis.container.appendChild(this.progressCodicon);\n\t\t\t}\n\n\t\t\t// Remove others\n\t\t\tfor (const node of Array.from(this.container.childNodes)) {\n\t\t\t\tif (node !== this.progressCodicon) {\n\t\t\t\t\tnode.remove();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If we have text to show, add a space to separate from progress\n\t\t\tlet textContent = text ?? '';\n\t\t\tif (textContent) {\n\t\t\t\ttextContent = ` ${textContent}`;\n\t\t\t}\n\n\t\t\t// Append new elements\n\t\t\tappend(this.container, ...renderLabelWithIcons(textContent));\n\t\t}\n\n\t\t// No Progress: no special handling\n\t\telse {\n\t\t\tsuper.text = text;\n\t\t}\n\t}\n}\n",
    "input2": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { toErrorMessage } from 'vs/base/common/errorMessage';\nimport { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';\nimport { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel';\nimport { ICommandService } from 'vs/platform/commands/common/commands';\nimport { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';\nimport { IStatusbarEntry, ShowTooltipCommand } from 'vs/workbench/services/statusbar/browser/statusbar';\nimport { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';\nimport { IThemeService, ThemeColor } from 'vs/platform/theme/common/themeService';\nimport { isThemeColor } from 'vs/editor/common/editorCommon';\nimport { addDisposableListener, EventType, hide, show, append, EventHelper } from 'vs/base/browser/dom';\nimport { INotificationService } from 'vs/platform/notification/common/notification';\nimport { assertIsDefined } from 'vs/base/common/types';\nimport { Command } from 'vs/editor/common/languages';\nimport { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';\nimport { KeyCode } from 'vs/base/common/keyCodes';\nimport { renderIcon, renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';\nimport { spinningLoading, syncing } from 'vs/platform/theme/common/iconRegistry';\nimport { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';\nimport { isMarkdownString, markdownStringEqual } from 'vs/base/common/htmlContent';\nimport { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';\nimport { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch';\n\nexport class StatusbarEntryItem extends Disposable {\n\n\tprivate readonly label: StatusBarCodiconLabel;\n\n\tprivate entry: IStatusbarEntry | undefined = undefined;\n\n\tprivate readonly foregroundListener = this._register(new MutableDisposable());\n\tprivate readonly backgroundListener = this._register(new MutableDisposable());\n\n\tprivate readonly commandMouseListener = this._register(new MutableDisposable());\n\tprivate readonly commandTouchListener = this._register(new MutableDisposable());\n\tprivate readonly commandKeyboardListener = this._register(new MutableDisposable());\n\n\tprivate hover: ICustomHover | undefined = undefined;\n\n\treadonly labelContainer: HTMLElement;\n\treadonly beakContainer: HTMLElement;\n\n\tget name(): string {\n\t\treturn assertIsDefined(this.entry).name;\n\t}\n\n\tget hasCommand(): boolean {\n\t\treturn typeof this.entry?.command !== 'undefined';\n\t}\n\n\tconstructor(\n\t\tprivate container: HTMLElement,\n\t\tentry: IStatusbarEntry,\n\t\tprivate readonly hoverDelegate: IHoverDelegate,\n\t\t@ICommandService private readonly commandService: ICommandService,\n\t\t@INotificationService private readonly notificationService: INotificationService,\n\t\t@ITelemetryService private readonly telemetryService: ITelemetryService,\n\t\t@IThemeService private readonly themeService: IThemeService\n\t) {\n\t\tsuper();\n\n\t\t// Label Container\n\t\tthis.labelContainer = document.createElement('a');\n\t\tthis.labelContainer.tabIndex = -1; // allows screen readers to read title, but still prevents tab focus.\n\t\tthis.labelContainer.setAttribute('role', 'button');\n\t\tthis._register(Gesture.addTarget(this.labelContainer)); // enable touch\n\n\t\t// Label (with support for progress)\n\t\tthis.label = new StatusBarCodiconLabel(this.labelContainer);\n\n\t\t// Add to parent\n\t\tthis.container.appendChild(this.labelContainer);\n\n\t\t// Beak Container\n\t\tthis.beakContainer = document.createElement('div');\n\t\tthis.beakContainer.className = 'status-bar-beak-container';\n\n\t\t// Add to parent\n\t\tthis.container.appendChild(this.beakContainer);\n\n\t\tthis.update(entry);\n\t}\n\n\tupdate(entry: IStatusbarEntry): void {\n\n\t\t// Update: Progress\n\t\tthis.label.showProgress = entry.showProgress ?? false;\n\n\t\t// Update: Text\n\t\tif (!this.entry || entry.text !== this.entry.text) {\n\t\t\tthis.label.text = entry.text;\n\n\t\t\tif (entry.text) {\n\t\t\t\tshow(this.labelContainer);\n\t\t\t} else {\n\t\t\t\thide(this.labelContainer);\n\t\t\t}\n\t\t}\n\n\t\t// Update: ARIA label\n\t\t//\n\t\t// Set the aria label on both elements so screen readers would read\n\t\t// the correct thing without duplication #96210\n\n\t\tif (!this.entry || entry.ariaLabel !== this.entry.ariaLabel) {\n\t\t\tthis.container.setAttribute('aria-label', entry.ariaLabel);\n\t\t\tthis.labelContainer.setAttribute('aria-label', entry.ariaLabel);\n\t\t}\n\n\t\tif (!this.entry || entry.role !== this.entry.role) {\n\t\t\tthis.labelContainer.setAttribute('role', entry.role || 'button');\n\t\t}\n\n\t\t// Update: Hover\n\t\tif (!this.entry || !this.isEqualTooltip(this.entry, entry)) {\n\t\t\tconst hoverContents = isMarkdownString(entry.tooltip) ? { markdown: entry.tooltip, markdownNotSupportedFallback: undefined } : entry.tooltip;\n\t\t\tif (this.hover) {\n\t\t\t\tthis.hover.update(hoverContents);\n\t\t\t} else {\n\t\t\t\tthis.hover = this._register(setupCustomHover(this.hoverDelegate, this.container, hoverContents));\n\t\t\t}\n\t\t}\n\n\t\t// Update: Command\n\t\tif (!this.entry || entry.command !== this.entry.command) {\n\t\t\tthis.commandMouseListener.clear();\n\t\t\tthis.commandTouchListener.clear();\n\t\t\tthis.commandKeyboardListener.clear();\n\n\t\t\tconst command = entry.command;\n\t\t\tif (command && (command !== ShowTooltipCommand || this.hover) /* \"Show Hover\" is only valid when we have a hover */) {\n\t\t\t\tthis.commandMouseListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command));\n\t\t\t\tthis.commandTouchListener.value = addDisposableListener(this.labelContainer, TouchEventType.Tap, () => this.executeCommand(command));\n\t\t\t\tthis.commandKeyboardListener.value = addDisposableListener(this.labelContainer, EventType.KEY_DOWN, e => {\n\t\t\t\t\tconst event = new StandardKeyboardEvent(e);\n\t\t\t\t\tif (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) {\n\t\t\t\t\t\tEventHelper.stop(e);\n\n\t\t\t\t\t\tthis.executeCommand(command);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tthis.labelContainer.classList.remove('disabled');\n\t\t\t} else {\n\t\t\t\tthis.labelContainer.classList.add('disabled');\n\t\t\t}\n\t\t}\n\n\t\t// Update: Beak\n\t\tif (!this.entry || entry.showBeak !== this.entry.showBeak) {\n\t\t\tif (entry.showBeak) {\n\t\t\t\tthis.container.classList.add('has-beak');\n\t\t\t} else {\n\t\t\t\tthis.container.classList.remove('has-beak');\n\t\t\t}\n\t\t}\n\n\t\t// Update: Foreground\n\t\tif (!this.entry || entry.color !== this.entry.color) {\n\t\t\tthis.applyColor(this.labelContainer, entry.color);\n\t\t}\n\n\t\t// Update: Background\n\t\tif (!this.entry || entry.backgroundColor !== this.entry.backgroundColor) {\n\t\t\tthis.container.classList.toggle('has-background-color', !!entry.backgroundColor);\n\t\t\tthis.applyColor(this.container, entry.backgroundColor, true);\n\t\t}\n\n\t\t// Remember for next round\n\t\tthis.entry = entry;\n\t}\n\n\tprivate isEqualTooltip({ tooltip }: IStatusbarEntry, { tooltip: otherTooltip }: IStatusbarEntry) {\n\t\tif (tooltip === undefined) {\n\t\t\treturn otherTooltip === undefined;\n\t\t}\n\n\t\tif (isMarkdownString(tooltip)) {\n\t\t\treturn isMarkdownString(otherTooltip) && markdownStringEqual(tooltip, otherTooltip);\n\t\t}\n\n\t\treturn tooltip === otherTooltip;\n\t}\n\n\tprivate async executeCommand(command: string | Command): Promise<void> {\n\n\t\t// Custom command from us: Show tooltip\n\t\tif (command === ShowTooltipCommand) {\n\t\t\tthis.hover?.show(true /* focus */);\n\t\t}\n\n\t\t// Any other command is going through command service\n\t\telse {\n\t\t\tconst id = typeof command === 'string' ? command : command.id;\n\t\t\tconst args = typeof command === 'string' ? [] : command.arguments ?? [];\n\n\t\t\tthis.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id, from: 'status bar' });\n\t\t\ttry {\n\t\t\t\tawait this.commandService.executeCommand(id, ...args);\n\t\t\t} catch (error) {\n\t\t\t\tthis.notificationService.error(toErrorMessage(error));\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate applyColor(container: HTMLElement, color: string | ThemeColor | undefined, isBackground?: boolean): void {\n\t\tlet colorResult: string | undefined = undefined;\n\n\t\tif (isBackground) {\n\t\t\tthis.backgroundListener.clear();\n\t\t} else {\n\t\t\tthis.foregroundListener.clear();\n\t\t}\n\n\t\tif (color) {\n\t\t\tif (isThemeColor(color)) {\n\t\t\t\tcolorResult = this.themeService.getColorTheme().getColor(color.id)?.toString();\n\n\t\t\t\tconst listener = this.themeService.onDidColorThemeChange(theme => {\n\t\t\t\t\tconst colorValue = theme.getColor(color.id)?.toString();\n\n\t\t\t\t\tif (isBackground) {\n\t\t\t\t\t\tcontainer.style.backgroundColor = colorValue ?? '';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontainer.style.color = colorValue ?? '';\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tif (isBackground) {\n\t\t\t\t\tthis.backgroundListener.value = listener;\n\t\t\t\t} else {\n\t\t\t\t\tthis.foregroundListener.value = listener;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcolorResult = color;\n\t\t\t}\n\t\t}\n\n\t\tif (isBackground) {\n\t\t\tcontainer.style.backgroundColor = colorResult ?? '';\n\t\t} else {\n\t\t\tcontainer.style.color = colorResult ?? '';\n\t\t}\n\t}\n}\n\nclass StatusBarCodiconLabel extends SimpleIconLabel {\n\n\tprivate progressCodicon = renderIcon(syncing);\n\n\tprivate currentText = '';\n\tprivate currentShowProgress = false;\n\n\tconstructor(\n\t\tprivate readonly container: HTMLElement\n\t) {\n\t\tsuper(container);\n\t}\n\n\tset showProgress(showProgress: boolean | 'syncing' | 'loading') {\n\t\tif (this.currentShowProgress !== showProgress) {\n\t\t\tthis.currentShowProgress = !!showProgress;\n\t\t\tthis.progressCodicon = renderIcon(showProgress === 'loading' ? spinningLoading : syncing);\n\t\t\tthis.text = this.currentText;\n\t\t}\n\t}\n\n\toverride set text(text: string) {\n\n\t\t// Progress: insert progress codicon as first element as needed\n\t\t// but keep it stable so that the animation does not reset\n\t\tif (this.currentShowProgress) {\n\n\t\t\t// Append as needed\n\t\t\tif (this.container.firstChild !== this.progressCodicon) {\n\t\t\t\tthis.container.appendChild(this.progressCodicon);\n\t\t\t}\n\n\t\t\t// Remove others\n\t\t\tfor (const node of Array.from(this.container.childNodes)) {\n\t\t\t\tif (node !== this.progressCodicon) {\n\t\t\t\t\tnode.remove();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If we have text to show, add a space to separate from progress\n\t\t\tlet textContent = text ?? '';\n\t\t\tif (textContent) {\n\t\t\t\ttextContent = ` ${textContent}`;\n\t\t\t}\n\n\t\t\t// Append new elements\n\t\t\tappend(this.container, ...renderLabelWithIcons(textContent));\n\t\t}\n\n\t\t// No Progress: no special handling\n\t\telse {\n\t\t\tsuper.text = text;\n\t\t}\n\t}\n}\n",
    "result": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { toErrorMessage } from 'vs/base/common/errorMessage';\nimport { toErrorMessage2 } from 'vs/base/common/errorMessage2';\nimport { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';\nimport { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel';\nimport { ICommandService } from 'vs/platform/commands/common/commands';\nimport { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';\nimport { IStatusbarEntry, ShowTooltipCommand } from 'vs/workbench/services/statusbar/browser/statusbar';\nimport { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';\nimport { IThemeService, ThemeColor } from 'vs/platform/theme/common/themeService';\nimport { isThemeColor } from 'vs/editor/common/editorCommon';\nimport { addDisposableListener, EventType, hide, show, append, EventHelper } from 'vs/base/browser/dom';\nimport { INotificationService } from 'vs/platform/notification/common/notification';\nimport { assertIsDefined } from 'vs/base/common/types';\nimport { Command } from 'vs/editor/common/languages';\nimport { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';\nimport { KeyCode } from 'vs/base/common/keyCodes';\nimport { renderIcon, renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';\nimport { spinningLoading, syncing } from 'vs/platform/theme/common/iconRegistry';\nimport { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';\nimport { isMarkdownString, markdownStringEqual } from 'vs/base/common/htmlContent';\nimport { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';\nimport { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch';\n\nexport class StatusbarEntryItem extends Disposable {\n\n\tprivate readonly label: StatusBarCodiconLabel;\n\n\tprivate entry: IStatusbarEntry | undefined = undefined;\n\n\tprivate readonly foregroundListener = this._register(new MutableDisposable());\n\tprivate readonly backgroundListener = this._register(new MutableDisposable());\n\n\tprivate readonly commandMouseListener = this._register(new MutableDisposable());\n\tprivate readonly commandTouchListener = this._register(new MutableDisposable());\n\tprivate readonly commandKeyboardListener = this._register(new MutableDisposable());\n\n\tprivate hover: ICustomHover | undefined = undefined;\n\n\treadonly labelContainer: HTMLElement;\n\n\tget name(): string {\n\t\treturn assertIsDefined(this.entry).name;\n\t}\n\n\tget hasCommand(): boolean {\n\t\treturn typeof this.entry?.command !== 'undefined';\n\t}\n\n\tconstructor(\n\t\tprivate container: HTMLElement,\n\t\tentry: IStatusbarEntry,\n\t\tprivate readonly hoverDelegate: IHoverDelegate,\n\t\t@ICommandService private readonly commandService: ICommandService,\n\t\t@INotificationService private readonly notificationService: INotificationService,\n\t\t@ITelemetryService private readonly telemetryService: ITelemetryService,\n\t\t@IThemeService private readonly themeService: IThemeService\n\t) {\n\t\tsuper();\n\n\t\t// Label Container\n\t\tthis.labelContainer = document.createElement('a');\n\t\tthis.labelContainer.tabIndex = -1; // allows screen readers to read title, but still prevents tab focus.\n\t\tthis.labelContainer.setAttribute('role', 'button');\n\t\tthis._register(Gesture.addTarget(this.labelContainer)); // enable touch\n\n\t\t// Label (with support for progress)\n\t\tthis.label = new StatusBarCodiconLabel(this.labelContainer);\n\t\tthis.container.appendChild(this.labelContainer);\n\n\t\tthis.update(entry);\n\t}\n\n\tupdate(entry: IStatusbarEntry): void {\n\n\t\t// Update: Progress\n\t\tthis.label.showProgress = entry.showProgress ?? false;\n\n\t\t// Update: Text\n\t\tif (!this.entry || entry.text !== this.entry.text) {\n\t\t\tthis.label.text = entry.text;\n\n\t\t\tif (entry.text) {\n\t\t\t\tshow(this.labelContainer);\n\t\t\t} else {\n\t\t\t\thide(this.labelContainer);\n\t\t\t}\n\t\t}\n\n\t\t// Update: ARIA label\n\t\t//\n\t\t// Set the aria label on both elements so screen readers would read\n\t\t// the correct thing without duplication #96210\n\n\t\tif (!this.entry || entry.ariaLabel !== this.entry.ariaLabel) {\n\t\t\tthis.container.setAttribute('aria-label', entry.ariaLabel);\n\t\t\tthis.labelContainer.setAttribute('aria-label', entry.ariaLabel);\n\t\t}\n\n\t\tif (!this.entry || entry.role !== this.entry.role) {\n\t\t\tthis.labelContainer.setAttribute('role', entry.role || 'button');\n\t\t}\n\n\t\t// Update: Hover\n\t\tif (!this.entry || !this.isEqualTooltip(this.entry, entry)) {\n\t\t\tconst hoverContents = isMarkdownString(entry.tooltip) ? { markdown: entry.tooltip, markdownNotSupportedFallback: undefined } : entry.tooltip;\n\t\t\tif (this.hover) {\n\t\t\t\tthis.hover.update(hoverContents);\n\t\t\t} else {\n\t\t\t\tthis.hover = this._register(setupCustomHover(this.hoverDelegate, this.container, hoverContents));\n\t\t\t}\n\t\t}\n\n\t\t// Update: Command\n\t\tif (!this.entry || entry.command !== this.entry.command) {\n\t\t\tthis.commandMouseListener.clear();\n\t\t\tthis.commandTouchListener.clear();\n\t\t\tthis.commandKeyboardListener.clear();\n\n\t\t\tconst command = entry.command;\n\t\t\tif (command && (command !== ShowTooltipCommand || this.hover) /* \"Show Hover\" is only valid when we have a hover */) {\n\t\t\t\tthis.commandMouseListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command));\n\t\t\t\tthis.commandTouchListener.value = addDisposableListener(this.labelContainer, TouchEventType.Tap, () => this.executeCommand(command));\n\t\t\t\tthis.commandKeyboardListener.value = addDisposableListener(this.labelContainer, EventType.KEY_DOWN, e => {\n\t\t\t\t\tconst event = new StandardKeyboardEvent(e);\n\t\t\t\t\tif (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) {\n\t\t\t\t\t\tEventHelper.stop(e);\n\n\t\t\t\t\t\tthis.executeCommand(command);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tthis.labelContainer.classList.remove('disabled');\n\t\t\t} else {\n\t\t\t\tthis.labelContainer.classList.add('disabled');\n\t\t\t}\n\t\t}\n\n\t\t// Update: Beak\n\t\tif (!this.entry || entry.showBeak !== this.entry.showBeak) {\n\t\t\tif (entry.showBeak) {\n\t\t\t\tthis.container.classList.add('has-beak');\n\t\t\t} else {\n\t\t\t\tthis.container.classList.remove('has-beak');\n\t\t\t}\n\t\t}\n\n\t\t// Update: Foreground\n\t\tif (!this.entry || entry.color !== this.entry.color) {\n\t\t\tthis.applyColor(this.labelContainer, entry.color);\n\t\t}\n\n\t\t// Update: Background\n\t\tif (!this.entry || entry.backgroundColor !== this.entry.backgroundColor) {\n\t\t\tthis.container.classList.toggle('has-background-color', !!entry.backgroundColor);\n\t\t\tthis.applyColor(this.container, entry.backgroundColor, true);\n\t\t}\n\n\t\t// Remember for next round\n\t\tthis.entry = entry;\n\t}\n\n\tprivate isEqualTooltip({ tooltip }: IStatusbarEntry, { tooltip: otherTooltip }: IStatusbarEntry) {\n\t\tif (tooltip === undefined) {\n\t\t\treturn otherTooltip === undefined;\n\t\t}\n\n\t\tif (isMarkdownString(tooltip)) {\n\t\t\treturn isMarkdownString(otherTooltip) && markdownStringEqual(tooltip, otherTooltip);\n\t\t}\n\n\t\treturn tooltip === otherTooltip;\n\t}\n\n\tprivate async executeCommand(command: string | Command): Promise<void> {\n\n\t\t// Custom command from us: Show tooltip\n\t\tif (command === ShowTooltipCommand) {\n\t\t\tthis.hover?.show(true /* focus */);\n\t\t}\n\n\t\t// Any other command is going through command service\n\t\telse {\n\t\t\tconst id = typeof command === 'string' ? command : command.id;\n\t\t\tconst args = typeof command === 'string' ? [] : command.arguments ?? [];\n\n\t\t\tthis.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id, from: 'status bar' });\n\t\t\ttry {\n\t\t\t\tawait this.commandService.executeCommand(id, ...args);\n\t\t\t} catch (error) {\n\t\t\t\tthis.notificationService.error(toErrorMessage(error));\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate applyColor(container: HTMLElement, color: string | ThemeColor | undefined, isBackground?: boolean): void {\n\t\tlet colorResult: string | undefined = undefined;\n\n\t\tif (isBackground) {\n\t\t\tthis.backgroundListener.clear();\n\t\t} else {\n\t\t\tthis.foregroundListener.clear();\n\t\t}\n\n\t\tif (color) {\n\t\t\tif (isThemeColor(color)) {\n\t\t\t\tcolorResult = this.themeService.getColorTheme().getColor(color.id)?.toString();\n\n\t\t\t\tconst listener = this.themeService.onDidColorThemeChange(theme => {\n\t\t\t\t\tconst colorValue = theme.getColor(color.id)?.toString();\n\n\t\t\t\t\tif (isBackground) {\n\t\t\t\t\t\tcontainer.style.backgroundColor = colorValue ?? '';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontainer.style.color = colorValue ?? '';\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tif (isBackground) {\n\t\t\t\t\tthis.backgroundListener.value = listener;\n\t\t\t\t} else {\n\t\t\t\t\tthis.foregroundListener.value = listener;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcolorResult = color;\n\t\t\t}\n\t\t}\n\n\t\tif (isBackground) {\n\t\t\tcontainer.style.backgroundColor = colorResult ?? '';\n\t\t} else {\n\t\t\tcontainer.style.color = colorResult ?? '';\n\t\t}\n\t}\n}\n\nclass StatusBarCodiconLabel extends SimpleIconLabel {\n\n\tprivate progressCodicon = renderIcon(syncing);\n\n\tprivate currentText = '';\n\tprivate currentShowProgress = false;\n\n\tconstructor(\n\t\tprivate readonly container: HTMLElement\n\t) {\n\t\tsuper(container);\n\t}\n\n\tset showProgress(showProgress: boolean | 'syncing' | 'loading') {\n\t\tif (this.currentShowProgress !== showProgress) {\n\t\t\tthis.currentShowProgress = !!showProgress;\n\t\t\tthis.progressCodicon = renderIcon(showProgress === 'loading' ? spinningLoading : syncing);\n\t\t\tthis.text = this.currentText;\n\t\t}\n\t}\n\n\toverride set text(text: string) {\n\n\t\t// Progress: insert progress codicon as first element as needed\n\t\t// but keep it stable so that the animation does not reset\n\t\tif (this.currentShowProgress) {\n\n\t\t\t// Append as needed\n\t\t\tif (this.container.firstChild !== this.progressCodicon) {\n\t\t\t\tthis.container.appendChild(this.progressCodicon);\n\t\t\t}\n\n\t\t\t// Remove others\n\t\t\tfor (const node of Array.from(this.container.childNodes)) {\n\t\t\t\tif (node !== this.progressCodicon) {\n\t\t\t\t\tnode.remove();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If we have text to show, add a space to separate from progress\n\t\t\tlet textContent = text ?? '';\n\t\t\tif (textContent) {\n\t\t\t\ttextContent = ` ${textContent}`;\n\t\t\t}\n\n\t\t\t// Append new elements\n\t\t\tappend(this.container, ...renderLabelWithIcons(textContent));\n\t\t}\n\n\t\t// No Progress: no special handling\n\t\telse {\n\t\t\tsuper.text = text;\n\t\t}\n\t}\n}\n",
    "initialResult": "/*---------------------------------------------------------------------------------------------\n *  Copyright (c) Microsoft Corporation. All rights reserved.\n *  Licensed under the MIT License. See License.txt in the project root for license information.\n *--------------------------------------------------------------------------------------------*/\n\nimport { toErrorMessage } from 'vs/base/common/errorMessage';\nimport { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';\nimport { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel';\nimport { ICommandService } from 'vs/platform/commands/common/commands';\nimport { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';\nimport { IStatusbarEntry, ShowTooltipCommand } from 'vs/workbench/services/statusbar/browser/statusbar';\nimport { WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';\nimport { IThemeService, ThemeColor } from 'vs/platform/theme/common/themeService';\nimport { isThemeColor } from 'vs/editor/common/editorCommon';\nimport { addDisposableListener, EventType, hide, show, append, EventHelper } from 'vs/base/browser/dom';\nimport { INotificationService } from 'vs/platform/notification/common/notification';\nimport { assertIsDefined } from 'vs/base/common/types';\nimport { Command } from 'vs/editor/common/languages';\nimport { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';\nimport { KeyCode } from 'vs/base/common/keyCodes';\nimport { renderIcon, renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';\nimport { spinningLoading, syncing } from 'vs/platform/theme/common/iconRegistry';\nimport { ICustomHover, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';\nimport { isMarkdownString, markdownStringEqual } from 'vs/base/common/htmlContent';\nimport { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';\nimport { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch';\n\nexport class StatusbarEntryItem extends Disposable {\n\n\tprivate readonly label: StatusBarCodiconLabel;\n\n\tprivate entry: IStatusbarEntry | undefined = undefined;\n\n\tprivate readonly foregroundListener = this._register(new MutableDisposable());\n\tprivate readonly backgroundListener = this._register(new MutableDisposable());\n\n\tprivate readonly commandMouseListener = this._register(new MutableDisposable());\n\tprivate readonly commandTouchListener = this._register(new MutableDisposable());\n\tprivate readonly commandKeyboardListener = this._register(new MutableDisposable());\n\n\tprivate hover: ICustomHover | undefined = undefined;\n\n\treadonly labelContainer: HTMLElement;\n\treadonly beakContainer: HTMLElement;\n\n\tget name(): string {\n\t\treturn assertIsDefined(this.entry).name;\n\t}\n\n\tget hasCommand(): boolean {\n\t\treturn typeof this.entry?.command !== 'undefined';\n\t}\n\n\tconstructor(\n\t\tprivate container: HTMLElement,\n\t\tentry: IStatusbarEntry,\n\t\tprivate readonly hoverDelegate: IHoverDelegate,\n\t\t@ICommandService private readonly commandService: ICommandService,\n\t\t@INotificationService private readonly notificationService: INotificationService,\n\t\t@ITelemetryService private readonly telemetryService: ITelemetryService,\n\t\t@IThemeService private readonly themeService: IThemeService\n\t) {\n\t\tsuper();\n\n\t\t// Label Container\n\t\tthis.labelContainer = document.createElement('a');\n\t\tthis.labelContainer.tabIndex = -1; // allows screen readers to read title, but still prevents tab focus.\n\t\tthis.labelContainer.setAttribute('role', 'button');\n\t\tthis._register(Gesture.addTarget(this.labelContainer)); // enable touch\n\n\t\t// Label (with support for progress)\n\t\tthis.label = new StatusBarCodiconLabel(this.labelContainer);\n\t\tthis.container.appendChild(this.labelContainer);\n\n\t\t// Beak Container\n\t\tthis.beakContainer = document.createElement('div');\n<<<<<<< c:\\dev\\microsoft\\diffing-dataset\\merges\\demo1\\input1.ts\n\t\tthis.beakContainer.className = 'status-bar-item-beak-container';\n=======\n\t\tthis.beakContainer.className = 'status-bar-beak-container';\n\n\t\t// Add to parent\n>>>>>>> c:\\dev\\microsoft\\diffing-dataset\\merges\\demo1\\input2.ts\n\t\tthis.container.appendChild(this.beakContainer);\n\n\t\tthis.update(entry);\n\t}\n\n\tupdate(entry: IStatusbarEntry): void {\n\n\t\t// Update: Progress\n\t\tthis.label.showProgress = entry.showProgress ?? false;\n\n\t\t// Update: Text\n\t\tif (!this.entry || entry.text !== this.entry.text) {\n\t\t\tthis.label.text = entry.text;\n\n\t\t\tif (entry.text) {\n\t\t\t\tshow(this.labelContainer);\n\t\t\t} else {\n\t\t\t\thide(this.labelContainer);\n\t\t\t}\n\t\t}\n\n\t\t// Update: ARIA label\n\t\t//\n\t\t// Set the aria label on both elements so screen readers would read\n\t\t// the correct thing without duplication #96210\n\n\t\tif (!this.entry || entry.ariaLabel !== this.entry.ariaLabel) {\n\t\t\tthis.container.setAttribute('aria-label', entry.ariaLabel);\n\t\t\tthis.labelContainer.setAttribute('aria-label', entry.ariaLabel);\n\t\t}\n\n\t\tif (!this.entry || entry.role !== this.entry.role) {\n\t\t\tthis.labelContainer.setAttribute('role', entry.role || 'button');\n\t\t}\n\n\t\t// Update: Hover\n\t\tif (!this.entry || !this.isEqualTooltip(this.entry, entry)) {\n\t\t\tconst hoverContents = isMarkdownString(entry.tooltip) ? { markdown: entry.tooltip, markdownNotSupportedFallback: undefined } : entry.tooltip;\n\t\t\tif (this.hover) {\n\t\t\t\tthis.hover.update(hoverContents);\n\t\t\t} else {\n\t\t\t\tthis.hover = this._register(setupCustomHover(this.hoverDelegate, this.container, hoverContents));\n\t\t\t}\n\t\t}\n\n\t\t// Update: Command\n\t\tif (!this.entry || entry.command !== this.entry.command) {\n\t\t\tthis.commandMouseListener.clear();\n\t\t\tthis.commandTouchListener.clear();\n\t\t\tthis.commandKeyboardListener.clear();\n\n\t\t\tconst command = entry.command;\n\t\t\tif (command && (command !== ShowTooltipCommand || this.hover) /* \"Show Hover\" is only valid when we have a hover */) {\n\t\t\t\tthis.commandMouseListener.value = addDisposableListener(this.labelContainer, EventType.CLICK, () => this.executeCommand(command));\n\t\t\t\tthis.commandTouchListener.value = addDisposableListener(this.labelContainer, TouchEventType.Tap, () => this.executeCommand(command));\n\t\t\t\tthis.commandKeyboardListener.value = addDisposableListener(this.labelContainer, EventType.KEY_DOWN, e => {\n\t\t\t\t\tconst event = new StandardKeyboardEvent(e);\n\t\t\t\t\tif (event.equals(KeyCode.Space) || event.equals(KeyCode.Enter)) {\n\t\t\t\t\t\tEventHelper.stop(e);\n\n\t\t\t\t\t\tthis.executeCommand(command);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tthis.labelContainer.classList.remove('disabled');\n\t\t\t} else {\n\t\t\t\tthis.labelContainer.classList.add('disabled');\n\t\t\t}\n\t\t}\n\n\t\t// Update: Beak\n\t\tif (!this.entry || entry.showBeak !== this.entry.showBeak) {\n\t\t\tif (entry.showBeak) {\n\t\t\t\tthis.container.classList.add('has-beak');\n\t\t\t} else {\n\t\t\t\tthis.container.classList.remove('has-beak');\n\t\t\t}\n\t\t}\n\n\t\t// Update: Foreground\n\t\tif (!this.entry || entry.color !== this.entry.color) {\n\t\t\tthis.applyColor(this.labelContainer, entry.color);\n\t\t}\n\n\t\t// Update: Background\n\t\tif (!this.entry || entry.backgroundColor !== this.entry.backgroundColor) {\n\t\t\tthis.container.classList.toggle('has-background-color', !!entry.backgroundColor);\n\t\t\tthis.applyColor(this.container, entry.backgroundColor, true);\n\t\t}\n\n\t\t// Remember for next round\n\t\tthis.entry = entry;\n\t}\n\n\tprivate isEqualTooltip({ tooltip }: IStatusbarEntry, { tooltip: otherTooltip }: IStatusbarEntry) {\n\t\tif (tooltip === undefined) {\n\t\t\treturn otherTooltip === undefined;\n\t\t}\n\n\t\tif (isMarkdownString(tooltip)) {\n\t\t\treturn isMarkdownString(otherTooltip) && markdownStringEqual(tooltip, otherTooltip);\n\t\t}\n\n\t\treturn tooltip === otherTooltip;\n\t}\n\n\tprivate async executeCommand(command: string | Command): Promise<void> {\n\n\t\t// Custom command from us: Show tooltip\n\t\tif (command === ShowTooltipCommand) {\n\t\t\tthis.hover?.show(true /* focus */);\n\t\t}\n\n\t\t// Any other command is going through command service\n\t\telse {\n\t\t\tconst id = typeof command === 'string' ? command : command.id;\n\t\t\tconst args = typeof command === 'string' ? [] : command.arguments ?? [];\n\n\t\t\tthis.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', { id, from: 'status bar' });\n\t\t\ttry {\n\t\t\t\tawait this.commandService.executeCommand(id, ...args);\n\t\t\t} catch (error) {\n\t\t\t\tthis.notificationService.error(toErrorMessage(error));\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate applyColor(container: HTMLElement, color: string | ThemeColor | undefined, isBackground?: boolean): void {\n\t\tlet colorResult: string | undefined = undefined;\n\n\t\tif (isBackground) {\n\t\t\tthis.backgroundListener.clear();\n\t\t} else {\n\t\t\tthis.foregroundListener.clear();\n\t\t}\n\n\t\tif (color) {\n\t\t\tif (isThemeColor(color)) {\n\t\t\t\tcolorResult = this.themeService.getColorTheme().getColor(color.id)?.toString();\n\n\t\t\t\tconst listener = this.themeService.onDidColorThemeChange(theme => {\n\t\t\t\t\tconst colorValue = theme.getColor(color.id)?.toString();\n\n\t\t\t\t\tif (isBackground) {\n\t\t\t\t\t\tcontainer.style.backgroundColor = colorValue ?? '';\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcontainer.style.color = colorValue ?? '';\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tif (isBackground) {\n\t\t\t\t\tthis.backgroundListener.value = listener;\n\t\t\t\t} else {\n\t\t\t\t\tthis.foregroundListener.value = listener;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcolorResult = color;\n\t\t\t}\n\t\t}\n\n\t\tif (isBackground) {\n\t\t\tcontainer.style.backgroundColor = colorResult ?? '';\n\t\t} else {\n\t\t\tcontainer.style.color = colorResult ?? '';\n\t\t}\n\t}\n}\n\nclass StatusBarCodiconLabel extends SimpleIconLabel {\n\n\tprivate progressCodicon = renderIcon(syncing);\n\n\tprivate currentText = '';\n\tprivate currentShowProgress = false;\n\n\tconstructor(\n\t\tprivate readonly container: HTMLElement\n\t) {\n\t\tsuper(container);\n\t}\n\n\tset showProgress(showProgress: boolean | 'syncing' | 'loading') {\n\t\tif (this.currentShowProgress !== showProgress) {\n\t\t\tthis.currentShowProgress = !!showProgress;\n\t\t\tthis.progressCodicon = renderIcon(showProgress === 'loading' ? spinningLoading : syncing);\n\t\t\tthis.text = this.currentText;\n\t\t}\n\t}\n\n\toverride set text(text: string) {\n\n\t\t// Progress: insert progress codicon as first element as needed\n\t\t// but keep it stable so that the animation does not reset\n\t\tif (this.currentShowProgress) {\n\n\t\t\t// Append as needed\n\t\t\tif (this.container.firstChild !== this.progressCodicon) {\n\t\t\t\tthis.container.appendChild(this.progressCodicon);\n\t\t\t}\n\n\t\t\t// Remove others\n\t\t\tfor (const node of Array.from(this.container.childNodes)) {\n\t\t\t\tif (node !== this.progressCodicon) {\n\t\t\t\t\tnode.remove();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If we have text to show, add a space to separate from progress\n\t\t\tlet textContent = text ?? '';\n\t\t\tif (textContent) {\n\t\t\t\ttextContent = ` ${textContent}`;\n\t\t\t}\n\n\t\t\t// Append new elements\n\t\t\tappend(this.container, ...renderLabelWithIcons(textContent));\n\t\t}\n\n\t\t// No Progress: no special handling\n\t\telse {\n\t\t\tsuper.text = text;\n\t\t}\n\t}\n}\n"
}
H-G-Hristov commented 2 years ago

Merge editor appears to work just for git. Can we have it for any two files? I can select and compare two files but can I use a merge editor to merge them?

cadsuara commented 2 years ago

I already suggested something similar in previous exploratory tickets like Explore UX for three-way merge #146091

My comment: https://github.com/microsoft/vscode/issues/146091#issuecomment-1089029759

Check also this blog

jcw- commented 2 years ago

My error rate doing merge conflict resolution drastically dropped once I took the time to learn how to use the 4-editor view - the key is being able to see the base that was each person's starting point. I have since been using p4mergetool for around a decade, and would feel blind without that middle view (the base) at this point.

Would be amazing if this was something that could be turned on in the VS Code implementation!

phiresky commented 2 years ago

the key is being able to see the base that was each person's starting point.

From what I understend you do see the base in the "result" view since opening the merge editor resets the result to a state where everything that's automatically resolved is resolved but every conflict shows the merge base instead

jcw- commented 2 years ago

I think you're describing how a results panel typically works though?

There's value in seeing the base horizontally aligned and in scroll sync with the two changes.

There's value in seeing the entire base, and not a partially merged version, to understand the context of what the surrounding code looked like before either parties changes were made.

mmatrosov commented 2 years ago

I also find dedicated pane with base extremely useful. Just let me attach an image of how it looks in kdiff3 for those not familiar with it (though I prefer to place "base" in the middle):

git-merge-conflict-solving-with-kdiff3-4

And for a more complicated example see how colors on the margin help you visually get what side was changed where:

git-merge-conflict-solving-with-kdiff3-5

bworline commented 2 years ago

Please add this. I would give up using Beyond Compare in a heartbeat if VS Code could [optionally] support the 4-window view.

yairchu commented 2 years ago

159920 was flagged as a duplicate of this issue, though there I requested an easier fix: to remove the current flawed merge editor. Anyhow I'll try to contribute my feedback here too:

The belief that you can merge correctly without knowing the base is a dangerous myth! When you do succeed in doing it often it's because you're merging your own recent code and you remember what the base was, but in general nothing shows you if things were added or removed. You need the base too to know that!

In the first minute of this video https://youtu.be/c3eV5HVdUuk I quizzed the audience on merging without the base and it shows that it can't consistently be done.

hediet commented 2 years ago

The belief that you can merge correctly without knowing the base is a dangerous myth!

We don't have that belief. In the previous version of VS Code we actually replaced the conflicting areas in the result with base, so you could see what base is for every conflict (afaik IntelliJ also starts with base+auto-merged changes, which is basically the same - they also don't have a separate base view).

However, we no longer do that in the last version of VS Code that we just released yesterday (as it breaks other git commands/tools when the merge editor automatically removes all conflict markers). (But still, in that version, you can check and uncheck the checkboxes to reset result to base - you can also use the "compare with base" commands to diff yours/theirs with base.)

The next version will have an (optional) base pane: image

yairchu commented 2 years ago

The belief that you can merge correctly without knowing the base is a dangerous myth!

We don't have that belief.

Good to know. But the fact that the base isn't shows will probably imply and perpetuate this myth to others.

In the previous version of VS Code we actually replaced the conflicting areas in the result with base

Why not simply use diff3 style? That is to convert the conflict to this style if it happened to be in diff2 style (which is a bad default by git that keeps perpetuating the myth).

jesperkristensen commented 2 years ago

This is very needed. It was super annoying when a VS Code update automatically enabled the new merge editor by default that was inferior to the diff3 conflict markers i had already set up.

Defaults are also important. Having to code review incorrect merges from my colleges because they didn't know they had to change a default setting before merges is just annoying.

yairchu commented 2 years ago

Defaults are also important. Having to code review incorrect merges from my colleges because they didn't know they had to change a default setting before merges is just annoying.

Btw, I've opened an issue for this: https://github.com/microsoft/vscode/issues/159920

theo-staizen commented 2 years ago

I have been using Kdiff3 and P4Merge for over a decade, exactly because they support 4-panel merges. The base panel is invaluable for any non-trivial merge conflicts and I am ecstatic to see this feature arrive in VSCode. Thank you @hediet <3

Does this PR also add support to use VSCode executable as the git merge tool, including providing all 4 file arguments (base, local, remote, merged). For example, this is my current .gitconfig using p4merge

[merge]
  tool = p4mergetool
  stat = true
[mergetool "p4mergetool"]
  cmd = /Applications/p4merge.app/Contents/MacOS/p4merge "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
  keepBackup = false
  keepTemporaries = false
  trustExitCode = false
  prompt = false
eric-wieser commented 2 years ago

How do we enable the optional base pane in 1.72?

HCanber commented 2 years ago

@eric-wieser It took me a while to find it. When you have the 3 way merge editor open, click the three dots menu and select Show base image

yairchu commented 2 years ago

@eric-wieser It took me a while to find it. When you have the 3 way merge editor open, click the three dots menu and select Show base image