Change modified in to use custom hover (#152401)

Ref #151787
This commit is contained in:
Raymond Zhao
2022-06-20 17:09:19 -07:00
committed by GitHub
parent 3438b31b9b
commit 10e6e87682
4 changed files with 81 additions and 31 deletions

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
import { IUpdatableHoverOptions } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { IDisposable } from 'vs/base/common/lifecycle';
@@ -12,7 +13,7 @@ export interface IHoverDelegateTarget extends IDisposable {
x?: number;
}
export interface IHoverDelegateOptions {
export interface IHoverDelegateOptions extends IUpdatableHoverOptions {
content: IMarkdownString | string | HTMLElement;
target: IHoverDelegateTarget | HTMLElement;
hoverPosition?: HoverPosition;

View File

@@ -33,6 +33,21 @@ export function setupNativeHover(htmlElement: HTMLElement, tooltip: string | ITo
export type IHoverContent = string | ITooltipMarkdownString | HTMLElement | undefined;
type IResolvedHoverContent = IMarkdownString | string | HTMLElement | undefined;
/**
* Copied from src\vs\workbench\services\hover\browser\hover.ts
* @deprecated Use IHoverService
*/
export interface IHoverAction {
label: string;
commandId: string;
iconClass?: string;
run(target: HTMLElement): void;
}
export interface IUpdatableHoverOptions {
actions?: IHoverAction[];
linkHandler?(url: string): void;
}
export interface ICustomHover extends IDisposable {
@@ -49,7 +64,7 @@ export interface ICustomHover extends IDisposable {
/**
* Updates the contents of the hover.
*/
update(tooltip: IHoverContent): void;
update(tooltip: IHoverContent, options?: IUpdatableHoverOptions): void;
}
@@ -61,7 +76,7 @@ class UpdatableHoverWidget implements IDisposable {
constructor(private hoverDelegate: IHoverDelegate, private target: IHoverDelegateTarget | HTMLElement, private fadeInAnimation: boolean) {
}
async update(content: IHoverContent, focus?: boolean): Promise<void> {
async update(content: IHoverContent, focus?: boolean, options?: IUpdatableHoverOptions): Promise<void> {
if (this._cancellationTokenSource) {
// there's an computation ongoing, cancel it
this._cancellationTokenSource.dispose(true);
@@ -99,10 +114,10 @@ class UpdatableHoverWidget implements IDisposable {
}
}
this.show(resolvedContent, focus);
this.show(resolvedContent, focus, options);
}
private show(content: IResolvedHoverContent, focus?: boolean): void {
private show(content: IResolvedHoverContent, focus?: boolean, options?: IUpdatableHoverOptions): void {
const oldHoverWidget = this._hoverWidget;
if (this.hasContent(content)) {
@@ -111,7 +126,8 @@ class UpdatableHoverWidget implements IDisposable {
target: this.target,
showPointer: this.hoverDelegate.placement === 'element',
hoverPosition: HoverPosition.BELOW,
skipFadeInAnimation: !this.fadeInAnimation || !!oldHoverWidget // do not fade in if the hover is already showing
skipFadeInAnimation: !this.fadeInAnimation || !!oldHoverWidget, // do not fade in if the hover is already showing
...options
};
this._hoverWidget = this.hoverDelegate.showHover(hoverOptions, focus);
@@ -142,7 +158,7 @@ class UpdatableHoverWidget implements IDisposable {
}
}
export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IHoverContent): ICustomHover {
export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTMLElement, content: IHoverContent, options?: IUpdatableHoverOptions): ICustomHover {
let hoverPreparation: IDisposable | undefined;
let hoverWidget: UpdatableHoverWidget | undefined;
@@ -163,7 +179,7 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM
return new TimeoutTimer(async () => {
if (!hoverWidget || hoverWidget.isDisposed) {
hoverWidget = new UpdatableHoverWidget(hoverDelegate, target || htmlElement, delay > 0);
await hoverWidget.update(content, focus);
await hoverWidget.update(content, focus, options);
}
}, delay);
};
@@ -208,9 +224,9 @@ export function setupCustomHover(hoverDelegate: IHoverDelegate, htmlElement: HTM
hide: () => {
hideHover(true, true);
},
update: async newContent => {
update: async (newContent, hoverOptions) => {
content = newContent;
await hoverWidget?.update(content);
await hoverWidget?.update(content, undefined, hoverOptions);
},
dispose: () => {
mouseOverDomEmitter.dispose();

View File

@@ -351,8 +351,7 @@
font-style: italic;
}
.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title .setting-item-ignored .codicon,
.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title .setting-item-default-overridden .codicon {
.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-title .codicon {
vertical-align: middle;
padding-left: 1px;
}

View File

@@ -6,7 +6,7 @@
import * as DOM from 'vs/base/browser/dom';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IHoverDelegate, IHoverDelegateOptions } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
import { setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
import { ITooltipMarkdownString, IUpdatableHoverOptions, setupCustomHover } from 'vs/base/browser/ui/iconLabel/iconLabelHover';
import { SimpleIconLabel } from 'vs/base/browser/ui/iconLabel/simpleIconLabel';
import { Emitter } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
@@ -30,6 +30,7 @@ export interface ISettingOverrideClickEvent {
export class SettingsTreeIndicatorsLabel {
private indicatorsContainerElement: HTMLElement;
private scopeOverridesElement: HTMLElement;
private scopeOverridesLabel: SimpleIconLabel;
private syncIgnoredElement: HTMLElement;
private defaultOverrideIndicatorElement: HTMLElement;
private hoverDelegate: IHoverDelegate;
@@ -42,7 +43,9 @@ export class SettingsTreeIndicatorsLabel {
this.indicatorsContainerElement = DOM.append(container, $('.misc-label'));
this.indicatorsContainerElement.style.display = 'inline';
this.scopeOverridesElement = this.createScopeOverridesElement();
const scopeOverridesIndicator = this.createScopeOverridesIndicator();
this.scopeOverridesElement = scopeOverridesIndicator.element;
this.scopeOverridesLabel = scopeOverridesIndicator.label;
this.syncIgnoredElement = this.createSyncIgnoredElement();
this.defaultOverrideIndicatorElement = this.createDefaultOverrideIndicator();
@@ -55,16 +58,17 @@ export class SettingsTreeIndicatorsLabel {
};
}
private createScopeOverridesElement(): HTMLElement {
private createScopeOverridesIndicator(): { element: HTMLElement; label: SimpleIconLabel } {
const otherOverridesElement = $('span.setting-item-overrides');
return otherOverridesElement;
const otherOverridesLabel = new SimpleIconLabel(otherOverridesElement);
return { element: otherOverridesElement, label: otherOverridesLabel };
}
private createSyncIgnoredElement(): HTMLElement {
const syncIgnoredElement = $('span.setting-item-ignored');
const syncIgnoredLabel = new SimpleIconLabel(syncIgnoredElement);
syncIgnoredLabel.text = '$(info) ' + localize('extensionSyncIgnoredLabel', 'Not synced');
const syncIgnoredHoverContent = localize('syncIgnoredTitle', "Settings sync does not sync this setting");
const syncIgnoredHoverContent = localize('syncIgnoredTitle', "This setting is ignored during sync");
setupCustomHover(this.hoverDelegate, syncIgnoredElement, syncIgnoredHoverContent);
return syncIgnoredElement;
}
@@ -106,28 +110,58 @@ export class SettingsTreeIndicatorsLabel {
this.scopeOverridesElement.style.display = 'none';
if (element.overriddenScopeList.length) {
this.scopeOverridesElement.style.display = 'inline';
const otherOverridesLabel = element.isConfigured ?
localize('alsoConfiguredIn', "Also modified in") :
localize('configuredIn', "Modified in");
DOM.append(this.scopeOverridesElement, $('span', undefined, `${otherOverridesLabel}: `));
for (let i = 0; i < element.overriddenScopeList.length; i++) {
const view = DOM.append(this.scopeOverridesElement, $('a.modified-scope', undefined, element.overriddenScopeList[i]));
if (i !== element.overriddenScopeList.length - 1) {
DOM.append(this.scopeOverridesElement, $('span', undefined, ', '));
}
if (element.overriddenScopeList.length === 1) {
// Just show all the text in the label.
const prefaceText = element.isConfigured ?
localize('alsoConfiguredIn', "Also modified in") :
localize('configuredIn', "Modified in");
this.scopeOverridesLabel.text = `${prefaceText}: `;
const firstScope = element.overriddenScopeList[0];
const view = DOM.append(this.scopeOverridesElement, $('a.modified-scope', undefined, firstScope));
elementDisposables.add(
DOM.addStandardDisposableListener(view, DOM.EventType.CLICK, (e: IMouseEvent) => {
onDidClickOverrideElement.fire({
targetKey: element.setting.key,
scope: element.overriddenScopeList[i]
scope: firstScope
});
e.preventDefault();
e.stopPropagation();
}));
} else {
// Show most of the text in a custom hover.
let scopeOverridesLabelText = '$(info) ';
scopeOverridesLabelText += element.isConfigured ?
localize('alsoConfiguredElsewhere', "Also modified elsewhere") :
localize('configuredElsewhere', "Modified elsewhere");
this.scopeOverridesLabel.text = scopeOverridesLabelText;
const prefaceText = element.isConfigured ?
localize('alsoModifiedInScopes', "The setting has also been modified in the following scopes:") :
localize('modifiedInScopes', "The setting has been modified in the following scopes:");
let contentMarkdownString = prefaceText;
let contentFallback = prefaceText;
for (const scope of element.overriddenScopeList) {
contentMarkdownString += `\n- [${scope}](${scope})`;
contentFallback += `\n• ${scope}`;
}
const content: ITooltipMarkdownString = {
markdown: {
value: contentMarkdownString,
isTrusted: false,
supportHtml: false
},
markdownNotSupportedFallback: contentFallback
};
const options: IUpdatableHoverOptions = {
linkHandler: (scope: string) => {
onDidClickOverrideElement.fire({
targetKey: element.setting.key,
scope
});
}
};
setupCustomHover(this.hoverDelegate, this.scopeOverridesElement, content, options);
}
}
this.render();
@@ -173,7 +207,7 @@ export function getIndicatorsLabelAriaLabel(element: SettingsTreeSettingElement,
// Add sync ignored text
const ignoredSettings = getIgnoredSettings(getDefaultIgnoredSettings(), configurationService);
if (ignoredSettings.includes(element.setting.key)) {
ariaLabelSections.push(localize('syncIgnoredTitle', "Settings sync does not sync this setting"));
ariaLabelSections.push(localize('syncIgnoredTitle', "This setting is ignored during sync"));
}
// Add default override indicator text