mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-25 01:41:41 +01:00
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
This commit is contained in:
@@ -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 = /^<code (codesetting)="([^\s"\:]+)(?::([^"]+))?">/;
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
||||
export class SimpleSettingRenderer {
|
||||
private _defaultSettings: DefaultSettings;
|
||||
private readonly codeSettingRegex: RegExp;
|
||||
|
||||
private _updatedSettings = new Map<string, any>(); // setting ID to user's original setting value
|
||||
private _encounteredSettings = new Map<string, ISetting>(); // setting ID to setting
|
||||
private _featuredSettings = new Map<string, any>(); // 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(`^<a (href)=".*code.*://settings/([^\\s"]+)"(?:\\s*codesetting="([^"]+)")?>`);
|
||||
}
|
||||
|
||||
get featuredSettingStates(): Map<string, boolean> {
|
||||
@@ -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 {
|
||||
|
||||
@@ -58,7 +58,15 @@ suite('Markdown Setting Renderer Test', () => {
|
||||
|
||||
suiteSetup(() => {
|
||||
configurationService = new MarkdownConfigurationService();
|
||||
preferencesService = <IPreferencesService>{};
|
||||
preferencesService = <IPreferencesService>{
|
||||
getSetting: (setting) => {
|
||||
let type = 'boolean';
|
||||
if (setting.includes('string')) {
|
||||
type = 'string';
|
||||
}
|
||||
return { type, key: setting };
|
||||
}
|
||||
};
|
||||
contextMenuService = <IContextMenuService>{};
|
||||
Registry.as<IConfigurationRegistry>(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 = '<code codesetting="example.booleanSetting">';
|
||||
const htmlNoValue = '<a href="code-oss://settings/example.booleanSetting" codesetting="true">';
|
||||
const renderedHtmlNoValue = htmlRenderer(htmlNoValue);
|
||||
assert.strictEqual(renderedHtmlNoValue,
|
||||
`<code tabindex="0"><a href="code-setting://example.booleanSetting" class="codesetting" title="View or change setting" aria-role="button"><svg width="14" height="14" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M9.1 4.4L8.6 2H7.4l-.5 2.4-.7.3-2-1.3-.9.8 1.3 2-.2.7-2.4.5v1.2l2.4.5.3.8-1.3 2 .8.8 2-1.3.8.3.4 2.3h1.2l.5-2.4.8-.3 2 1.3.8-.8-1.3-2 .3-.8 2.3-.4V7.4l-2.4-.5-.3-.8 1.3-2-.8-.8-2 1.3-.7-.2zM9.4 1l.5 2.4L12 2.1l2 2-1.4 2.1 2.4.4v2.8l-2.4.5L14 12l-2 2-2.1-1.4-.5 2.4H6.6l-.5-2.4L4 13.9l-2-2 1.4-2.1L1 9.4V6.6l2.4-.5L2.1 4l2-2 2.1 1.4.4-2.4h2.8zm.6 7c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zM8 9c.6 0 1-.4 1-1s-.4-1-1-1-1 .4-1 1 .4 1 1 1z"/></svg>
|
||||
`<code tabindex="0"><a href="code-setting://example.booleanSetting/true" class="codesetting" title="View or change setting" aria-role="button"><svg width="14" height="14" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M9.1 4.4L8.6 2H7.4l-.5 2.4-.7.3-2-1.3-.9.8 1.3 2-.2.7-2.4.5v1.2l2.4.5.3.8-1.3 2 .8.8 2-1.3.8.3.4 2.3h1.2l.5-2.4.8-.3 2 1.3.8-.8-1.3-2 .3-.8 2.3-.4V7.4l-2.4-.5-.3-.8 1.3-2-.8-.8-2 1.3-.7-.2zM9.4 1l.5 2.4L12 2.1l2 2-1.4 2.1 2.4.4v2.8l-2.4.5L14 12l-2 2-2.1-1.4-.5 2.4H6.6l-.5-2.4L4 13.9l-2-2 1.4-2.1L1 9.4V6.6l2.4-.5L2.1 4l2-2 2.1 1.4.4-2.4h2.8zm.6 7c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zM8 9c.6 0 1-.4 1-1s-.4-1-1-1-1 .4-1 1 .4 1 1 1z"/></svg>
|
||||
<span class="separator"></span>
|
||||
<span class="setting-name">example.booleanSetting</span>
|
||||
</a></code><code>`);
|
||||
|
||||
@@ -81,7 +81,6 @@ export class ShowCurrentReleaseNotesFromCurrentFileAction extends Action2 {
|
||||
},
|
||||
category: localize2('developerCategory', "Developer"),
|
||||
f1: true,
|
||||
precondition: RELEASE_NOTES_URL
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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<boolean> {
|
||||
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();
|
||||
|
||||
@@ -257,6 +257,7 @@ export interface IPreferencesService {
|
||||
openDefaultKeybindingsFile(): Promise<IEditorPane | undefined>;
|
||||
openLanguageSpecificSettings(languageId: string, options?: IOpenSettingsOptions): Promise<IEditorPane | undefined>;
|
||||
getEditableSettingsURI(configurationTarget: ConfigurationTarget, resource?: URI): Promise<URI | null>;
|
||||
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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user