Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
joaomoreno opened this issue Jul 15, 2022 · 16 comments · Fixed by #159923
Closed

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

joaomoreno opened this issue Jul 15, 2022 · 16 comments · Fixed by #159923
Assignees
Labels
feature-request Request for new features or functionality insiders-released Patch has been released in VS Code Insiders merge-editor verification-needed Verification of issue is requested verified Verification succeeded

Comments

@joaomoreno
Copy link
Member

joaomoreno commented Jul 15, 2022

Verification steps:

  • Use the "Open Merge Editor State From JSON" command to load the attached JSON
  • Use the menu behind "..." to toggle base
  • Verify base is shown
{
    "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

This comment was marked as spam.

@cadsuara
Copy link

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

My comment:
#146091 (comment)

Check also this blog

@jcw-
Copy link

jcw- commented Jul 23, 2022

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
Copy link

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-
Copy link

jcw- commented Jul 27, 2022

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
Copy link

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
Copy link

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

@yairchu
Copy link

yairchu commented Sep 2, 2022

#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
Copy link
Member

hediet commented Sep 2, 2022

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
Copy link

yairchu commented Sep 2, 2022

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
Copy link

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.

@vscodenpa vscodenpa added the unreleased Patch has not yet been released in VS Code Insiders label Sep 5, 2022
@yairchu
Copy link

yairchu commented Sep 5, 2022

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: #159920

@vscodenpa vscodenpa added insiders-released Patch has been released in VS Code Insiders and removed unreleased Patch has not yet been released in VS Code Insiders labels Sep 6, 2022
@theo-staizen
Copy link

theo-staizen commented Sep 27, 2022

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

@hediet hediet added the verification-needed Verification of issue is requested label Sep 27, 2022
@lramos15 lramos15 added the verified Verification succeeded label Sep 28, 2022
@eric-wieser
Copy link

How do we enable the optional base pane in 1.72?

@HCanber
Copy link

HCanber commented Oct 19, 2022

@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
Copy link

yairchu commented Oct 20, 2022

@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

  • It shouldn't be off by default
  • And once the default is on, there also shouldn't be an option to turn it off

@github-actions github-actions bot locked and limited conversation to collaborators Oct 23, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
feature-request Request for new features or functionality insiders-released Patch has been released in VS Code Insiders merge-editor verification-needed Verification of issue is requested verified Verification succeeded
Projects
None yet
Development

Successfully merging a pull request may close this issue.