Skip to content

Commit

Permalink
feat: add dismiss button (#221508)
Browse files Browse the repository at this point in the history
  • Loading branch information
rzhao271 committed Jul 11, 2024
1 parent 269fb9d commit d778608
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,14 @@
}

.settings-editor > .settings-body .settings-tree-container .setting-item-extension-toggle .setting-item-extension-toggle-button {
display: block;
display: inline-block;
width: fit-content;
}

.settings-editor > .settings-body .settings-tree-container .setting-item-extension-toggle .setting-item-extension-dismiss-button {
display: inline-block;
width: fit-content;
margin-left: 8px;
}

.settings-editor.no-results > .settings-body .settings-toc-container,
Expand Down
120 changes: 71 additions & 49 deletions src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ export class SettingsEditor2 extends EditorPane {
private dimension!: DOM.Dimension;

private installedExtensionIds: string[] = [];
private dismissedExtensionSettings: string[] = [];

private readonly DISMISSED_EXTENSION_SETTINGS_STORAGE_KEY = 'settingsEditor2.dismissedExtensionSettings';

private readonly inputChangeListener: MutableDisposable<IDisposable>;

Expand Down Expand Up @@ -265,6 +268,8 @@ export class SettingsEditor2 extends EditorPane {

this.editorMemento = this.getEditorMemento<ISettingsEditor2State>(editorGroupService, textResourceConfigurationService, SETTINGS_EDITOR_STATE_KEY);

this.dismissedExtensionSettings = this.storageService.get(this.DISMISSED_EXTENSION_SETTINGS_STORAGE_KEY, StorageScope.PROFILE, '').split('\t');

this._register(configurationService.onDidChangeConfiguration(e => {
if (e.source !== ConfigurationTarget.DEFAULT) {
this.onConfigUpdate(e.affectedKeys);
Expand All @@ -286,6 +291,13 @@ export class SettingsEditor2 extends EditorPane {
}
}));

this._register(extensionManagementService.onDidInstallExtensions(() => {
this.refreshInstalledExtensionsList();
}));
this._register(extensionManagementService.onDidUninstallExtension(() => {
this.refreshInstalledExtensionsList();
}));

this.modelDisposables = this._register(new DisposableStore());

if (ENABLE_LANGUAGE_FILTER && !SettingsEditor2.SUGGESTIONS.includes(`@${LANGUAGE_SETTING_TAG}`)) {
Expand Down Expand Up @@ -399,7 +411,7 @@ export class SettingsEditor2 extends EditorPane {
private async refreshInstalledExtensionsList(): Promise<void> {
const installedExtensions = await this.extensionManagementService.getInstalled();
this.installedExtensionIds = installedExtensions
.filter(ext => ext.manifest && ext.manifest.contributes && ext.manifest.contributes.configuration)
.filter(ext => ext.manifest.contributes?.configuration)
.map(ext => ext.identifier.id);
}

Expand Down Expand Up @@ -679,6 +691,16 @@ export class SettingsEditor2 extends EditorPane {
this.onConfigUpdate(undefined, true);
}

private onDidDismissExtensionSetting(extensionId: string): void {
if (this.dismissedExtensionSettings.includes(extensionId)) {
return;
}

this.dismissedExtensionSettings.push(extensionId);
this.storageService.store(this.DISMISSED_EXTENSION_SETTINGS_STORAGE_KEY, this.dismissedExtensionSettings.join('\t'), StorageScope.PROFILE, StorageTarget.USER);
this.onConfigUpdate(undefined, true);
}

private onDidClickSetting(evt: ISettingLinkClickEvent, recursed?: boolean): void {
const targetElement = this.currentSettingsModel.getElementsByName(evt.targetKey)?.[0];
let revealFailed = false;
Expand Down Expand Up @@ -916,6 +938,7 @@ export class SettingsEditor2 extends EditorPane {
private createSettingsTree(container: HTMLElement): void {
this.settingRenderers = this._register(this.instantiationService.createInstance(SettingTreeRenderers));
this._register(this.settingRenderers.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value, e.type, e.manualReset, e.scope)));
this._register(this.settingRenderers.onDidDismissExtensionSetting((e) => this.onDidDismissExtensionSetting(e)));
this._register(this.settingRenderers.onDidOpenSettings(settingKey => {
this.openSettingsFile({ revealSetting: { key: settingKey, edit: true } });
}));
Expand Down Expand Up @@ -1210,43 +1233,6 @@ export class SettingsEditor2 extends EditorPane {
});
}

private addOrRemoveManageExtensionSetting(setting: ISetting, extension: IGalleryExtension, groups: ISettingsGroup[]): ISettingsGroup | undefined {
const matchingGroups = groups.filter(g => {
const lowerCaseId = g.extensionInfo?.id.toLowerCase();
return (lowerCaseId === setting.stableExtensionId!.toLowerCase() ||
lowerCaseId === setting.prereleaseExtensionId!.toLowerCase());
});

const extensionId = setting.displayExtensionId!;
const extensionInstalled = this.installedExtensionIds.includes(extensionId);
if (!matchingGroups.length && !extensionInstalled) {
// Only show the recommendation when the extension hasn't been installed.
const newGroup: ISettingsGroup = {
sections: [{
settings: [setting],
}],
id: extensionId,
title: setting.extensionGroupTitle!,
titleRange: nullRange,
range: nullRange,
extensionInfo: {
id: extensionId,
displayName: extension?.displayName,
}
};
groups.push(newGroup);
return newGroup;
} else if (matchingGroups.length >= 2 || extensionInstalled) {
// Remove the group with the manage extension setting.
const matchingGroupIndex = matchingGroups.findIndex(group =>
group.sections.length === 1 && group.sections[0].settings.length === 1 && group.sections[0].settings[0].displayExtensionId);
if (matchingGroupIndex !== -1) {
groups.splice(matchingGroupIndex, 1);
}
}
return undefined;
}

private createSettingsOrderByTocIndex(resolvedSettingsRoot: ITOCEntry<ISetting>): Map<string, number> {
const index = new Map<string, number>();
function indexSettings(resolvedSettingsRoot: ITOCEntry<ISetting>, counter = 0): number {
Expand Down Expand Up @@ -1301,11 +1287,37 @@ export class SettingsEditor2 extends EditorPane {
}

const additionalGroups: ISettingsGroup[] = [];
let setAdditionalGroups = false;
const toggleData = await getExperimentalExtensionToggleData(this.extensionGalleryService, this.productService);
if (toggleData && groups.filter(g => g.extensionInfo).length) {
for (const key in toggleData.settingsEditorRecommendedExtensions) {
const recommendationInfo = toggleData.settingsEditorRecommendedExtensions[key];
const extension = toggleData.recommendedExtensionsGalleryInfo[key];
const extension: IGalleryExtension = toggleData.recommendedExtensionsGalleryInfo[key];
if (!extension) {
continue;
}

const extensionId = extension.identifier.id;
const extensionInstalled = this.installedExtensionIds.includes(extensionId);

// Drill down to see whether the group and setting already exist
// and need to be removed.
const matchingGroupIndex = groups.findIndex(g =>
g.extensionInfo && g.extensionInfo!.id.toLowerCase() === extensionId.toLowerCase() &&
g.sections.length === 1 && g.sections[0].settings.length === 1 && g.sections[0].settings[0].displayExtensionId
);
if (extensionInstalled || this.dismissedExtensionSettings.includes(extensionId)) {
if (matchingGroupIndex !== -1) {
groups.splice(matchingGroupIndex, 1);
setAdditionalGroups = true;
}
continue;
}

if (matchingGroupIndex !== -1) {
continue;
}

// Create the entry. extensionInstalled is false in this case.
let manifest: IExtensionManifest | null = null;
try {
manifest = await this.extensionGalleryService.getManifest(extension, CancellationToken.None);
Expand All @@ -1323,7 +1335,8 @@ export class SettingsEditor2 extends EditorPane {
groupTitle = contributesConfiguration[0].title;
}

const extensionName = extension?.displayName ?? extension?.name ?? extension.identifier.id;
const recommendationInfo = toggleData.settingsEditorRecommendedExtensions[key];
const extensionName = extension.displayName ?? extension.name ?? extensionId;
const settingKey = `${key}.manageExtension`;
const setting: ISetting = {
range: nullRange,
Expand All @@ -1336,17 +1349,26 @@ export class SettingsEditor2 extends EditorPane {
descriptionRanges: [],
scope: ConfigurationScope.WINDOW,
type: 'null',
displayExtensionId: extension.identifier.id,
prereleaseExtensionId: key,
stableExtensionId: key,
displayExtensionId: extensionId,
extensionGroupTitle: groupTitle ?? extensionName,
categoryLabel: 'Extensions',
title: extensionName
};
const additionalGroup = this.addOrRemoveManageExtensionSetting(setting, extension, groups);
if (additionalGroup) {
additionalGroups.push(additionalGroup);
}
const additionalGroup: ISettingsGroup = {
sections: [{
settings: [setting],
}],
id: extensionId,
title: setting.extensionGroupTitle!,
titleRange: nullRange,
range: nullRange,
extensionInfo: {
id: extensionId,
displayName: extension.displayName,
}
};
additionalGroups.push(additionalGroup);
setAdditionalGroups = true;
}
}

Expand All @@ -1356,7 +1378,7 @@ export class SettingsEditor2 extends EditorPane {
const commonlyUsed = resolveSettingsTree(commonlyUsedDataToUse, groups, this.logService);
resolvedSettingsRoot.children!.unshift(commonlyUsed.tree);

if (toggleData) {
if (toggleData && setAdditionalGroups) {
// Add the additional groups to the model to help with searching.
this.defaultSettingsEditorModel.setAdditionalGroups(additionalGroups);
}
Expand Down
40 changes: 31 additions & 9 deletions src/vs/workbench/contrib/preferences/browser/settingsTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,7 @@ interface ISettingBoolItemTemplate extends ISettingItemTemplate<boolean> {

interface ISettingExtensionToggleItemTemplate extends ISettingItemTemplate<undefined> {
actionButton: Button;
dismissButton: Button;
}

interface ISettingTextItemTemplate extends ISettingItemTemplate<string> {
Expand Down Expand Up @@ -1055,7 +1056,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre
}
}

export class SettingGroupRenderer implements ITreeRenderer<SettingsTreeGroupElement, never, IGroupTitleTemplate> {
class SettingGroupRenderer implements ITreeRenderer<SettingsTreeGroupElement, never, IGroupTitleTemplate> {
templateId = SETTINGS_ELEMENT_TEMPLATE_ID;

renderTemplate(container: HTMLElement): IGroupTitleTemplate {
Expand Down Expand Up @@ -1622,15 +1623,15 @@ abstract class SettingIncludeExcludeRenderer extends AbstractSettingRenderer imp
}
}

export class SettingExcludeRenderer extends SettingIncludeExcludeRenderer {
class SettingExcludeRenderer extends SettingIncludeExcludeRenderer {
templateId = SETTINGS_EXCLUDE_TEMPLATE_ID;

protected override isExclude(): boolean {
return true;
}
}

export class SettingIncludeRenderer extends SettingIncludeExcludeRenderer {
class SettingIncludeRenderer extends SettingIncludeExcludeRenderer {
templateId = SETTINGS_INCLUDE_TEMPLATE_ID;

protected override isExclude(): boolean {
Expand Down Expand Up @@ -1745,7 +1746,7 @@ class SettingMultilineTextRenderer extends AbstractSettingTextRenderer implement
}
}

export class SettingEnumRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingEnumItemTemplate> {
class SettingEnumRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingEnumItemTemplate> {
templateId = SETTINGS_ENUM_TEMPLATE_ID;

renderTemplate(container: HTMLElement): ISettingEnumItemTemplate {
Expand Down Expand Up @@ -1862,7 +1863,7 @@ const settingsNumberInputBoxStyles = getInputBoxStyle({
inputBorder: settingsNumberInputBorder
});

export class SettingNumberRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingNumberItemTemplate> {
class SettingNumberRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingNumberItemTemplate> {
templateId = SETTINGS_NUMBER_TEMPLATE_ID;

renderTemplate(_container: HTMLElement): ISettingNumberItemTemplate {
Expand Down Expand Up @@ -1916,7 +1917,7 @@ export class SettingNumberRenderer extends AbstractSettingRenderer implements IT
}
}

export class SettingBoolRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingBoolItemTemplate> {
class SettingBoolRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingBoolItemTemplate> {
templateId = SETTINGS_BOOL_TEMPLATE_ID;

renderTemplate(_container: HTMLElement): ISettingBoolItemTemplate {
Expand Down Expand Up @@ -2011,9 +2012,12 @@ type ManageExtensionClickTelemetryClassification = {
comment: 'Event used to gain insights into when users interact with an extension management setting';
};

export class SettingsExtensionToggleRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingExtensionToggleItemTemplate> {
class SettingsExtensionToggleRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingExtensionToggleItemTemplate> {
templateId = SETTINGS_EXTENSION_TOGGLE_TEMPLATE_ID;

private readonly _onDidDismissExtensionSetting = this._register(new Emitter<string>());
readonly onDidDismissExtensionSetting = this._onDidDismissExtensionSetting.event;

renderTemplate(_container: HTMLElement): ISettingExtensionToggleItemTemplate {
const common = super.renderCommonTemplate(null, _container, 'extension-toggle');

Expand All @@ -2024,9 +2028,18 @@ export class SettingsExtensionToggleRenderer extends AbstractSettingRenderer imp
actionButton.element.classList.add('setting-item-extension-toggle-button');
actionButton.label = localize('showExtension', "Show Extension");

const dismissButton = new Button(common.containerElement, {
title: false,
secondary: true,
...defaultButtonStyles
});
dismissButton.element.classList.add('setting-item-extension-dismiss-button');
dismissButton.label = localize('dismiss', "Dismiss");

const template: ISettingExtensionToggleItemTemplate = {
...common,
actionButton
actionButton,
dismissButton
};

this.addSettingElementFocusHandler(template);
Expand All @@ -2046,6 +2059,11 @@ export class SettingsExtensionToggleRenderer extends AbstractSettingRenderer imp
this._telemetryService.publicLog2<{ extensionId: String }, ManageExtensionClickTelemetryClassification>('ManageExtensionClick', { extensionId });
this._commandService.executeCommand('extension.open', extensionId);
}));

template.elementDisposables.add(template.dismissButton.onDidClick(async () => {
this._telemetryService.publicLog2<{ extensionId: String }, ManageExtensionClickTelemetryClassification>('DismissExtensionClick', { extensionId });
this._onDidDismissExtensionSetting.fire(extensionId);
}));
}
}

Expand All @@ -2055,6 +2073,8 @@ export class SettingTreeRenderers extends Disposable {
private readonly _onDidChangeSetting = this._register(new Emitter<ISettingChangeEvent>());
readonly onDidChangeSetting: Event<ISettingChangeEvent>;

readonly onDidDismissExtensionSetting: Event<string>;

readonly onDidOpenSettings: Event<string>;

readonly onDidClickSettingLink: Event<ISettingLinkClickEvent>;
Expand Down Expand Up @@ -2098,6 +2118,7 @@ export class SettingTreeRenderers extends Disposable {

const actionFactory = (setting: ISetting, settingTarget: SettingsTarget) => this.getActionsForSetting(setting, settingTarget);
const emptyActionFactory = (_: ISetting) => [];
const extensionRenderer = this._instantiationService.createInstance(SettingsExtensionToggleRenderer, [], emptyActionFactory);
const settingRenderers = [
this._instantiationService.createInstance(SettingBoolRenderer, this.settingActions, actionFactory),
this._instantiationService.createInstance(SettingNumberRenderer, this.settingActions, actionFactory),
Expand All @@ -2110,14 +2131,15 @@ export class SettingTreeRenderers extends Disposable {
this._instantiationService.createInstance(SettingEnumRenderer, this.settingActions, actionFactory),
this._instantiationService.createInstance(SettingObjectRenderer, this.settingActions, actionFactory),
this._instantiationService.createInstance(SettingBoolObjectRenderer, this.settingActions, actionFactory),
this._instantiationService.createInstance(SettingsExtensionToggleRenderer, [], emptyActionFactory)
extensionRenderer
];

this.onDidClickOverrideElement = Event.any(...settingRenderers.map(r => r.onDidClickOverrideElement));
this.onDidChangeSetting = Event.any(
...settingRenderers.map(r => r.onDidChangeSetting),
this._onDidChangeSetting.event
);
this.onDidDismissExtensionSetting = extensionRenderer.onDidDismissExtensionSetting;
this.onDidOpenSettings = Event.any(...settingRenderers.map(r => r.onDidOpenSettings));
this.onDidClickSettingLink = Event.any(...settingRenderers.map(r => r.onDidClickSettingLink));
this.onDidFocusSetting = Event.any(...settingRenderers.map(r => r.onDidFocusSetting));
Expand Down
2 changes: 0 additions & 2 deletions src/vs/workbench/services/preferences/common/preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ export interface ISetting {
// Internal properties
allKeysAreBoolean?: boolean;
displayExtensionId?: string;
stableExtensionId?: string;
prereleaseExtensionId?: string;
title?: string;
extensionGroupTitle?: string;
internalOrder?: number;
Expand Down
Loading

0 comments on commit d778608

Please sign in to comment.