From 51ce580a33efbaf0630b09b2f7ea2c0313a2b500 Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Mon, 8 Jul 2024 17:21:41 +0200 Subject: [PATCH] Add url handling for settings and adopt in release notes (#219702) * Add url handling for settings and adopt in release notes Fixes #212079 * Update regex to use product service, fix test * Incorporate feedback * Check value of setting before passing to settings editor * Fix test --- .../browser/markdownSettingRenderer.ts | 34 +++-------- .../browser/markdownSettingRenderer.test.ts | 14 ++++- .../update/browser/update.contribution.ts | 1 - .../preferences/browser/preferencesService.ts | 58 ++++++++++++++++++- .../preferences/common/preferences.ts | 3 + .../test/browser/preferencesService.test.ts | 2 + 6 files changed, 81 insertions(+), 31 deletions(-) diff --git a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 095a2978564906..fe1c3c04e266d3 100644 --- a/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts +++ b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts @@ -4,22 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { IPreferencesService, ISetting, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; +import { IPreferencesService, ISetting } from 'vs/workbench/services/preferences/common/preferences'; import { settingKeyToDisplayFormat } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; import { URI } from 'vs/base/common/uri'; -import { Schemas } from 'vs/base/common/network'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { DefaultSettings } from 'vs/workbench/services/preferences/common/preferencesModels'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems'; import { IAction } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; - -const codeSettingRegex = /^/; +import { Schemas } from 'vs/base/common/network'; export class SimpleSettingRenderer { - private _defaultSettings: DefaultSettings; + private readonly codeSettingRegex: RegExp; + private _updatedSettings = new Map(); // setting ID to user's original setting value private _encounteredSettings = new Map(); // setting ID to setting private _featuredSettings = new Map(); // setting ID to feature value @@ -29,9 +27,9 @@ export class SimpleSettingRenderer { @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IPreferencesService private readonly _preferencesService: IPreferencesService, @ITelemetryService private readonly _telemetryService: ITelemetryService, - @IClipboardService private readonly _clipboardService: IClipboardService + @IClipboardService private readonly _clipboardService: IClipboardService, ) { - this._defaultSettings = new DefaultSettings([], ConfigurationTarget.USER); + this.codeSettingRegex = new RegExp(`^`); } get featuredSettingStates(): Map { @@ -44,12 +42,12 @@ export class SimpleSettingRenderer { getHtmlRenderer(): (html: string) => string { return (html): string => { - const match = codeSettingRegex.exec(html); + const match = this.codeSettingRegex.exec(html); if (match && match.length === 4) { const settingId = match[2]; const rendered = this.render(settingId, match[3]); if (rendered) { - html = html.replace(codeSettingRegex, rendered); + html = html.replace(this.codeSettingRegex, rendered); } } return html; @@ -60,25 +58,11 @@ export class SimpleSettingRenderer { return `${Schemas.codeSetting}://${settingId}${value ? `/${value}` : ''}`; } - private settingsGroups: ISettingsGroup[] | undefined = undefined; private getSetting(settingId: string): ISetting | undefined { - if (!this.settingsGroups) { - this.settingsGroups = this._defaultSettings.getSettingsGroups(); - } if (this._encounteredSettings.has(settingId)) { return this._encounteredSettings.get(settingId); } - for (const group of this.settingsGroups) { - for (const section of group.sections) { - for (const setting of section.settings) { - if (setting.key === settingId) { - this._encounteredSettings.set(settingId, setting); - return setting; - } - } - } - } - return undefined; + return this._preferencesService.getSetting(settingId); } parseValue(settingId: string, value: string): any { diff --git a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts index c0ee61a516173e..1889507c974292 100644 --- a/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts +++ b/src/vs/workbench/contrib/markdown/test/browser/markdownSettingRenderer.test.ts @@ -58,7 +58,15 @@ suite('Markdown Setting Renderer Test', () => { suiteSetup(() => { configurationService = new MarkdownConfigurationService(); - preferencesService = {}; + preferencesService = { + getSetting: (setting) => { + let type = 'boolean'; + if (setting.includes('string')) { + type = 'string'; + } + return { type, key: setting }; + } + }; contextMenuService = {}; Registry.as(Extensions.Configuration).registerConfiguration(configuration); settingRenderer = new SimpleSettingRenderer(configurationService, contextMenuService, preferencesService, { publicLog2: () => { } } as any, { writeText: async () => { } } as any); @@ -70,10 +78,10 @@ suite('Markdown Setting Renderer Test', () => { test('render code setting button with value', () => { const htmlRenderer = settingRenderer.getHtmlRenderer(); - const htmlNoValue = ''; + const htmlNoValue = ''; const renderedHtmlNoValue = htmlRenderer(htmlNoValue); assert.strictEqual(renderedHtmlNoValue, - ` + ` example.booleanSetting `); diff --git a/src/vs/workbench/contrib/update/browser/update.contribution.ts b/src/vs/workbench/contrib/update/browser/update.contribution.ts index fa3edab7d3acc5..9e34bee7036cd7 100644 --- a/src/vs/workbench/contrib/update/browser/update.contribution.ts +++ b/src/vs/workbench/contrib/update/browser/update.contribution.ts @@ -81,7 +81,6 @@ export class ShowCurrentReleaseNotesFromCurrentFileAction extends Action2 { }, category: localize2('developerCategory', "Developer"), f1: true, - precondition: RELEASE_NOTES_URL }); } diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 1d8f6e0ebdc2ea..d12424fba94eb7 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -35,7 +35,7 @@ import { IJSONEditingService } from 'vs/workbench/services/configuration/common/ import { GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; import { KeybindingsEditorInput } from 'vs/workbench/services/preferences/browser/keybindingsEditorInput'; -import { DEFAULT_SETTINGS_EDITOR_SETTING, FOLDER_SETTINGS_PATH, IKeybindingsEditorOptions, IKeybindingsEditorPane, IOpenSettingsOptions, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorOptions, USE_SPLIT_JSON_SETTING, validateSettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; +import { DEFAULT_SETTINGS_EDITOR_SETTING, FOLDER_SETTINGS_PATH, IKeybindingsEditorOptions, IKeybindingsEditorPane, IOpenSettingsOptions, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorOptions, ISettingsGroup, SETTINGS_AUTHORITY, USE_SPLIT_JSON_SETTING, validateSettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { defaultKeybindingsContents, DefaultKeybindingsEditorModel, DefaultRawSettingsEditorModel, DefaultSettings, DefaultSettingsEditorModel, Settings2EditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; @@ -45,6 +45,7 @@ import { isObject } from 'vs/base/common/types'; import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; import { IUserDataProfilesService } from 'vs/platform/userDataProfile/common/userDataProfile'; +import { IURLService } from 'vs/platform/url/common/url'; const emptyEditableSettingsContent = '{\n}'; @@ -58,6 +59,9 @@ export class PreferencesService extends Disposable implements IPreferencesServic private _defaultWorkspaceSettingsContentModel: DefaultSettings | undefined; private _defaultFolderSettingsContentModel: DefaultSettings | undefined; + private _settingsGroups: ISettingsGroup[] | undefined = undefined; + private _defaultSettings: DefaultSettings | undefined = undefined; + constructor( @IEditorService private readonly editorService: IEditorService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @@ -75,7 +79,8 @@ export class PreferencesService extends Disposable implements IPreferencesServic @ILanguageService private readonly languageService: ILanguageService, @ILabelService private readonly labelService: ILabelService, @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, - @ITextEditorService private readonly textEditorService: ITextEditorService + @ITextEditorService private readonly textEditorService: ITextEditorService, + @IURLService urlService: IURLService ) { super(); // The default keybindings.json updates based on keyboard layouts, so here we make sure @@ -88,6 +93,8 @@ export class PreferencesService extends Disposable implements IPreferencesServic } modelService.updateModel(model, defaultKeybindingsContents(keybindingService)); })); + + this._register(urlService.registerHandler(this)); } readonly defaultKeybindingsResource = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/keybindings.json' }); @@ -593,6 +600,53 @@ export class PreferencesService extends Disposable implements IPreferencesServic return position; } + private get defaultSettings(): DefaultSettings { + if (!this._defaultSettings) { + this._defaultSettings = new DefaultSettings([], ConfigurationTarget.USER); + } + return this._defaultSettings; + } + + getSetting(settingId: string): ISetting | undefined { + if (!this._settingsGroups) { + this._settingsGroups = this.defaultSettings.getSettingsGroups(); + } + + for (const group of this._settingsGroups) { + for (const section of group.sections) { + for (const setting of section.settings) { + if (setting.key === settingId) { + return setting; + } + } + } + } + return undefined; + } + + /** + * Should be of the format: + * code://settings/settingName + * Examples: + * code://settings/files.autoSave + * + */ + async handleURL(uri: URI): Promise { + if (uri.authority !== SETTINGS_AUTHORITY) { + return false; + } + + const openSettingsOptions: IOpenSettingsOptions = {}; + const settingInfo = uri.path.split('/').filter(part => !!part); + if ((settingInfo.length === 0) || !this.getSetting(settingInfo[0])) { + return false; + } + + openSettingsOptions.query = settingInfo[0]; + this.openSettings(openSettingsOptions); + return true; + } + public override dispose(): void { this._onDispose.fire(); super.dispose(); diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 1f0ff92e25a5ac..f45dc085c294c5 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -257,6 +257,7 @@ export interface IPreferencesService { openDefaultKeybindingsFile(): Promise; openLanguageSpecificSettings(languageId: string, options?: IOpenSettingsOptions): Promise; getEditableSettingsURI(configurationTarget: ConfigurationTarget, resource?: URI): Promise; + getSetting(settingId: string): ISetting | undefined; createSplitJsonEditorInput(configurationTarget: ConfigurationTarget, resource: URI): EditorInput; } @@ -329,3 +330,5 @@ export interface IDefineKeybindingEditorContribution extends IEditorContribution export const FOLDER_SETTINGS_PATH = '.vscode/settings.json'; export const DEFAULT_SETTINGS_EDITOR_SETTING = 'workbench.settings.openDefaultSettings'; export const USE_SPLIT_JSON_SETTING = 'workbench.settings.useSplitJSON'; + +export const SETTINGS_AUTHORITY = 'settings'; diff --git a/src/vs/workbench/services/preferences/test/browser/preferencesService.test.ts b/src/vs/workbench/services/preferences/test/browser/preferencesService.test.ts index 7e5107a38274a9..bbc9001d5d63fd 100644 --- a/src/vs/workbench/services/preferences/test/browser/preferencesService.test.ts +++ b/src/vs/workbench/services/preferences/test/browser/preferencesService.test.ts @@ -9,6 +9,7 @@ import { TestCommandService } from 'vs/editor/test/browser/editorTestServices'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IURLService } from 'vs/platform/url/common/url'; import { DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { TestJSONEditingService } from 'vs/workbench/services/configuration/test/common/testServices'; @@ -32,6 +33,7 @@ suite('PreferencesService', () => { testInstantiationService.stub(IJSONEditingService, TestJSONEditingService); testInstantiationService.stub(IRemoteAgentService, TestRemoteAgentService); testInstantiationService.stub(ICommandService, TestCommandService); + testInstantiationService.stub(IURLService, { registerHandler: () => { } }); // PreferencesService creates a PreferencesEditorInput which depends on IPreferencesService, add the real one, not a stub const collection = new ServiceCollection();