Path: blob/main/src/vs/workbench/contrib/preferences/common/preferences.ts
3296 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 { IContextKeyService, 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 { ISearchResult, ISettingsEditorModel } from '../../../services/preferences/common/preferences.js';1516export interface IWorkbenchSettingsConfiguration {17workbench: {18settings: {19openDefaultSettings: boolean;20naturalLanguageSearchEndpoint: string;21naturalLanguageSearchKey: string;22naturalLanguageSearchAutoIngestFeedback: boolean;23useNaturalLanguageSearchPost: boolean;24enableNaturalLanguageSearch: boolean;25enableNaturalLanguageSearchFeedback: boolean;26};27};28}2930export interface IEndpointDetails {31urlBase: string;32key?: string;33}3435export const IPreferencesSearchService = createDecorator<IPreferencesSearchService>('preferencesSearchService');3637export interface IPreferencesSearchService {38readonly _serviceBrand: undefined;3940getLocalSearchProvider(filter: string): ISearchProvider;41getRemoteSearchProvider(filter: string, newExtensionsOnly?: boolean): ISearchProvider | undefined;42getAiSearchProvider(filter: string): IAiSearchProvider | undefined;43}4445export interface ISearchProvider {46searchModel(preferencesModel: ISettingsEditorModel, token: CancellationToken): Promise<ISearchResult | null>;47}4849export interface IRemoteSearchProvider extends ISearchProvider {50setFilter(filter: string): void;51}5253export interface IAiSearchProvider extends IRemoteSearchProvider {54getLLMRankedResults(token: CancellationToken): Promise<ISearchResult | null>;55}5657export const PREFERENCES_EDITOR_COMMAND_OPEN = 'workbench.preferences.action.openPreferencesEditor';58export const CONTEXT_PREFERENCES_SEARCH_FOCUS = new RawContextKey<boolean>('inPreferencesSearch', false);5960export const SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS = 'settings.action.clearSearchResults';61export const SETTINGS_EDITOR_COMMAND_SHOW_AI_RESULTS = 'settings.action.showAIResults';62export const SETTINGS_EDITOR_COMMAND_TOGGLE_AI_SEARCH = 'settings.action.toggleAiSearch';63export const SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU = 'settings.action.showContextMenu';64export const SETTINGS_EDITOR_COMMAND_SUGGEST_FILTERS = 'settings.action.suggestFilters';6566export const CONTEXT_SETTINGS_EDITOR = new RawContextKey<boolean>('inSettingsEditor', false);67export const CONTEXT_SETTINGS_JSON_EDITOR = new RawContextKey<boolean>('inSettingsJSONEditor', false);68export const CONTEXT_SETTINGS_SEARCH_FOCUS = new RawContextKey<boolean>('inSettingsSearch', false);69export const CONTEXT_TOC_ROW_FOCUS = new RawContextKey<boolean>('settingsTocRowFocus', false);70export const CONTEXT_SETTINGS_ROW_FOCUS = new RawContextKey<boolean>('settingRowFocus', false);71export const CONTEXT_KEYBINDINGS_EDITOR = new RawContextKey<boolean>('inKeybindings', false);72export const CONTEXT_KEYBINDINGS_SEARCH_FOCUS = new RawContextKey<boolean>('inKeybindingsSearch', false);73export const CONTEXT_KEYBINDING_FOCUS = new RawContextKey<boolean>('keybindingFocus', false);74export const CONTEXT_WHEN_FOCUS = new RawContextKey<boolean>('whenFocus', false);75export const CONTEXT_AI_SETTING_RESULTS_AVAILABLE = new RawContextKey<boolean>('aiSettingResultsAvailable', false);7677export const KEYBINDINGS_EDITOR_COMMAND_SEARCH = 'keybindings.editor.searchKeybindings';78export const KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS = 'keybindings.editor.clearSearchResults';79export const KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_HISTORY = 'keybindings.editor.clearSearchHistory';80export const KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS = 'keybindings.editor.recordSearchKeys';81export const KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE = 'keybindings.editor.toggleSortByPrecedence';82export const KEYBINDINGS_EDITOR_COMMAND_DEFINE = 'keybindings.editor.defineKeybinding';83export const KEYBINDINGS_EDITOR_COMMAND_ADD = 'keybindings.editor.addKeybinding';84export const KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN = 'keybindings.editor.defineWhenExpression';85export const KEYBINDINGS_EDITOR_COMMAND_ACCEPT_WHEN = 'keybindings.editor.acceptWhenExpression';86export const KEYBINDINGS_EDITOR_COMMAND_REJECT_WHEN = 'keybindings.editor.rejectWhenExpression';87export const KEYBINDINGS_EDITOR_COMMAND_REMOVE = 'keybindings.editor.removeKeybinding';88export const KEYBINDINGS_EDITOR_COMMAND_RESET = 'keybindings.editor.resetKeybinding';89export const KEYBINDINGS_EDITOR_COMMAND_COPY = 'keybindings.editor.copyKeybindingEntry';90export const KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND = 'keybindings.editor.copyCommandKeybindingEntry';91export const KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND_TITLE = 'keybindings.editor.copyCommandTitle';92export const KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR = 'keybindings.editor.showConflicts';93export const KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS = 'keybindings.editor.focusKeybindings';94export const KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS = 'keybindings.editor.showDefaultKeybindings';95export const KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS = 'keybindings.editor.showUserKeybindings';96export const KEYBINDINGS_EDITOR_SHOW_EXTENSION_KEYBINDINGS = 'keybindings.editor.showExtensionKeybindings';9798export const MODIFIED_SETTING_TAG = 'modified';99export const EXTENSION_SETTING_TAG = 'ext:';100export const FEATURE_SETTING_TAG = 'feature:';101export const ID_SETTING_TAG = 'id:';102export const LANGUAGE_SETTING_TAG = 'lang:';103export const GENERAL_TAG_SETTING_TAG = 'tag:';104export const POLICY_SETTING_TAG = 'hasPolicy';105export const WORKSPACE_TRUST_SETTING_TAG = 'workspaceTrust';106export const REQUIRE_TRUSTED_WORKSPACE_SETTING_TAG = 'requireTrustedWorkspace';107export const KEYBOARD_LAYOUT_OPEN_PICKER = 'workbench.action.openKeyboardLayoutPicker';108109export const ENABLE_LANGUAGE_FILTER = true;110111export const ENABLE_EXTENSION_TOGGLE_SETTINGS = true;112export const EXTENSION_FETCH_TIMEOUT_MS = 1000;113114export const STRING_MATCH_SEARCH_PROVIDER_NAME = 'local';115export const TF_IDF_SEARCH_PROVIDER_NAME = 'tfIdf';116export const FILTER_MODEL_SEARCH_PROVIDER_NAME = 'filterModel';117export const EMBEDDINGS_ONLY_SEARCH_PROVIDER_NAME = 'embeddingsOnly';118export const EMBEDDINGS_SEARCH_PROVIDER_NAME = 'embeddingsFull';119export const LLM_RANKED_SEARCH_PROVIDER_NAME = 'llmRanked';120121export enum WorkbenchSettingsEditorSettings {122ShowAISearchToggle = 'workbench.settings.showAISearchToggle',123EnableNaturalLanguageSearch = 'workbench.settings.enableNaturalLanguageSearch',124}125126export type ExtensionToggleData = {127settingsEditorRecommendedExtensions: IStringDictionary<IExtensionRecommendations>;128recommendedExtensionsGalleryInfo: IStringDictionary<IGalleryExtension>;129commonlyUsed: string[];130};131132let cachedExtensionToggleData: ExtensionToggleData | undefined;133134export async function getExperimentalExtensionToggleData(135contextKeyService: IContextKeyService,136extensionGalleryService: IExtensionGalleryService,137productService: IProductService,138): Promise<ExtensionToggleData | undefined> {139if (!ENABLE_EXTENSION_TOGGLE_SETTINGS) {140return undefined;141}142143if (!extensionGalleryService.isEnabled()) {144return undefined;145}146147if (contextKeyService.getContextKeyValue<boolean>('chatSetupHidden')) {148return undefined;149}150151if (cachedExtensionToggleData) {152return cachedExtensionToggleData;153}154155if (productService.extensionRecommendations && productService.commonlyUsedSettings) {156const settingsEditorRecommendedExtensions: IStringDictionary<IExtensionRecommendations> = {};157Object.keys(productService.extensionRecommendations).forEach(extensionId => {158const extensionInfo = productService.extensionRecommendations![extensionId];159if (extensionInfo.onSettingsEditorOpen) {160settingsEditorRecommendedExtensions[extensionId] = extensionInfo;161}162});163164const recommendedExtensionsGalleryInfo: IStringDictionary<IGalleryExtension> = {};165for (const key in settingsEditorRecommendedExtensions) {166const extensionId = key;167// Recommend prerelease if not on Stable.168const isStable = productService.quality === 'stable';169try {170const extensions = await raceTimeout(171extensionGalleryService.getExtensions([{ id: extensionId, preRelease: !isStable }], CancellationToken.None),172EXTENSION_FETCH_TIMEOUT_MS);173if (extensions?.length === 1) {174recommendedExtensionsGalleryInfo[key] = extensions[0];175} else {176// same as network connection fail. we do not want a blank settings page: https://github.com/microsoft/vscode/issues/195722177// so instead of returning partial data we return undefined here178return undefined;179}180} catch (e) {181// Network connection fail. Return nothing rather than partial data.182return undefined;183}184}185186cachedExtensionToggleData = {187settingsEditorRecommendedExtensions,188recommendedExtensionsGalleryInfo,189commonlyUsed: productService.commonlyUsedSettings190};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.");213214export const knownAcronyms = new Set<string>();215[216'css',217'html',218'scss',219'less',220'json',221'js',222'ts',223'ie',224'id',225'php',226'scm',227].forEach(str => knownAcronyms.add(str));228229export const knownTermMappings = new Map<string, string>();230knownTermMappings.set('power shell', 'PowerShell');231knownTermMappings.set('powershell', 'PowerShell');232knownTermMappings.set('javascript', 'JavaScript');233knownTermMappings.set('typescript', 'TypeScript');234knownTermMappings.set('github', 'GitHub');235knownTermMappings.set('jet brains', 'JetBrains');236knownTermMappings.set('jetbrains', 'JetBrains');237knownTermMappings.set('re sharper', 'ReSharper');238knownTermMappings.set('resharper', 'ReSharper');239240export function wordifyKey(key: string): string {241key = key242.replace(/\.([a-z0-9])/g, (_, p1) => ` \u203A ${p1.toUpperCase()}`) // Replace dot with spaced '>'243.replace(/([a-z0-9])([A-Z])/g, '$1 $2') // Camel case to spacing, fooBar => foo Bar244.replace(/([A-Z]{1,})([A-Z][a-z])/g, '$1 $2') // Split consecutive capitals letters, AISearch => AI Search245.replace(/^[a-z]/g, match => match.toUpperCase()) // Upper casing all first letters, foo => Foo246.replace(/\b\w+\b/g, match => { // Upper casing known acronyms247return knownAcronyms.has(match.toLowerCase()) ?248match.toUpperCase() :249match;250});251252for (const [k, v] of knownTermMappings) {253key = key.replace(new RegExp(`\\b${k}\\b`, 'gi'), v);254}255256return key;257}258259260