From fea4db1666427b95b649614fb7c40f25e3f5cf8d Mon Sep 17 00:00:00 2001 From: Christof Marti Date: Wed, 16 Nov 2016 13:51:42 -0800 Subject: [PATCH] Revert "#15361 Refactoring" This reverts commit 463438e7c979850842800460f537d2e943663bfc. --- .../browser/defaultSettingsEditors.ts | 381 ++++++++---------- .../settings/browser/openSettingsService.ts | 280 ++++++++++--- .../parts/settings/common/defaultSettings.ts | 156 ------- .../parts/settings/common/openSettings.ts | 39 +- 4 files changed, 391 insertions(+), 465 deletions(-) delete mode 100644 src/vs/workbench/parts/settings/common/defaultSettings.ts diff --git a/src/vs/workbench/parts/settings/browser/defaultSettingsEditors.ts b/src/vs/workbench/parts/settings/browser/defaultSettingsEditors.ts index 5847191af05..0b339046925 100644 --- a/src/vs/workbench/parts/settings/browser/defaultSettingsEditors.ts +++ b/src/vs/workbench/parts/settings/browser/defaultSettingsEditors.ts @@ -6,45 +6,38 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as nls from 'vs/nls'; import URI from 'vs/base/common/uri'; -import { hasClass, getDomNodePagePosition } from 'vs/base/browser/dom'; -import { parse } from 'vs/base/common/json'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IAction } from 'vs/base/common/actions'; -import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import * as network from 'vs/base/common/network'; +import * as strings from 'vs/base/common/strings'; import Event, { Emitter } from 'vs/base/common/event'; import { LinkedMap as Map } from 'vs/base/common/map'; -import { Registry } from 'vs/platform/platform'; import { EditorOptions, EditorInput, } from 'vs/workbench/common/editor'; import { StringEditorInput } from 'vs/workbench/common/editor/stringEditorInput'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import { ICommonCodeEditor, IEditorViewState } from 'vs/editor/common/editorCommon'; import { StringEditor } from 'vs/workbench/browser/parts/editor/stringEditor'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IMessageService } from 'vs/platform/message/common/message'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEventService } from 'vs/platform/event/common/event'; +import { IThemeService } from 'vs/workbench/services/themes/common/themeService'; +import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IFoldingController, ID as FoldingContributionId } from 'vs/editor/contrib/folding/common/folding'; -import { IOpenSettingsService, ISettingsGroup, ISetting } from 'vs/workbench/parts/settings/common/openSettings'; -import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; -import { ICodeEditor, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IConfigurationNode, IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { Registry } from 'vs/platform/platform'; export class AbstractSettingsInput extends StringEditorInput { private _willDispose = new Emitter(); public willDispose: Event = this._willDispose.event; - constructor( - name: string, - description: string, - value: string, - private _resource: URI, - modeId: string, - singleton: boolean, - @IInstantiationService instantiationService: IInstantiationService - ) { - super(name, description, value, modeId, singleton, instantiationService); - } - - public getResource(): URI { - return this._resource; + public get resource(): URI { + return this.getResource(); } public dispose() { @@ -54,39 +47,177 @@ export class AbstractSettingsInput extends StringEditorInput { } } +interface ISettingsGroup { + title: string; + sections: ISettingsSection[]; +} + +interface ISettingsSection { + description?: string; + settings: ISetting[]; +} + +interface ISetting { + key: string; + value: any; + description?: string; +} + +class SettingsModel { + + private settingsGroups: ISettingsGroup[]; + private indent: string; + + constructor( @IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService) { + const editorConfig = this.configurationService.getConfiguration(); + this.indent = editorConfig.editor.insertSpaces ? strings.repeat(' ', editorConfig.editor.tabSize) : '\t'; + + const configurations = Registry.as(Extensions.Configuration).getConfigurations(); + this.settingsGroups = configurations.sort(this.compareConfigurationNodes).reduce((result, config) => this.parseConfig(config, result), []); + } + + private parseConfig(config: IConfigurationNode, result: ISettingsGroup[], settingsGroup?: ISettingsGroup): ISettingsGroup[] { + if (config.title) { + if (!settingsGroup) { + settingsGroup = result.filter(g => g.title === config.title)[0]; + if (!settingsGroup) { + settingsGroup = { sections: [{ settings: [] }], title: config.title }; + result.push(settingsGroup); + } + } else { + settingsGroup.sections[settingsGroup.sections.length - 1].description = config.title; + } + } + if (config.properties) { + if (!settingsGroup) { + settingsGroup = { sections: [{ settings: [] }], title: config.id }; + result.push(settingsGroup); + } + const configurationSettings = Object.keys(config.properties).map((key) => { + const prop = config.properties[key]; + const value = prop.default; + const description = prop.description || ''; + return { key, value, description }; + }); + settingsGroup.sections[settingsGroup.sections.length - 1].settings.push(...configurationSettings); + } + if (config.allOf) { + config.allOf.forEach(c => this.parseConfig(c, result, settingsGroup)); + } + return result; + } + + private compareConfigurationNodes(c1: IConfigurationNode, c2: IConfigurationNode): number { + if (typeof c1.order !== 'number') { + return 1; + } + if (typeof c2.order !== 'number') { + return -1; + } + if (c1.order === c2.order) { + const title1 = c1.title || ''; + const title2 = c2.title || ''; + return title1.localeCompare(title2); + } + return c1.order - c2.order; + } + + public toContent(): string { + let defaultsHeader = '// ' + nls.localize('defaultSettingsHeader', "Overwrite settings by placing them into your settings file.\n"); + defaultsHeader += '// ' + nls.localize('defaultSettingsHeader2', "See http://go.microsoft.com/fwlink/?LinkId=808995 for the most commonly used settings.\n\n"); + + let lastEntry = -1; + const result: string[] = []; + result.push('{'); + for (const group of this.settingsGroups) { + result.push('// ' + group.title); + for (const section of group.sections) { + if (section.description) { + result.push(this.indent + '// ' + section.description); + } + for (const setting of section.settings) { + result.push(this.indent + '// ' + setting.description); + let valueString = JSON.stringify(setting.value, null, this.indent); + if (valueString && (typeof setting.value === 'object')) { + valueString = valueString.split('\n').join('\n' + this.indent); + } + if (lastEntry !== -1) { + result[lastEntry] += ','; + } + lastEntry = result.length; + result.push(this.indent + JSON.stringify(setting.key) + ': ' + valueString); + result.push(''); + } + } + } + result.push('}'); + + return defaultsHeader + result.join('\n'); + } + +} + export class DefaultSettingsInput extends AbstractSettingsInput { + static uri: URI = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/settings.json' }); // URI is used to register JSON schema support private static INSTANCE: DefaultSettingsInput; - public static getInstance(instantiationService: IInstantiationService, openSettingsService: IOpenSettingsService): DefaultSettingsInput { + public static getInstance(instantiationService: IInstantiationService, configurationService: IWorkspaceConfigurationService): DefaultSettingsInput { if (!DefaultSettingsInput.INSTANCE) { - const defaultSettings = openSettingsService.defaultSettings; - DefaultSettingsInput.INSTANCE = instantiationService.createInstance(DefaultSettingsInput, nls.localize('defaultName', "Default Settings"), null, defaultSettings.content, defaultSettings.uri, 'application/json', false); + const content = instantiationService.createInstance(SettingsModel).toContent(); + DefaultSettingsInput.INSTANCE = instantiationService.createInstance(DefaultSettingsInput, nls.localize('defaultName', "Default Settings"), null, content, 'application/json', false); } return DefaultSettingsInput.INSTANCE; } + + protected getResource(): URI { + return DefaultSettingsInput.uri; + } } export class DefaultKeybindingsInput extends AbstractSettingsInput { private static INSTANCE: DefaultKeybindingsInput; - public static getInstance(instantiationService: IInstantiationService, openSettingsService: IOpenSettingsService): DefaultKeybindingsInput { + public static getInstance(instantiationService: IInstantiationService, keybindingService: IKeybindingService): DefaultKeybindingsInput { if (!DefaultKeybindingsInput.INSTANCE) { - const defaultKeybindings = openSettingsService.defaultKeybindings; - DefaultKeybindingsInput.INSTANCE = instantiationService.createInstance(DefaultKeybindingsInput, nls.localize('defaultKeybindings', "Default Keyboard Shortcuts"), null, defaultKeybindings.content, defaultKeybindings.uri, 'application/json', false); + const defaultsHeader = '// ' + nls.localize('defaultKeybindingsHeader', "Overwrite key bindings by placing them into your key bindings file."); + const defaultContents = keybindingService.getDefaultKeybindings(); + + DefaultKeybindingsInput.INSTANCE = instantiationService.createInstance(DefaultKeybindingsInput, nls.localize('defaultKeybindings', "Default Keyboard Shortcuts"), null, defaultsHeader + '\n' + defaultContents, 'application/json', false); } return DefaultKeybindingsInput.INSTANCE; } + + protected getResource(): URI { + return URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/keybindings.json' }); // URI is used to register JSON schema support + } } export class DefaultSettingsEditor extends StringEditor { public static ID = 'workbench.editors.defaultSettingsEditor'; - private static VIEW_STATE: Map = new Map(); + private static VIEW_STATE: Map = new Map(); private inputDisposeListener; + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IInstantiationService instantiationService: IInstantiationService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IStorageService storageService: IStorageService, + @IMessageService messageService: IMessageService, + @IConfigurationService configurationService: IConfigurationService, + @IEventService eventService: IEventService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService, + @IThemeService themeService: IThemeService, + @ICommandService private commandService: ICommandService, + @IUntitledEditorService untitledEditorService: IUntitledEditorService + ) { + super(telemetryService, instantiationService, contextService, storageService, + messageService, configurationService, eventService, editorService, themeService, untitledEditorService); + } + public getId(): string { return DefaultSettingsEditor.ID; } @@ -105,7 +236,7 @@ export class DefaultSettingsEditor extends StringEditor { } protected restoreViewState(input: EditorInput) { - const viewState = DefaultSettingsEditor.VIEW_STATE.get((input).getResource()); + const viewState = DefaultSettingsEditor.VIEW_STATE.get(this.getResource(input)); if (viewState) { this.getControl().restoreViewState(viewState); } else if (input instanceof DefaultSettingsInput) { @@ -116,7 +247,7 @@ export class DefaultSettingsEditor extends StringEditor { private saveState(input: AbstractSettingsInput) { const state = this.getControl().saveViewState(); if (state) { - const resource = input.getResource(); + const resource = this.getResource(input); if (DefaultSettingsEditor.VIEW_STATE.has(resource)) { DefaultSettingsEditor.VIEW_STATE.delete(resource); } @@ -124,8 +255,12 @@ export class DefaultSettingsEditor extends StringEditor { } } + private getResource(input: AbstractSettingsInput): URI { + return input.resource; + } + private foldAll() { - const foldingController = (this.getControl()).getContribution(FoldingContributionId); + const foldingController = (this.getControl()).getContribution(FoldingContributionId); foldingController.foldAll(); } @@ -137,178 +272,4 @@ export class DefaultSettingsEditor extends StringEditor { this.inputDisposeListener = (input).willDispose(() => this.saveState(input)); } } -} - -@editorContribution -export class DefaultSettingsContribution extends Disposable implements editorCommon.IEditorContribution { - - private settingsActions: SettingsActionsDecorators = null; - - constructor(private editor: ICodeEditor, - @IInstantiationService private instantiationService: IInstantiationService, - @IOpenSettingsService private openSettingsService: IOpenSettingsService - ) { - super(); - this._register(editor.onDidChangeModel(() => this.onModelChanged())); - } - - public getId(): string { - return 'editor.contrib.settings'; - } - - private onModelChanged(): void { - const model = this.editor.getModel(); - - if (!this.canHandle(model)) { - if (this.settingsActions) { - this.settingsActions.dispose(); - this.settingsActions = null; - } - return; - } - - if (model.uri.fsPath === this.openSettingsService.defaultSettings.uri.fsPath) { - this.styleDefaultSettings(model); - } - } - - private canHandle(model: editorCommon.IModel) { - if (model) { - if (model.uri.fsPath === this.openSettingsService.defaultSettings.uri.fsPath) { - return true; - } - } - return false; - } - - private styleDefaultSettings(model: editorCommon.IModel) { - this.renderDecorations(model); - } - - private renderDecorations(model: editorCommon.IModel) { - this.settingsActions = this.instantiationService.createInstance(SettingsActionsDecorators, this.editor); - this.settingsActions.render(this.openSettingsService.defaultSettings.getSettingsGroups()); - } - -} - -export class SettingsActionsDecorators extends Disposable { - - private decorationIds: string[] = []; - - constructor(private editor: ICodeEditor, - @IOpenSettingsService private settingsService: IOpenSettingsService, - @IContextMenuService private contextMenuService: IContextMenuService - ) { - super(); - this._register(editor.onMouseUp(e => this.onEditorMouseUp(e))); - } - - public render(settingGroups: ISettingsGroup[]): void { - const model = this.editor.getModel(); - model.changeDecorations(changeAccessor => { - this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, []); - }); - model.changeDecorations(changeAccessor => { - this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, this.createDecorations(settingGroups, model)); - }); - } - - private createDecorations(settingsGroups: ISettingsGroup[], model: editorCommon.IModel): editorCommon.IModelDeltaDecoration[] { - let result: editorCommon.IModelDeltaDecoration[] = []; - for (const settingsGroup of settingsGroups) { - for (const settingsSection of settingsGroup.sections) { - for (const setting of settingsSection.settings) { - result.push(this.createDecoration(setting, model)); - } - } - } - return result; - } - - private createDecoration(setting: ISetting, model: editorCommon.IModel): editorCommon.IModelDeltaDecoration { - const jsonSchema: IJSONSchema = this.getConfigurationsMap()[setting.key]; - const maxColumn = model.getLineMaxColumn(setting.range.startLineNumber + 1); - const range = { - startLineNumber: setting.range.startLineNumber + 1, - startColumn: maxColumn, - endLineNumber: setting.range.startLineNumber + 1, - endColumn: maxColumn - }; - return { - range, options: { - afterContentClassName: `copySetting${(jsonSchema.enum || jsonSchema.type === 'boolean') ? '.select' : ''}`, - } - }; - } - - private onEditorMouseUp(e: IEditorMouseEvent): void { - let range = e.target.range; - if (!range || !range.isEmpty) { - return; - } - if (!e.event.leftButton) { - return; - } - - switch (e.target.type) { - case editorCommon.MouseTargetType.CONTENT_EMPTY: - if (hasClass(e.target.element, 'copySetting')) { - this.onClick(e); - } - return; - default: - return; - } - } - - private getConfigurationsMap(): { [qualifiedKey: string]: IJSONSchema } { - return Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); - } - - private onClick(e: IEditorMouseEvent) { - const model = this.editor.getModel(); - const setting = parse('{' + model.getLineContent(e.target.range.startLineNumber) + '}'); - const key = Object.keys(setting)[0]; - let value = setting[key]; - let jsonSchema: IJSONSchema = this.getConfigurationsMap()[key]; - const actions = this.getActions(key, jsonSchema); - if (actions) { - let elementPosition = getDomNodePagePosition(e.target.element); - const anchor = { x: elementPosition.left + elementPosition.width, y: elementPosition.top + elementPosition.height + 10 }; - this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, - getActions: () => TPromise.wrap(actions) - }); - return; - } - this.settingsService.copyConfiguration({ key, value }); - } - - private getActions(key: string, jsonSchema: IJSONSchema): IAction[] { - if (jsonSchema.type === 'boolean') { - return [{ - id: 'truthyValue', - label: 'true', - enabled: true, - run: () => this.settingsService.copyConfiguration({ key, value: true }) - }, { - id: 'falsyValue', - label: 'false', - enabled: true, - run: () => this.settingsService.copyConfiguration({ key, value: false }) - }]; - } - if (jsonSchema.enum) { - return jsonSchema.enum.map(value => { - return { - id: value, - label: value, - enabled: true, - run: () => this.settingsService.copyConfiguration({ key, value }) - }; - }); - } - return null; - } } \ No newline at end of file diff --git a/src/vs/workbench/parts/settings/browser/openSettingsService.ts b/src/vs/workbench/parts/settings/browser/openSettingsService.ts index 57b1b204601..036effe15bc 100644 --- a/src/vs/workbench/parts/settings/browser/openSettingsService.ts +++ b/src/vs/workbench/parts/settings/browser/openSettingsService.ts @@ -8,8 +8,13 @@ import * as nls from 'vs/nls'; import URI from 'vs/base/common/uri'; import * as labels from 'vs/base/common/labels'; import { Delayer } from 'vs/base/common/async'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { parseTree, findNodeAtLocation } from 'vs/base/common/json'; +import { Disposable, dispose } from 'vs/base/common/lifecycle'; +import { JSONVisitor, visit, parse, parseTree, findNodeAtLocation } from 'vs/base/common/json'; +import { Registry } from 'vs/platform/platform'; +import { hasClass, getDomNodePagePosition } from 'vs/base/browser/dom'; +import { IAction } from 'vs/base/common/actions'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; +import { Extensions } from 'vs/workbench/common/actionRegistry'; import { asFileEditorInput } from 'vs/workbench/common/editor'; import { StringEditorInput } from 'vs/workbench/common/editor/stringEditorInput'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -20,19 +25,18 @@ import { IEditorGroupService } from 'vs/workbench/services/group/common/groupSer import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IFileService, IFileOperationResult, FileOperationResult } from 'vs/platform/files/common/files'; import { IMessageService, Severity, IChoiceService } from 'vs/platform/message/common/message'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ICodeEditor, IEditorMouseEvent } from 'vs/editor/browser/editorBrowser'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { IConfigurationEditingService, ConfigurationTarget, IConfigurationValue } from 'vs/workbench/services/configuration/common/configurationEditing'; -import { IOpenSettingsService, IDefaultSettings, IDefaultKeybindings } from 'vs/workbench/parts/settings/common/openSettings'; -import { DefaultSettings, DefaultKeybindings } from 'vs/workbench/parts/settings/common/defaultSettings'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ITextModelContentProvider } from 'vs/platform/textmodelResolver/common/resolver'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IOpenSettingsService } from 'vs/workbench/parts/settings/common/openSettings'; import { DefaultSettingsInput, DefaultKeybindingsInput } from 'vs/workbench/parts/settings/browser/defaultSettingsEditors'; - +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const SETTINGS_INFO_IGNORE_KEY = 'settings.workspace.info.ignore'; @@ -49,9 +53,9 @@ export class OpenSettingsService extends Disposable implements IOpenSettingsServ _serviceBrand: any; private configurationTarget: ConfigurationTarget = null; - - private _defaultSettings: IDefaultSettings; - private _defaultKeybindings: IDefaultKeybindings; + private defaultSettingsActionsRenderer: SettingsActionsRenderer; + private userSettingsActionsRenderer: SettingsActionsRenderer; + private workspaceSettingsActionsRenderer: SettingsActionsRenderer; constructor( @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @@ -61,6 +65,7 @@ export class OpenSettingsService extends Disposable implements IOpenSettingsServ @IMessageService private messageService: IMessageService, @IChoiceService private choiceService: IChoiceService, @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IKeybindingService private keybindingService: IKeybindingService, @IInstantiationService private instantiationService: IInstantiationService, @IStorageService private storageService: IStorageService, @IEnvironmentService private environmentService: IEnvironmentService, @@ -82,20 +87,6 @@ export class OpenSettingsService extends Disposable implements IOpenSettingsServ })); } - public get defaultSettings(): IDefaultSettings { - if (!this._defaultSettings) { - this._defaultSettings = this.instantiationService.createInstance(DefaultSettings); - } - return this._defaultSettings; - } - - public get defaultKeybindings(): IDefaultKeybindings { - if (!this._defaultKeybindings) { - this._defaultKeybindings = this.instantiationService.createInstance(DefaultKeybindings); - } - return this._defaultKeybindings; - } - openGlobalSettings(): TPromise { if (this.configurationService.hasWorkspaceConfiguration() && !this.storageService.getBoolean(SETTINGS_INFO_IGNORE_KEY, StorageScope.WORKSPACE)) { this.promptToOpenWorkspaceSettings(); @@ -114,7 +105,7 @@ export class OpenSettingsService extends Disposable implements IOpenSettingsServ openGlobalKeybindingSettings(): TPromise { const emptyContents = '// ' + nls.localize('emptyKeybindingsHeader', "Place your key bindings in this file to overwrite the defaults") + '\n[\n]'; - return this.openTwoEditors(DefaultKeybindingsInput.getInstance(this.instantiationService, this), URI.file(this.environmentService.appKeybindingsPath), emptyContents).then(() => null); + return this.openTwoEditors(DefaultKeybindingsInput.getInstance(this.instantiationService, this.keybindingService), URI.file(this.environmentService.appKeybindingsPath), emptyContents).then(() => null); } openEditableSettings(configurationTarget: ConfigurationTarget, showVisibleEditor: boolean = false): TPromise { @@ -135,22 +126,6 @@ export class OpenSettingsService extends Disposable implements IOpenSettingsServ })); } - public copyConfiguration(configurationValue: IConfigurationValue): void { - this.telemetryService.publicLog('defaultSettingsActions.copySetting', { userConfigurationKeys: [configurationValue.key] }); - this.openEditableSettings(this.configurationTarget, true).then(editor => { - const editorControl = editor.getControl(); - const disposable = editorControl.onDidChangeModelContent(() => { - new Delayer(100).trigger((): any => { - editorControl.focus(); - editorControl.setSelection(this.getSelectionRange(configurationValue.key, editorControl.getModel())); - }); - disposable.dispose(); - }); - this.configurationEditingService.writeConfiguration(this.configurationTarget, configurationValue) - .then(null, error => this.messageService.show(Severity.Error, error)); - }); - } - private isEditorFor(editor: IEditor, configurationTarget: ConfigurationTarget): boolean { const fileEditorInput = asFileEditorInput(editor.input); return !!fileEditorInput && fileEditorInput.getResource().fsPath === this.getEditableSettingsURI(configurationTarget).fsPath; @@ -202,7 +177,8 @@ export class OpenSettingsService extends Disposable implements IOpenSettingsServ if (openDefaultSettings) { const emptySettingsContents = this.getEmptyEditableSettingsContent(configurationTarget); const settingsResource = this.getEditableSettingsURI(configurationTarget); - return this.openTwoEditors(DefaultSettingsInput.getInstance(this.instantiationService, this), settingsResource, emptySettingsContents).then(() => null); + return this.openTwoEditors(DefaultSettingsInput.getInstance(this.instantiationService, this.configurationService), settingsResource, emptySettingsContents) + .then(editors => this.renderActionsForDefaultSettings(editors[0])); } return this.openEditableSettings(configurationTarget).then(() => null); } @@ -241,6 +217,61 @@ export class OpenSettingsService extends Disposable implements IOpenSettingsServ resource.fsPath === this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE).fsPath ? ConfigurationTarget.WORKSPACE : null; } + private renderActionsForDefaultSettings(defaultSettingsEditor: IEditor) { + const defaultSettingsEditorControl = defaultSettingsEditor.getControl(); + if (!this.defaultSettingsActionsRenderer) { + this.defaultSettingsActionsRenderer = this.instantiationService.createInstance(SettingsActionsRenderer, defaultSettingsEditorControl, this.copyConfiguration.bind(this)); + const disposable = defaultSettingsEditorControl.getModel().onWillDispose(() => { + this.defaultSettingsActionsRenderer.dispose(); + this.defaultSettingsActionsRenderer = null; + dispose(disposable); + }); + } + this.defaultSettingsActionsRenderer.render(); + } + + protected renderActionsForUserSettingsEditor(settingsEditor: IEditor) { + const settingsEditorControl = settingsEditor.getControl(); + if (!this.userSettingsActionsRenderer) { + this.userSettingsActionsRenderer = this.instantiationService.createInstance(SettingsActionsRenderer, settingsEditorControl, this.copyConfiguration.bind(this)); + const disposable = settingsEditorControl.getModel().onWillDispose(() => { + this.userSettingsActionsRenderer.dispose(); + this.userSettingsActionsRenderer = null; + dispose(disposable); + }); + } + this.userSettingsActionsRenderer.render(); + } + + protected renderActionsForWorkspaceSettingsEditor(settingsEditor: IEditor) { + const settingsEditorControl = settingsEditor.getControl(); + if (!this.workspaceSettingsActionsRenderer) { + this.workspaceSettingsActionsRenderer = this.instantiationService.createInstance(SettingsActionsRenderer, settingsEditorControl, this.copyConfiguration.bind(this)); + const disposable = settingsEditorControl.getModel().onWillDispose(() => { + this.workspaceSettingsActionsRenderer.dispose(); + this.workspaceSettingsActionsRenderer = null; + dispose(disposable); + }); + } + this.workspaceSettingsActionsRenderer.render(); + } + + private copyConfiguration(configurationValue: IConfigurationValue) { + this.telemetryService.publicLog('defaultSettingsActions.copySetting', { userConfigurationKeys: [configurationValue.key] }); + this.openEditableSettings(this.configurationTarget, true).then(editor => { + const editorControl = editor.getControl(); + const disposable = editorControl.onDidChangeModelContent(() => { + new Delayer(100).trigger((): any => { + editorControl.focus(); + editorControl.setSelection(this.getSelectionRange(configurationValue.key, editorControl.getModel())); + }); + disposable.dispose(); + }); + this.configurationEditingService.writeConfiguration(this.configurationTarget, configurationValue) + .then(null, error => this.messageService.show(Severity.Error, error)); + }); + } + private getSelectionRange(setting: string, model: editorCommon.IModel): editorCommon.IRange { const tree = parseTree(model.getValue()); const node = findNodeAtLocation(tree, [setting]); @@ -254,28 +285,155 @@ export class OpenSettingsService extends Disposable implements IOpenSettingsServ } } -export class SettingsContentProvider implements ITextModelContentProvider { +class SettingsActionsRenderer extends Disposable { - constructor( - @IOpenSettingsService private openSettingsService: IOpenSettingsService, - @IModelService private modelService: IModelService, - @IModeService private modeService: IModeService + private decorationIds: string[] = []; + + constructor(private settingsEditor: ICodeEditor, + private copyConfiguration: (configurationValue: IConfigurationValue) => void, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IConfigurationService private configurationService: IConfigurationService, + @IEnvironmentService private environmentService: IEnvironmentService, + @IContextMenuService private contextMenuService: IContextMenuService, + @IMessageService private messageService: IMessageService ) { + super(); + this._register(this.settingsEditor.onMouseUp(e => this.onEditorMouseUp(e))); + this._register(this.settingsEditor.getModel().onDidChangeContent(() => this.render())); + } + + public render(): void { + const defaultSettingsModel = this.settingsEditor.getModel(); + if (defaultSettingsModel) { + defaultSettingsModel.changeDecorations(changeAccessor => { + this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, []); + }); + defaultSettingsModel.changeDecorations(changeAccessor => { + this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, this.createDecorations()); + }); + } + } + + private createDecorations(): editorCommon.IModelDeltaDecoration[] { + const settingsModel = this.settingsEditor.getModel(); + let result: editorCommon.IModelDeltaDecoration[] = []; + let parsingConfigurations = false; + let parsingConfiguration = false; + let visitor: JSONVisitor = { + onObjectBegin: (offset: number, length: number) => { + if (parsingConfigurations) { + parsingConfiguration = true; + } else { + parsingConfigurations = true; + } + }, + onObjectProperty: (property: string, offset: number, length: number) => { + if (!parsingConfiguration) { + result.push(this.createDecoration(property, offset, settingsModel)); + } + }, + onObjectEnd: () => { + if (parsingConfiguration) { + parsingConfiguration = false; + } else { + parsingConfigurations = false; + } + }, + }; + visit(settingsModel.getValue(), visitor); + return result; + } + + private createDecoration(property: string, offset: number, model: editorCommon.IModel): editorCommon.IModelDeltaDecoration { + const jsonSchema: IJSONSchema = this.getConfigurationsMap()[property]; + const position = model.getPositionAt(offset); + const maxColumn = model.getLineMaxColumn(position.lineNumber); + const range = { + startLineNumber: position.lineNumber, + startColumn: maxColumn, + endLineNumber: position.lineNumber, + endColumn: maxColumn + }; + return { + range, options: { + afterContentClassName: `copySetting${(jsonSchema.enum || jsonSchema.type === 'boolean') ? '.select' : ''}`, + } + }; + } + + private onEditorMouseUp(e: IEditorMouseEvent): void { + let range = e.target.range; + if (!range || !range.isEmpty) { + return; + } + if (!e.event.leftButton) { + return; + } + + switch (e.target.type) { + case editorCommon.MouseTargetType.CONTENT_EMPTY: + if (hasClass(e.target.element, 'copySetting')) { + this.onClick(e); + } + return; + default: + return; + } + } + + private getConfigurationsMap(): { [qualifiedKey: string]: IJSONSchema } { + return Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); } - public provideTextContent(uri: URI): TPromise { - if (uri.scheme !== 'vscode') { - return null; + private onClick(e: IEditorMouseEvent) { + const model = this.settingsEditor.getModel(); + const setting = parse('{' + model.getLineContent(e.target.range.startLineNumber) + '}'); + const key = Object.keys(setting)[0]; + let value = setting[key]; + let jsonSchema: IJSONSchema = this.getConfigurationsMap()[key]; + const actions = this.getActions(key, jsonSchema); + if (actions) { + let elementPosition = getDomNodePagePosition(e.target.element); + const anchor = { x: elementPosition.left + elementPosition.width, y: elementPosition.top + elementPosition.height + 10 }; + this.contextMenuService.showContextMenu({ + getAnchor: () => anchor, + getActions: () => TPromise.wrap(actions) + }); + return; } - const defaultSettings = this.openSettingsService.defaultSettings; - if (defaultSettings.uri.fsPath === uri.fsPath) { - let mode = this.modeService.getOrCreateMode('application/json'); - return TPromise.as(this.modelService.createModel(defaultSettings.content, mode, uri)); + this.copyConfiguration({ key, value }); + } + + private getActions(key: string, jsonSchema: IJSONSchema): IAction[] { + if (jsonSchema.type === 'boolean') { + return [{ + id: 'truthyValue', + label: 'true', + enabled: true, + run: () => { + this.copyConfiguration({ key, value: true }); + } + }, { + id: 'falsyValue', + label: 'false', + enabled: true, + run: () => { + this.copyConfiguration({ key, value: false }); + } + }]; } - const defaultKeybindings = this.openSettingsService.defaultKeybindings; - if (defaultKeybindings.uri.fsPath === uri.fsPath) { - let mode = this.modeService.getOrCreateMode('application/json'); - return TPromise.as(this.modelService.createModel(defaultKeybindings.content, mode, uri)); + if (jsonSchema.enum) { + return jsonSchema.enum.map(value => { + return { + id: value, + label: value, + enabled: true, + run: () => { + this.copyConfiguration({ key, value }); + } + }; + }); } return null; } diff --git a/src/vs/workbench/parts/settings/common/defaultSettings.ts b/src/vs/workbench/parts/settings/common/defaultSettings.ts deleted file mode 100644 index 33bb7a81352..00000000000 --- a/src/vs/workbench/parts/settings/common/defaultSettings.ts +++ /dev/null @@ -1,156 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import * as network from 'vs/base/common/network'; -import * as strings from 'vs/base/common/strings'; -import URI from 'vs/base/common/uri'; -import { Registry } from 'vs/platform/platform'; -import { IConfigurationNode, IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { IDefaultSettings, IDefaultKeybindings, ISettingsGroup } from 'vs/workbench/parts/settings/common/openSettings'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; - -export class DefaultSettings implements IDefaultSettings { - - private _uri: URI = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/settings.json' }); - private _content: string; - private _settingsGroups: ISettingsGroup[]; - private indent: string; - - constructor( @IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService) { - const editorConfig = this.configurationService.getConfiguration(); - this.indent = editorConfig.editor.insertSpaces ? strings.repeat(' ', editorConfig.editor.tabSize) : '\t'; - - const configurations = Registry.as(Extensions.Configuration).getConfigurations(); - this._settingsGroups = configurations.sort(this.compareConfigurationNodes).reduce((result, config) => this.parseConfig(config, result), []); - this._content = this.toContent(); - } - - public get uri(): URI { - return this._uri; - } - - public get content(): string { - return this._content; - } - - public getSettingsGroups(): ISettingsGroup[] { - return this._settingsGroups; - } - - private parseConfig(config: IConfigurationNode, result: ISettingsGroup[], settingsGroup?: ISettingsGroup): ISettingsGroup[] { - if (config.title) { - if (!settingsGroup) { - settingsGroup = result.filter(g => g.title === config.title)[0]; - if (!settingsGroup) { - settingsGroup = { sections: [{ settings: [] }], title: config.title }; - result.push(settingsGroup); - } - } else { - settingsGroup.sections[settingsGroup.sections.length - 1].description = config.title; - } - } - if (config.properties) { - if (!settingsGroup) { - settingsGroup = { sections: [{ settings: [] }], title: config.id }; - result.push(settingsGroup); - } - const configurationSettings = Object.keys(config.properties).map((key) => { - const prop = config.properties[key]; - const value = prop.default; - const description = prop.description || ''; - return { key, value, description }; - }); - settingsGroup.sections[settingsGroup.sections.length - 1].settings.push(...configurationSettings); - } - if (config.allOf) { - config.allOf.forEach(c => this.parseConfig(c, result, settingsGroup)); - } - return result; - } - - private compareConfigurationNodes(c1: IConfigurationNode, c2: IConfigurationNode): number { - if (typeof c1.order !== 'number') { - return 1; - } - if (typeof c2.order !== 'number') { - return -1; - } - if (c1.order === c2.order) { - const title1 = c1.title || ''; - const title2 = c2.title || ''; - return title1.localeCompare(title2); - } - return c1.order - c2.order; - } - - private toContent(): string { - let defaultsHeader = '// ' + nls.localize('defaultSettingsHeader', "Overwrite settings by placing them into your settings file.\n"); - defaultsHeader += '// ' + nls.localize('defaultSettingsHeader2', "See http://go.microsoft.com/fwlink/?LinkId=808995 for the most commonly used settings.\n\n"); - - let lastEntry = -1; - const result: string[] = []; - result.push('{'); - let lineNumber = 4; // Beginning of settings - for (const group of this._settingsGroups) { - result.push('// ' + group.title); - lineNumber++; - group.range = { startLineNumber: lineNumber, startColumn: 1, endLineNumber: lineNumber, endColumn: 1 }; - for (const section of group.sections) { - if (section.description) { - result.push(this.indent + '// ' + section.description); - lineNumber++; - section.range = { startLineNumber: lineNumber, startColumn: 1, endLineNumber: lineNumber, endColumn: 1 }; - } - for (const setting of section.settings) { - result.push(this.indent + '// ' + setting.description); - lineNumber++; - const settingStart = lineNumber; - let valueString = JSON.stringify(setting.value, null, this.indent); - let valueLines = 1; - if (valueString && (typeof setting.value === 'object')) { - const mulitLineValue = valueString.split('\n'); - valueString = mulitLineValue.join('\n' + this.indent); - valueLines = mulitLineValue.length; - } - if (lastEntry !== -1) { - result[lastEntry] += ','; - } - lastEntry = result.length; - result.push(this.indent + JSON.stringify(setting.key) + ': ' + valueString); - lineNumber += valueLines; - setting.range = { startLineNumber: settingStart, startColumn: 1, endLineNumber: lineNumber, endColumn: 1 }; - result.push(''); - lineNumber++; - } - } - } - result.push('}'); - - return defaultsHeader + result.join('\n'); - } -} - -export class DefaultKeybindings implements IDefaultKeybindings { - - private _uri: URI; - private _content: string; - - constructor( @IKeybindingService keybindingService: IKeybindingService) { - this._uri = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/keybindings.json' }); - - const defaultsHeader = '// ' + nls.localize('defaultKeybindingsHeader', "Overwrite key bindings by placing them into your key bindings file."); - this._content = defaultsHeader + '\n' + keybindingService.getDefaultKeybindings(); - } - - public get uri(): URI { - return this._uri; - } - - public get content(): string { - return this._content; - } -} \ No newline at end of file diff --git a/src/vs/workbench/parts/settings/common/openSettings.ts b/src/vs/workbench/parts/settings/common/openSettings.ts index 561a135d164..92b6783d9ac 100644 --- a/src/vs/workbench/parts/settings/common/openSettings.ts +++ b/src/vs/workbench/parts/settings/common/openSettings.ts @@ -5,51 +5,14 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IRange } from 'vs/editor/common/editorCommon'; -import URI from 'vs/base/common/uri'; -import { IConfigurationValue } from 'vs/workbench/services/configuration/common/configurationEditing'; - -export interface ISettingsGroup { - range?: IRange; - title: string; - sections: ISettingsSection[]; -} - -export interface ISettingsSection { - range?: IRange; - description?: string; - settings: ISetting[]; -} - -export interface ISetting { - range?: IRange; - key: string; - value: any; - description?: string; -} - -export interface IDefaultSettings { - uri: URI; - content: string; - - getSettingsGroups(): ISettingsGroup[]; -} - -export interface IDefaultKeybindings { - uri: URI; - content: string; -} export const IOpenSettingsService = createDecorator('openSettingsService'); export interface IOpenSettingsService { _serviceBrand: any; - defaultSettings: IDefaultSettings; - defaultKeybindings: IDefaultKeybindings; - openGlobalSettings(): TPromise; openWorkspaceSettings(): TPromise; openGlobalKeybindingSettings(): TPromise; - copyConfiguration(configurationValue: IConfigurationValue): void; + } -- GitLab