Add experimental settings type (v2) (#183263)

This commit is contained in:
Raymond Zhao 2023-05-25 14:35:14 -07:00 committed by GitHub
parent 51f1d2df15
commit 731d08f2bb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 295 additions and 33 deletions

View file

@ -191,6 +191,8 @@ export interface IProductConfiguration {
readonly 'editSessions.store'?: Omit<ConfigurationSyncStore, 'insidersUrl' | 'stableUrl'>;
readonly darwinUniversalAssetId?: string;
readonly profileTemplatesUrl?: string;
readonly commonlyUsedSettings?: string[];
}
export interface ITunnelApplicationConfig {
@ -201,6 +203,11 @@ export interface ITunnelApplicationConfig {
export interface IExtensionRecommendations {
readonly onFileOpen: IFileOpenCondition[];
readonly onSettingsEditorOpen?: ISettingsEditorOpenCondition;
}
export interface ISettingsEditorOpenCondition {
readonly prerelease: boolean | string;
}
export interface IExtensionRecommendationCondition {

View file

@ -160,6 +160,11 @@
color: var(--vscode-settings-headerForeground);
}
.settings-editor > .settings-body .settings-tree-container .setting-item-extension-toggle .setting-item-extension-toggle-button {
display: block;
width: fit-content;
}
.settings-editor.no-results > .settings-body .settings-toc-container,
.settings-editor.no-results > .settings-body .settings-tree-container {
display: none;
@ -481,6 +486,7 @@
.settings-editor > .settings-body .settings-tree-container .setting-item.setting-item-number input[type=number] {
/* Hide arrow button that shows in type=number fields */
-moz-appearance: textfield !important;
appearance: textfield !important;
}
.settings-editor > .settings-body .settings-tree-container .setting-item-contents .setting-item-markdown * {
@ -556,7 +562,7 @@
padding: 0px;
}
.settings-editor > .settings-body .settings-tree-container .setting-item-bool .setting-value-checkbox.codicon:not(.checked)::before {
.settings-editor > .settings-body .settings-tree-container .setting-item-bool .setting-value-checkbox.codicon:not(.checked)::before {
opacity: 0;
}

View file

@ -12,7 +12,6 @@ import { ITreeElement } from 'vs/base/browser/ui/tree/tree';
import { Action } from 'vs/base/common/actions';
import { Delayer, IntervalTimer, ThrottledDelayer, timeout } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import * as collections from 'vs/base/common/collections';
import { fromNow } from 'vs/base/common/date';
import { isCancellationError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
@ -39,16 +38,16 @@ import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane';
import { IEditorMemento, IEditorOpenContext, IEditorPane } from 'vs/workbench/common/editor';
import { SuggestEnabledInput } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput';
import { SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
import { commonlyUsedData, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout';
import { getCommonlyUsedData, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout';
import { AbstractSettingRenderer, HeightChangeParams, ISettingLinkClickEvent, resolveConfiguredUntrustedSettings, createTocTreeForExtensionSettings, resolveSettingsTree, SettingsTree, SettingTreeRenderers } from 'vs/workbench/contrib/preferences/browser/settingsTree';
import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels';
import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree';
import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, ENABLE_LANGUAGE_FILTER, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS, WORKSPACE_TRUST_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences';
import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, ENABLE_LANGUAGE_FILTER, EXTENSION_SETTING_TAG, FEATURE_SETTING_TAG, ID_SETTING_TAG, IPreferencesSearchService, ISearchProvider, LANGUAGE_SETTING_TAG, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS, WORKSPACE_TRUST_SETTING_TAG, getExperimentalExtensionToggleData } from 'vs/workbench/contrib/preferences/common/preferences';
import { settingsHeaderBorder, settingsSashBorder, settingsTextInputBorder } from 'vs/workbench/contrib/preferences/common/settingsEditorColorRegistry';
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IOpenSettingsOptions, IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEditorOptions, SettingMatchType, SettingValueType, validateSettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences';
import { IOpenSettingsOptions, IPreferencesService, ISearchResult, ISetting, ISettingsEditorModel, ISettingsEditorOptions, ISettingsGroup, SettingMatchType, SettingValueType, validateSettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences';
import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput';
import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
import { Settings2EditorModel, nullRange } from 'vs/workbench/services/preferences/common/preferencesModels';
import { IUserDataSyncWorkbenchService } from 'vs/workbench/services/userDataSync/common/userDataSync';
import { preferencesClearInputIcon, preferencesFilterIcon } from 'vs/workbench/contrib/preferences/browser/preferencesIcons';
import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust';
@ -59,11 +58,14 @@ import { Orientation, Sizing, SplitView } from 'vs/base/browser/ui/splitview/spl
import { Color } from 'vs/base/common/color';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { SettingsSearchFilterDropdownMenuActionViewItem } from 'vs/workbench/contrib/preferences/browser/settingsSearchMenu';
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionGalleryService, IExtensionManagementService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ISettingOverrideClickEvent } from 'vs/workbench/contrib/preferences/browser/settingsEditorSettingIndicators';
import { ConfigurationScope, Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles';
import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService';
import { IProductService } from 'vs/platform/product/common/productService';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
export const enum SettingsFocusContext {
Search,
@ -229,7 +231,11 @@ export class SettingsEditor2 extends EditorPane {
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
@IExtensionService private readonly extensionService: IExtensionService,
@ILanguageService private readonly languageService: ILanguageService,
@IExtensionManagementService extensionManagementService: IExtensionManagementService
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
@IWorkbenchAssignmentService private readonly workbenchAssignmentService: IWorkbenchAssignmentService,
@IProductService private readonly productService: IProductService,
@IEnvironmentService private readonly environmentService: IEnvironmentService,
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
) {
super(SettingsEditor2.ID, telemetryService, themeService, storageService);
this.delayedFilterLogging = new Delayer<void>(1000);
@ -1189,14 +1195,49 @@ export class SettingsEditor2 extends EditorPane {
});
}
private addOrRemoveManageExtensionSetting(setting: ISetting, extension: IGalleryExtension, groups: ISettingsGroup[]): ISettingsGroup | undefined {
const extensionId = setting.extensionId!;
const matchingGroups = groups.filter(g => g.extensionInfo?.id.toLowerCase() === extensionId.toLowerCase());
if (!matchingGroups.length) {
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) {
// 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].extensionId);
if (matchingGroupIndex !== -1) {
groups.splice(matchingGroupIndex, 1);
}
}
return undefined;
}
private async onConfigUpdate(keys?: ReadonlySet<string>, forceRefresh = false, schemaChange = false): Promise<void> {
if (keys && this.settingsTreeModel) {
return this.updateElementsByKey(keys);
}
if (!this.defaultSettingsEditorModel) {
return;
}
const groups = this.defaultSettingsEditorModel.settingsGroups.slice(1); // Without commonlyUsed
const dividedGroups = collections.groupBy(groups, g => g.extensionInfo ? 'extension' : 'core');
const settingsResult = resolveSettingsTree(tocData, dividedGroups.core, this.logService);
const coreSettings = groups.filter(g => !g.extensionInfo);
const settingsResult = resolveSettingsTree(tocData, coreSettings, this.logService);
const resolvedSettingsRoot = settingsResult.tree;
// Warn for settings not included in layout
@ -1210,10 +1251,61 @@ export class SettingsEditor2 extends EditorPane {
this.hasWarnedMissingSettings = true;
}
const commonlyUsed = resolveSettingsTree(commonlyUsedData, dividedGroups.core, this.logService);
const additionalGroups: ISettingsGroup[] = [];
const toggleData = await getExperimentalExtensionToggleData(this.workbenchAssignmentService, this.environmentService, this.productService);
if (toggleData && groups.filter(g => g.extensionInfo).length) {
for (const key in toggleData.settingsEditorRecommendedExtensions) {
const prerelease = toggleData.settingsEditorRecommendedExtensions[key].onSettingsEditorOpen!.prerelease;
const extensionId = (typeof prerelease === 'string' && this.productService.quality !== 'stable') ? prerelease : key;
const [extension] = await this.extensionGalleryService.getExtensions([{ id: extensionId }], CancellationToken.None);
if (!extension) {
continue;
}
let groupTitle: string | undefined;
const manifest = await this.extensionGalleryService.getManifest(extension, CancellationToken.None);
const contributesConfiguration = manifest?.contributes?.configuration;
if (!Array.isArray(contributesConfiguration)) {
groupTitle = contributesConfiguration?.title;
} else if (contributesConfiguration.length === 1) {
groupTitle = contributesConfiguration[0].title;
}
const extensionName = extension?.displayName ?? extension?.name ?? extensionId;
const settingKey = `${key}.manageExtension`;
const setting: ISetting = {
range: nullRange,
key: settingKey,
keyRange: nullRange,
value: null,
valueRange: nullRange,
description: [extension?.description || ''],
descriptionIsMarkdown: false,
descriptionRanges: [],
title: localize('manageExtension', "Manage {0}", extensionName),
scope: ConfigurationScope.WINDOW,
type: 'null',
extensionId: extensionId,
extensionGroupTitle: groupTitle ?? extensionName
};
const additionalGroup = this.addOrRemoveManageExtensionSetting(setting, extension, groups);
if (additionalGroup) {
additionalGroups.push(additionalGroup);
}
}
}
resolvedSettingsRoot.children!.push(await createTocTreeForExtensionSettings(this.extensionService, groups.filter(g => g.extensionInfo)));
const commonlyUsedDataToUse = await getCommonlyUsedData(this.workbenchAssignmentService, this.environmentService, this.productService);
const commonlyUsed = resolveSettingsTree(commonlyUsedDataToUse, groups, this.logService);
resolvedSettingsRoot.children!.unshift(commonlyUsed.tree);
resolvedSettingsRoot.children!.push(await createTocTreeForExtensionSettings(this.extensionService, dividedGroups.extension || []));
if (toggleData) {
// Add the additional groups to the model to help with searching.
this.defaultSettingsEditorModel.setAdditionalGroups(additionalGroups);
}
if (!this.workspaceTrustManagementService.isWorkspaceTrusted() && (this.viewState.settingsTarget instanceof URI || this.viewState.settingsTarget === ConfigurationTarget.WORKSPACE)) {
const configuredUntrustedWorkspaceSettings = resolveConfiguredUntrustedSettings(groups, this.viewState.settingsTarget, this.viewState.languageFilter, this.configurationService);

View file

@ -5,6 +5,10 @@
import { isWindows } from 'vs/base/common/platform';
import { localize } from 'vs/nls';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IProductService } from 'vs/platform/product/common/productService';
import { getExperimentalExtensionToggleData } from 'vs/workbench/contrib/preferences/common/preferences';
import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService';
export interface ITOCEntry<T> {
id: string;
label: string;
@ -13,11 +17,29 @@ export interface ITOCEntry<T> {
settings?: Array<T>;
}
export const commonlyUsedData: ITOCEntry<string> = {
id: 'commonlyUsed',
label: localize('commonlyUsed', "Commonly Used"),
settings: ['files.autoSave', 'editor.fontSize', 'editor.fontFamily', 'editor.tabSize', 'editor.renderWhitespace', 'editor.cursorStyle', 'editor.multiCursorModifier', 'editor.insertSpaces', 'editor.wordWrap', 'files.exclude', 'files.associations', 'workbench.editor.enablePreview']
};
const defaultCommonlyUsedSettings: string[] = [
'files.autoSave',
'editor.fontSize',
'editor.fontFamily',
'editor.tabSize',
'editor.renderWhitespace',
'editor.cursorStyle',
'editor.multiCursorModifier',
'editor.insertSpaces',
'editor.wordWrap',
'files.exclude',
'files.associations',
'workbench.editor.enablePreview'
];
export async function getCommonlyUsedData(workbenchAssignmentService: IWorkbenchAssignmentService, environmentService: IEnvironmentService, productService: IProductService): Promise<ITOCEntry<string>> {
const toggleData = await getExperimentalExtensionToggleData(workbenchAssignmentService, environmentService, productService);
return {
id: 'commonlyUsed',
label: localize('commonlyUsed', "Commonly Used"),
settings: toggleData ? toggleData.commonlyUsed : defaultCommonlyUsedSettings
};
}
export const tocData: ITOCEntry<string> = {
id: 'root',

View file

@ -64,6 +64,9 @@ import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/use
import { defaultButtonStyles, getInputBoxStyle, getListStyles, getSelectBoxStyles } from 'vs/platform/theme/browser/defaultStyles';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { RenderIndentGuides } from 'vs/base/browser/ui/tree/abstractTree';
import { IProductService } from 'vs/platform/product/common/productService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
const $ = DOM.$;
@ -502,7 +505,7 @@ function _resolveSettingsTree(tocData: ITOCEntry<string>, allSettings: Set<ISett
if (tocData.children) {
children = tocData.children
.map(child => _resolveSettingsTree(child, allSettings, logService))
.filter(child => (child.children && child.children.length) || (child.settings && child.settings.length));
.filter(child => child.children?.length || child.settings?.length);
}
let settings: ISetting[] | undefined;
@ -603,6 +606,10 @@ interface ISettingBoolItemTemplate extends ISettingItemTemplate<boolean> {
checkbox: Toggle;
}
interface ISettingExtensionToggleItemTemplate extends ISettingItemTemplate<undefined> {
actionButton: Button;
}
interface ISettingTextItemTemplate extends ISettingItemTemplate<string> {
inputBox: InputBox;
validationErrorMessageElement: HTMLElement;
@ -659,6 +666,7 @@ const SETTINGS_BOOL_OBJECT_TEMPLATE_ID = 'settings.boolObject.template';
const SETTINGS_COMPLEX_TEMPLATE_ID = 'settings.complex.template';
const SETTINGS_NEW_EXTENSIONS_TEMPLATE_ID = 'settings.newExtensions.template';
const SETTINGS_ELEMENT_TEMPLATE_ID = 'settings.group.template';
const SETTINGS_EXTENSION_TOGGLE_TEMPLATE_ID = 'settings.extensionToggle.template';
export interface ISettingChangeEvent {
key: string;
@ -758,6 +766,10 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre
@IContextMenuService protected readonly _contextMenuService: IContextMenuService,
@IKeybindingService protected readonly _keybindingService: IKeybindingService,
@IConfigurationService protected readonly _configService: IConfigurationService,
@IExtensionService protected readonly _extensionsService: IExtensionService,
@IExtensionsWorkbenchService protected readonly _extensionsWorkbenchService: IExtensionsWorkbenchService,
@IProductService protected readonly _productService: IProductService,
@ITelemetryService protected readonly _telemetryService: ITelemetryService,
) {
super();
@ -873,7 +885,7 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre
template.containerElement.setAttribute(AbstractSettingRenderer.SETTING_ID_ATTR, element.id);
const titleTooltip = setting.key + (element.isConfigured ? ' - Modified' : '');
template.categoryElement.textContent = element.displayCategory && (element.displayCategory + ': ');
template.categoryElement.textContent = element.displayCategory ? (element.displayCategory + ': ') : '';
template.categoryElement.title = titleTooltip;
template.labelElement.text = element.displayLabel;
@ -1878,6 +1890,50 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre
}
}
type ManageExtensionClickTelemetryClassification = {
extensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension the user went to manage.' };
owner: 'rzhao271';
comment: 'Event used to gain insights into when users are using an experimental extension management setting';
};
export class SettingsExtensionToggleRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingExtensionToggleItemTemplate> {
templateId = SETTINGS_EXTENSION_TOGGLE_TEMPLATE_ID;
renderTemplate(_container: HTMLElement): ISettingExtensionToggleItemTemplate {
const common = super.renderCommonTemplate(null, _container, 'extension-toggle');
const actionButton = new Button(common.containerElement, {
title: false,
...defaultButtonStyles
});
actionButton.element.classList.add('setting-item-extension-toggle-button');
actionButton.label = localize('manageExtension', "Manage extension");
const template: ISettingExtensionToggleItemTemplate = {
...common,
actionButton
};
this.addSettingElementFocusHandler(template);
return template;
}
renderElement(element: ITreeNode<SettingsTreeSettingElement, never>, index: number, templateData: ISettingExtensionToggleItemTemplate): void {
super.renderSettingElement(element, index, templateData);
}
protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingExtensionToggleItemTemplate, onChange: (_: undefined) => void): void {
template.elementDisposables.clear();
const extensionId = dataElement.setting.extensionId!;
template.elementDisposables.add(template.actionButton.onDidClick(async () => {
this._telemetryService.publicLog2<{ extensionId: String }, ManageExtensionClickTelemetryClassification>('ManageExtensionClick', { extensionId });
this._commandService.executeCommand('extension.open', extensionId);
}));
}
}
export class SettingTreeRenderers {
readonly onDidClickOverrideElement: Event<ISettingOverrideClickEvent>;
@ -1924,6 +1980,7 @@ export class SettingTreeRenderers {
];
const actionFactory = (setting: ISetting) => this.getActionsForSetting(setting);
const emptyActionFactory = (_: ISetting) => [];
const settingRenderers = [
this._instantiationService.createInstance(SettingBoolRenderer, this.settingActions, actionFactory),
this._instantiationService.createInstance(SettingNumberRenderer, this.settingActions, actionFactory),
@ -1936,6 +1993,7 @@ export class SettingTreeRenderers {
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)
];
this.onDidClickOverrideElement = Event.any(...settingRenderers.map(r => r.onDidClickOverrideElement));
@ -2146,6 +2204,10 @@ class SettingsTreeDelegate extends CachedListVirtualDelegate<SettingsTreeGroupCh
}
if (element instanceof SettingsTreeSettingElement) {
if (element.valueType === SettingValueType.ExtensionToggle) {
return SETTINGS_EXTENSION_TOGGLE_TEMPLATE_ID;
}
const invalidTypeError = element.isConfigured && getInvalidTypeError(element.value, element.setting.type);
if (invalidTypeError) {
return SETTINGS_COMPLEX_TEMPLATE_ID;

View file

@ -10,7 +10,7 @@ import { URI } from 'vs/base/common/uri';
import { ConfigurationTarget, IConfigurationValue } from 'vs/platform/configuration/common/configuration';
import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
import { ITOCEntry, knownAcronyms, knownTermMappings, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout';
import { ENABLE_LANGUAGE_FILTER, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences';
import { ENABLE_EXTENSION_TOGGLE_SETTINGS, ENABLE_LANGUAGE_FILTER, MODIFIED_SETTING_TAG, POLICY_SETTING_TAG, REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences';
import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_SCOPES, IWorkbenchConfigurationService, APPLICATION_SCOPES } from 'vs/workbench/services/configuration/common/configuration';
@ -21,6 +21,7 @@ import { ConfigurationScope, EditPresentationTypes, Extensions, IConfigurationRe
import { ILanguageService } from 'vs/editor/common/languages/language';
import { Registry } from 'vs/platform/registry/common/platform';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { IProductService } from 'vs/platform/product/common/productService';
export const ONLINE_SERVICES_SETTING_TAG = 'usesOnlineServices';
@ -168,7 +169,8 @@ export class SettingsTreeSettingElement extends SettingsTreeElement {
parent: SettingsTreeGroupElement,
inspectResult: IInspectResult,
isWorkspaceTrusted: boolean,
private readonly languageService: ILanguageService
private readonly languageService: ILanguageService,
private readonly productService: IProductService
) {
super(sanitizeId(parent.id + '_' + setting.key));
this.setting = setting;
@ -194,6 +196,11 @@ export class SettingsTreeSettingElement extends SettingsTreeElement {
}
private initLabels(): void {
if (this.setting.title) {
this._displayLabel = this.setting.title;
this._displayCategory = '';
return;
}
const displayKeyFormat = settingKeyToDisplayFormat(this.setting.key, this.parent!.id, this.setting.isLanguageTagSetting);
this._displayLabel = displayKeyFormat.label;
this._displayCategory = displayKeyFormat.category;
@ -303,7 +310,9 @@ export class SettingsTreeSettingElement extends SettingsTreeElement {
this.description = this.setting.description.join('\n');
}
if (this.setting.enum && (!this.setting.type || settingTypeEnumRenderable(this.setting.type))) {
if (isExtensionToggleSetting(this.setting, this.productService)) {
this.valueType = SettingValueType.ExtensionToggle;
} else if (this.setting.enum && (!this.setting.type || settingTypeEnumRenderable(this.setting.type))) {
this.valueType = SettingValueType.Enum;
} else if (this.setting.type === 'string') {
if (this.setting.editPresentation === EditPresentationTypes.Multiline) {
@ -476,6 +485,7 @@ export class SettingsTreeModel {
@IWorkbenchConfigurationService private readonly _configurationService: IWorkbenchConfigurationService,
@ILanguageService private readonly _languageService: ILanguageService,
@IUserDataProfileService private readonly _userDataProfileService: IUserDataProfileService,
@IProductService private readonly _productService: IProductService
) {
}
@ -583,7 +593,7 @@ export class SettingsTreeModel {
private createSettingsTreeSettingElement(setting: ISetting, parent: SettingsTreeGroupElement): SettingsTreeSettingElement {
const target = this.getTargetToInspect(setting.scope);
const inspectResult = inspectSetting(setting.key, target, this._viewState.languageFilter, this._configurationService);
const element = new SettingsTreeSettingElement(setting, parent, inspectResult, this._isWorkspaceTrusted, this._languageService);
const element = new SettingsTreeSettingElement(setting, parent, inspectResult, this._isWorkspaceTrusted, this._languageService, this._productService);
const nameElements = this._treeElementsBySettingName.get(setting.key) || [];
nameElements.push(element);
@ -737,7 +747,13 @@ function trimCategoryForGroup(category: string, groupId: string): string {
return trimmed;
}
export function isExcludeSetting(setting: ISetting): boolean {
function isExtensionToggleSetting(setting: ISetting, productService: IProductService): boolean {
return ENABLE_EXTENSION_TOGGLE_SETTINGS &&
!!productService.extensionRecommendations &&
!!setting.extensionId;
}
function isExcludeSetting(setting: ISetting): boolean {
return setting.key === 'files.exclude' ||
setting.key === 'search.exclude' ||
setting.key === 'workbench.localHistory.exclude' ||
@ -746,7 +762,7 @@ export function isExcludeSetting(setting: ISetting): boolean {
setting.key === 'files.watcherExclude';
}
export function isIncludeSetting(setting: ISetting): boolean {
function isIncludeSetting(setting: ISetting): boolean {
return setting.key === 'files.readonlyInclude';
}
@ -826,8 +842,9 @@ export class SearchResultModel extends SettingsTreeModel {
@IWorkbenchEnvironmentService private environmentService: IWorkbenchEnvironmentService,
@ILanguageService languageService: ILanguageService,
@IUserDataProfileService userDataProfileService: IUserDataProfileService,
@IProductService productService: IProductService
) {
super(viewState, isWorkspaceTrusted, configurationService, languageService, userDataProfileService);
super(viewState, isWorkspaceTrusted, configurationService, languageService, userDataProfileService, productService);
this.update({ id: 'searchResultModel', label: '' });
}

View file

@ -3,10 +3,15 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ISettingsEditorModel, ISearchResult } from 'vs/workbench/services/preferences/common/preferences';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IStringDictionary } from 'vs/base/common/collections';
import { IExtensionRecommendations } from 'vs/base/common/product';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IProductService } from 'vs/platform/product/common/productService';
import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService';
import { ISearchResult, ISettingsEditorModel } from 'vs/workbench/services/preferences/common/preferences';
export interface IWorkbenchSettingsConfiguration {
workbench: {
@ -88,3 +93,39 @@ export const REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG = 'requireTrustedWorkspace';
export const KEYBOARD_LAYOUT_OPEN_PICKER = 'workbench.action.openKeyboardLayoutPicker';
export const ENABLE_LANGUAGE_FILTER = true;
export const ENABLE_EXTENSION_TOGGLE_SETTINGS = true;
type ExtensionToggleData = {
settingsEditorRecommendedExtensions: IStringDictionary<IExtensionRecommendations>;
commonlyUsed: string[];
};
let cachedExtensionToggleData: ExtensionToggleData | undefined;
export async function getExperimentalExtensionToggleData(workbenchAssignmentService: IWorkbenchAssignmentService, environmentService: IEnvironmentService, productService: IProductService): Promise<ExtensionToggleData | undefined> {
if (!ENABLE_EXTENSION_TOGGLE_SETTINGS) {
return undefined;
}
if (cachedExtensionToggleData) {
return cachedExtensionToggleData;
}
const isTreatment = await workbenchAssignmentService.getTreatment<boolean>('ExtensionToggleSettings');
if ((isTreatment || !environmentService.isBuilt) && productService.extensionRecommendations && productService.commonlyUsedSettings) {
const settingsEditorRecommendedExtensions: Record<string, IExtensionRecommendations> = {};
Object.keys(productService.extensionRecommendations).forEach(key => {
const value = productService.extensionRecommendations![key];
if (value.onSettingsEditorOpen) {
settingsEditorRecommendedExtensions[key] = value;
}
});
cachedExtensionToggleData = {
settingsEditorRecommendedExtensions,
commonlyUsed: productService.commonlyUsedSettings
};
return cachedExtensionToggleData;
}
return undefined;
}

View file

@ -38,7 +38,8 @@ export enum SettingValueType {
NullableNumber = 'nullable-number',
Object = 'object',
BooleanObject = 'boolean-object',
LanguageTag = 'language-tag'
LanguageTag = 'language-tag',
ExtensionToggle = 'extension-toggle'
}
export interface ISettingsGroup {
@ -94,6 +95,11 @@ export interface ISetting {
isLanguageTagSetting?: boolean;
categoryOrder?: number;
categoryLabel?: string;
// For ExtensionToggle settings
extensionId?: string;
title?: string;
extensionGroupTitle?: string;
}
export interface IExtensionSetting extends ISetting {

View file

@ -16,7 +16,7 @@ import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { ITextEditorModel } from 'vs/editor/common/services/resolverService';
import * as nls from 'vs/nls';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry, IExtensionInfo, IRegisteredConfigurationPropertySchema, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry';
import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationPropertySchema, IConfigurationRegistry, IExtensionInfo, IRegisteredConfigurationPropertySchema, OVERRIDE_PROPERTY_REGEX } from 'vs/platform/configuration/common/configurationRegistry';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { Registry } from 'vs/platform/registry/common/platform';
import { EditorModel } from 'vs/workbench/common/editor/editorModel';
@ -256,6 +256,7 @@ export class Settings2EditorModel extends AbstractSettingsModel implements ISett
private readonly _onDidChangeGroups: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChangeGroups: Event<void> = this._onDidChangeGroups.event;
private additionalGroups: ISettingsGroup[] | undefined;
private dirty = false;
constructor(
@ -276,17 +277,25 @@ export class Settings2EditorModel extends AbstractSettingsModel implements ISett
}));
}
/** Doesn't include the "Commonly Used" group */
protected override get filterGroups(): ISettingsGroup[] {
// Don't filter "commonly used"
return this.settingsGroups.slice(1);
}
get settingsGroups(): ISettingsGroup[] {
const groups = this._defaultSettings.getSettingsGroups(this.dirty);
if (this.additionalGroups?.length) {
groups.push(...this.additionalGroups);
}
this.dirty = false;
return groups;
}
/** For programmatically added groups outside of registered configurations */
setAdditionalGroups(groups: ISettingsGroup[]) {
this.additionalGroups = groups;
}
findValueMatches(filter: string, setting: ISetting): IRange[] {
// TODO @roblou
return [];
@ -667,7 +676,7 @@ export class DefaultSettings extends Disposable {
const categoryOrder = config.order;
for (const key in settingsObject) {
const prop = settingsObject[key];
const prop: IConfigurationPropertySchema = settingsObject[key];
if (this.matchesScope(prop)) {
const value = prop.default;
let description = (prop.markdownDescription || prop.description || '');