Path: blob/main/src/vs/workbench/contrib/preferences/common/preferences.ts
5284 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { raceTimeout } from '../../../../base/common/async.js';6import { CancellationToken } from '../../../../base/common/cancellation.js';7import { IStringDictionary } from '../../../../base/common/collections.js';8import { IExtensionRecommendations } from '../../../../base/common/product.js';9import { localize } from '../../../../nls.js';10import { RawContextKey } from '../../../../platform/contextkey/common/contextkey.js';11import { IExtensionGalleryService, IGalleryExtension } from '../../../../platform/extensionManagement/common/extensionManagement.js';12import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js';13import { IProductService } from '../../../../platform/product/common/productService.js';14import { IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js';15import { ISearchResult, ISettingsEditorModel } from '../../../services/preferences/common/preferences.js';1617export interface IWorkbenchSettingsConfiguration {18workbench: {19settings: {20openDefaultSettings: boolean;21naturalLanguageSearchEndpoint: string;22naturalLanguageSearchKey: string;23naturalLanguageSearchAutoIngestFeedback: boolean;24useNaturalLanguageSearchPost: boolean;25enableNaturalLanguageSearch: boolean;26enableNaturalLanguageSearchFeedback: boolean;27};28};29}3031export interface IEndpointDetails {32urlBase: string;33key?: string;34}3536export const IPreferencesSearchService = createDecorator<IPreferencesSearchService>('preferencesSearchService');3738export interface IPreferencesSearchService {39readonly _serviceBrand: undefined;4041getLocalSearchProvider(filter: string): ISearchProvider;42getRemoteSearchProvider(filter: string, newExtensionsOnly?: boolean): ISearchProvider | undefined;43getAiSearchProvider(filter: string): IAiSearchProvider | undefined;44}4546export interface ISearchProvider {47searchModel(preferencesModel: ISettingsEditorModel, token: CancellationToken): Promise<ISearchResult | null>;48}4950export interface IRemoteSearchProvider extends ISearchProvider {51setFilter(filter: string): void;52}5354export interface IAiSearchProvider extends IRemoteSearchProvider {55getLLMRankedResults(token: CancellationToken): Promise<ISearchResult | null>;56}5758export const PREFERENCES_EDITOR_COMMAND_OPEN = 'workbench.preferences.action.openPreferencesEditor';59export const CONTEXT_PREFERENCES_SEARCH_FOCUS = new RawContextKey<boolean>('inPreferencesSearch', false);6061export const SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS = 'settings.action.clearSearchResults';62export const SETTINGS_EDITOR_COMMAND_SHOW_AI_RESULTS = 'settings.action.showAIResults';63export const SETTINGS_EDITOR_COMMAND_TOGGLE_AI_SEARCH = 'settings.action.toggleAiSearch';64export const SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU = 'settings.action.showContextMenu';65export const SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS = 'settings.action.suggestFilters';6667export const CONTEXT_SETTINGS_EDITOR = new RawContextKey<boolean>('inSettingsEditor', false);68export const CONTEXT_SETTINGS_JSON_EDITOR = new RawContextKey<boolean>('inSettingsJSONEditor', false);69export const CONTEXT_SETTINGS_SEARCH_FOCUS = new RawContextKey<boolean>('inSettingsSearch', false);70export const CONTEXT_TOC_ROW_FOCUS = new RawContextKey<boolean>('settingsTocRowFocus', false);71export const CONTEXT_SETTINGS_ROW_FOCUS = new RawContextKey<boolean>('settingRowFocus', false);72export const CONTEXT_KEYBINDINGS_EDITOR = new RawContextKey<boolean>('inKeybindings', false);73export const CONTEXT_KEYBINDINGS_SEARCH_FOCUS = new RawContextKey<boolean>('inKeybindingsSearch', false);74export const CONTEXT_KEYBINDINGS_SEARCH_HAS_VALUE = new RawContextKey<boolean>('keybindingsSearchHasValue', false);75export const CONTEXT_KEYBINDING_FOCUS = new RawContextKey<boolean>('keybindingFocus', false);76export const CONTEXT_WHEN_FOCUS = new RawContextKey<boolean>('whenFocus', false);77export const CONTEXT_AI_SETTING_RESULTS_AVAILABLE = new RawContextKey<boolean>('aiSettingResultsAvailable', false);7879export const KEYBINDINGS_EDITOR_COMMAND_SEARCH = 'keybindings.editor.searchKeybindings';80export const KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS = 'keybindings.editor.clearSearchResults';81export const KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_HISTORY = 'keybindings.editor.clearSearchHistory';82export const KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS = 'keybindings.editor.recordSearchKeys';83export const KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE = 'keybindings.editor.toggleSortByPrecedence';84export const KEYBINDINGS_EDITOR_COMMAND_DEFINE = 'keybindings.editor.defineKeybinding';85export const KEYBINDINGS_EDITOR_COMMAND_ADD = 'keybindings.editor.addKeybinding';86export const KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN = 'keybindings.editor.defineWhenExpression';87export const KEYBINDINGS_EDITOR_COMMAND_ACCEPT_WHEN = 'keybindings.editor.acceptWhenExpression';88export const KEYBINDINGS_EDITOR_COMMAND_REJECT_WHEN = 'keybindings.editor.rejectWhenExpression';89export const KEYBINDINGS_EDITOR_COMMAND_REMOVE = 'keybindings.editor.removeKeybinding';90export const KEYBINDINGS_EDITOR_COMMAND_RESET = 'keybindings.editor.resetKeybinding';91export const KEYBINDINGS_EDITOR_COMMAND_COPY = 'keybindings.editor.copyKeybindingEntry';92export const KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND = 'keybindings.editor.copyCommandKeybindingEntry';93export const KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND_TITLE = 'keybindings.editor.copyCommandTitle';94export const KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR = 'keybindings.editor.showConflicts';95export const KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS = 'keybindings.editor.focusKeybindings';96export const KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS = 'keybindings.editor.showDefaultKeybindings';97export const KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS = 'keybindings.editor.showUserKeybindings';98export const KEYBINDINGS_EDITOR_SHOW_EXTENSION_KEYBINDINGS = 'keybindings.editor.showExtensionKeybindings';99100export const MODIFIED_SETTING_TAG = 'modified';101export const EXTENSION_SETTING_TAG = 'ext:';102export const FEATURE_SETTING_TAG = 'feature:';103export const ID_SETTING_TAG = 'id:';104export const LANGUAGE_SETTING_TAG = 'lang:';105export const GENERAL_TAG_SETTING_TAG = 'tag:';106export const POLICY_SETTING_TAG = 'hasPolicy';107export const WORKSPACE_TRUST_SETTING_TAG = 'workspaceTrust';108export const REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG = 'requireTrustedWorkspace';109export const ADVANCED_SETTING_TAG = 'advanced';110export const KEYBOARD_LAYOUT_OPEN_PICKER = 'workbench.action.openKeyboardLayoutPicker';111112export const ENABLE_LANGUAGE_FILTER = true;113114export const ENABLE_EXTENSION_TOGGLE_SETTINGS = true;115export const EXTENSION_FETCH_TIMEOUT_MS = 1000;116117export const STRING_MATCH_SEARCH_PROVIDER_NAME = 'local';118export const TF_IDF_SEARCH_PROVIDER_NAME = 'tfIdf';119export const FILTER_MODEL_SEARCH_PROVIDER_NAME = 'filterModel';120export const EMBEDDINGS_SEARCH_PROVIDER_NAME = 'embeddingsFull';121export const LLM_RANKED_SEARCH_PROVIDER_NAME = 'llmRanked';122123export enum WorkbenchSettingsEditorSettings {124ShowAISearchToggle = 'workbench.settings.showAISearchToggle',125EnableNaturalLanguageSearch = 'workbench.settings.enableNaturalLanguageSearch',126}127128export type ExtensionToggleData = {129settingsEditorRecommendedExtensions: IStringDictionary<IExtensionRecommendations>;130recommendedExtensionsGalleryInfo: IStringDictionary<IGalleryExtension>;131};132133let cachedExtensionToggleData: ExtensionToggleData | undefined;134135export async function getExperimentalExtensionToggleData(136chatEntitlementService: IChatEntitlementService,137extensionGalleryService: IExtensionGalleryService,138productService: IProductService,139): Promise<ExtensionToggleData | undefined> {140if (!ENABLE_EXTENSION_TOGGLE_SETTINGS) {141return undefined;142}143144if (!extensionGalleryService.isEnabled()) {145return undefined;146}147148if (chatEntitlementService.sentiment.hidden || chatEntitlementService.sentiment.disabled) {149return undefined;150}151152if (cachedExtensionToggleData) {153return cachedExtensionToggleData;154}155156if (productService.extensionRecommendations) {157const settingsEditorRecommendedExtensions: IStringDictionary<IExtensionRecommendations> = {};158Object.keys(productService.extensionRecommendations).forEach(extensionId => {159const extensionInfo = productService.extensionRecommendations![extensionId];160if (extensionInfo.onSettingsEditorOpen) {161settingsEditorRecommendedExtensions[extensionId] = extensionInfo;162}163});164165const recommendedExtensionsGalleryInfo: IStringDictionary<IGalleryExtension> = {};166for (const key in settingsEditorRecommendedExtensions) {167const extensionId = key;168// Recommend prerelease if not on Stable.169const isStable = productService.quality === 'stable';170try {171const extensions = await raceTimeout(172extensionGalleryService.getExtensions([{ id: extensionId, preRelease: !isStable }], CancellationToken.None),173EXTENSION_FETCH_TIMEOUT_MS);174if (extensions?.length === 1) {175recommendedExtensionsGalleryInfo[key] = extensions[0];176} else {177// same as network connection fail. we do not want a blank settings page: https://github.com/microsoft/vscode/issues/195722178// so instead of returning partial data we return undefined here179return undefined;180}181} catch (e) {182// Network connection fail. Return nothing rather than partial data.183return undefined;184}185}186187cachedExtensionToggleData = {188settingsEditorRecommendedExtensions,189recommendedExtensionsGalleryInfo190};191return cachedExtensionToggleData;192}193return undefined;194}195196/**197* Compares two nullable numbers such that null values always come after defined ones.198*/199export function compareTwoNullableNumbers(a: number | undefined, b: number | undefined): number {200const aOrMax = a ?? Number.MAX_SAFE_INTEGER;201const bOrMax = b ?? Number.MAX_SAFE_INTEGER;202if (aOrMax < bOrMax) {203return -1;204} else if (aOrMax > bOrMax) {205return 1;206} else {207return 0;208}209}210211export const PREVIEW_INDICATOR_DESCRIPTION = localize('previewIndicatorDescription', "Preview setting: this setting controls a new feature that is still under refinement yet ready to use. Feedback is welcome.");212export const EXPERIMENTAL_INDICATOR_DESCRIPTION = localize('experimentalIndicatorDescription', "Experimental setting: this setting controls a new feature that is actively being developed and may be unstable. It is subject to change or removal.");213export const ADVANCED_INDICATOR_DESCRIPTION = localize('advancedIndicatorDescription', "Advanced setting: this setting is intended for advanced scenarios and configurations. Only modify this if you know what it does.");214215export const knownAcronyms = new Set<string>();216[217'css',218'html',219'scss',220'less',221'json',222'js',223'ts',224'ie',225'id',226'php',227'scm',228].forEach(str => knownAcronyms.add(str));229230export const knownTermMappings = new Map<string, string>();231knownTermMappings.set('power shell', 'PowerShell');232knownTermMappings.set('powershell', 'PowerShell');233knownTermMappings.set('javascript', 'JavaScript');234knownTermMappings.set('typescript', 'TypeScript');235knownTermMappings.set('github', 'GitHub');236knownTermMappings.set('jet brains', 'JetBrains');237knownTermMappings.set('jetbrains', 'JetBrains');238knownTermMappings.set('re sharper', 'ReSharper');239knownTermMappings.set('resharper', 'ReSharper');240241export function wordifyKey(key: string): string {242key = key243.replace(/\.([a-z0-9])/g, (_, p1) => ` \u203A ${p1.toUpperCase()}`) // Replace dot with spaced '>'244.replace(/([a-z0-9])([A-Z])/g, '$1 $2') // Camel case to spacing, fooBar => foo Bar245.replace(/([A-Z]{1,})([A-Z][a-z])/g, '$1 $2') // Split consecutive capitals letters, AISearch => AI Search246.replace(/^[a-z]/g, match => match.toUpperCase()) // Upper casing all first letters, foo => Foo247.replace(/\b\w+\b/g, match => { // Upper casing known acronyms248return knownAcronyms.has(match.toLowerCase()) ?249match.toUpperCase() :250match;251});252253for (const [k, v] of knownTermMappings) {254key = key.replace(new RegExp(`\\b${k}\\b`, 'gi'), v);255}256257return key;258}259260261