From f618bf3befda9b9c5bade12bc77cb33dff8d1284 Mon Sep 17 00:00:00 2001 From: Benjamin Christopher Simmonds <44439583+benibenj@users.noreply.github.com> Date: Thu, 8 Feb 2024 18:16:28 +0100 Subject: [PATCH] Custom hover for editor tabs (#204742) --- .../browser/parts/editor/editorTabsControl.ts | 46 ++++++++++++++++++- .../parts/editor/multiEditorTabsControl.ts | 15 +++--- .../parts/editor/singleEditorTabsControl.ts | 9 +--- 3 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index 3d202d8d48b..058d0a91a92 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -25,7 +25,7 @@ import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillEditorsDragData, isWindowDraggedOver } from 'vs/workbench/browser/dnd'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorGroupsView, IEditorGroupView, IEditorPartsView, IInternalEditorOpenOptions } from 'vs/workbench/browser/parts/editor/editor'; -import { IEditorCommandsContext, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, EditorsOrder, EditorInputCapabilities, IToolbarActions, GroupIdentifier } from 'vs/workbench/common/editor'; +import { IEditorCommandsContext, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, EditorsOrder, EditorInputCapabilities, IToolbarActions, GroupIdentifier, Verbosity } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ResourceContextKey, ActiveEditorPinnedContext, ActiveEditorStickyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, ActiveEditorFirstInGroupContext, ActiveEditorAvailableEditorIdsContext, applyAvailableEditorIds, ActiveEditorLastInGroupContext } from 'vs/workbench/common/contextkeys'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; @@ -44,6 +44,11 @@ import { IAuxiliaryEditorPart, MergeGroupMode } from 'vs/workbench/services/edit import { isMacintosh } from 'vs/base/common/platform'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { MarkdownString, MarkdownStringTextNewlineStyle } from 'vs/base/common/htmlContent'; +import { ITooltipMarkdownString } from 'vs/base/browser/ui/iconLabel/iconLabelHover'; +import { IDecorationsService } from 'vs/workbench/services/decorations/common/decorations'; +import { IHoverDelegate } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; export class EditorCommandsContextActionRunner extends ActionRunner { @@ -135,7 +140,9 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC @IQuickInputService protected quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, @IEditorResolverService private readonly editorResolverService: IEditorResolverService, - @IHostService private readonly hostService: IHostService + @IHostService private readonly hostService: IHostService, + @IDecorationsService private readonly decorationsService: IDecorationsService, + @IHoverService private readonly hoverService: IHoverService ) { super(themeService); @@ -444,6 +451,41 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC return this.groupsView.partOptions.tabHeight !== 'compact' ? EditorTabsControl.EDITOR_TAB_HEIGHT.normal : EditorTabsControl.EDITOR_TAB_HEIGHT.compact; } + protected getHoverTitle(editor: EditorInput): ITooltipMarkdownString { + const title = editor.getTitle(Verbosity.LONG); + const markdown = new MarkdownString(title); + + if (editor.resource) { + const decoration = this.decorationsService.getDecoration(editor.resource, false); + if (decoration) { + const decorations = decoration.tooltip.split('• '); + const decorationString = `• ${decorations.join('\n• ')}`; + + markdown.appendText('\n', MarkdownStringTextNewlineStyle.Paragraph); + markdown.appendText(decorationString, MarkdownStringTextNewlineStyle.Break); + } + } + + return { + markdown, + markdownNotSupportedFallback: title + }; + } + + protected getHoverDelegate(): IHoverDelegate { + return { + delay: 500, + showHover: options => { + return this.hoverService.showHover({ + ...options, + persistence: { + hideOnHover: true + } + }); + } + }; + } + protected updateTabHeight(): void { this.parent.style.setProperty('--editor-group-tab-height', `${this.tabHeight}px`); } diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 4372fd674a4..d79a5be26c3 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -56,6 +56,8 @@ import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor import { StickyEditorGroupModel, UnstickyEditorGroupModel } from 'vs/workbench/common/editor/filteredEditorGroupModel'; import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IHoverService } from 'vs/platform/hover/browser/hover'; +import { IDecorationsService } from 'vs/workbench/services/decorations/common/decorations'; interface IEditorInputLabel { readonly editor: EditorInput; @@ -149,9 +151,11 @@ export class MultiEditorTabsControl extends EditorTabsControl { @IPathService private readonly pathService: IPathService, @ITreeViewsDnDService private readonly treeViewsDragAndDropService: ITreeViewsDnDService, @IEditorResolverService editorResolverService: IEditorResolverService, - @IHostService hostService: IHostService + @IHostService hostService: IHostService, + @IDecorationsService decorationsService: IDecorationsService, + @IHoverService hoverService: IHoverService, ) { - super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService, hostService); + super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService, hostService, decorationsService, hoverService); // Resolve the correct path library for the OS we are on // If we are connected to remote, this accounts for the @@ -793,7 +797,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { tabContainer.appendChild(tabBorderTopContainer); // Tab Editor Label - const editorLabel = this.tabResourceLabels.create(tabContainer); + const editorLabel = this.tabResourceLabels.create(tabContainer, { hoverDelegate: this.getHoverDelegate() }); // Tab Actions const tabActionsContainer = document.createElement('div'); @@ -1469,14 +1473,11 @@ export class MultiEditorTabsControl extends EditorTabsControl { tabContainer.setAttribute('aria-description', ''); } - const title = tabLabel.title || ''; - tabContainer.title = title; - // Label tabLabelWidget.setResource( { name, description, resource: EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.BOTH }) }, { - title, + title: this.getHoverTitle(editor), extraClasses: coalesce(['tab-label', fileDecorationBadges ? 'tab-label-has-badge' : undefined].concat(editor.getLabelExtraClasses())), italic: !this.tabsModel.isPinned(editor), forceLabel, diff --git a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts index 810dbc73083..c33305205c2 100644 --- a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts @@ -51,7 +51,7 @@ export class SingleEditorTabsControl extends EditorTabsControl { titleContainer.appendChild(labelContainer); // Editor Label - this.editorLabel = this._register(this.instantiationService.createInstance(ResourceLabel, labelContainer, undefined)).element; + this.editorLabel = this._register(this.instantiationService.createInstance(ResourceLabel, labelContainer, { hoverDelegate: this.getHoverDelegate() })).element; this._register(addDisposableListener(this.editorLabel.element, EventType.CLICK, e => this.onTitleLabelClick(e))); // Breadcrumbs @@ -304,11 +304,6 @@ export class SingleEditorTabsControl extends EditorTabsControl { description = editor.getDescription(this.getVerbosity(labelFormat)) || ''; } - let title = editor.getTitle(Verbosity.LONG); - if (description === title) { - title = ''; // dont repeat what is already shown - } - editorLabel.setResource( { resource: EditorResourceAccessor.getOriginalUri(editor, { supportSideBySide: SideBySideEditor.BOTH }), @@ -316,7 +311,7 @@ export class SingleEditorTabsControl extends EditorTabsControl { description }, { - title, + title: this.getHoverTitle(editor), italic: !isEditorPinned, extraClasses: ['single-tab', 'title-label'].concat(editor.getLabelExtraClasses()), fileDecorations: {