diff --git a/src/vs/workbench/parts/preferences/browser/media/preferences.css b/src/vs/workbench/parts/preferences/browser/media/preferences.css index 6d074583c7f99f1de61bbcd453c1daf1a20a1c41..eee39c73482bc18c7d182b61a648184d9f325837 100644 --- a/src/vs/workbench/parts/preferences/browser/media/preferences.css +++ b/src/vs/workbench/parts/preferences/browser/media/preferences.css @@ -213,6 +213,10 @@ background: url('edit_inverse.svg') center center no-repeat; } +.monaco-editor .invalidSetting { + color: #b1b1b1; +} + .monaco-editor .floating-click-widget { padding: 10px; border-radius: 5px; diff --git a/src/vs/workbench/parts/preferences/browser/preferencesActions.ts b/src/vs/workbench/parts/preferences/browser/preferencesActions.ts index a528ab2d951e4c0608cf09e0f9d231498401f2df..ee92a54842fddef26cda3e3db8e25d56a432d030 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesActions.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesActions.ts @@ -12,6 +12,7 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IQuickOpenService, IPickOpenEntry, IFilePickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen'; import { IPreferencesService, getSettingsTargetName } from 'vs/workbench/parts/preferences/common/preferences'; import { IWorkspaceContextService } from "vs/platform/workspace/common/workspace"; +import { ConfigurationTarget } from "vs/workbench/services/configuration/common/configurationEditing"; export class OpenGlobalSettingsAction extends Action { @@ -106,7 +107,7 @@ export class OpenFolderSettingsAction extends Action { public run(): TPromise { const picks: IPickOpenEntry[] = this.workspaceContextService.getWorkspace().roots.map((root, index) => { return { - label: getSettingsTargetName(root, this.workspaceContextService), + label: getSettingsTargetName(ConfigurationTarget.FOLDER, root, this.workspaceContextService), id: `${index}` }; }); @@ -114,7 +115,7 @@ export class OpenFolderSettingsAction extends Action { return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickFolder', "Select Folder") }) .then(pick => { if (pick) { - return this.preferencesService.openSettings(this.workspaceContextService.getWorkspace().roots[parseInt(pick.id)]); + return this.preferencesService.openFolderSettings(this.workspaceContextService.getWorkspace().roots[parseInt(pick.id)]); } return undefined; }); diff --git a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts index 95644da856de4a3f10774289b04a8f2ca4ebf064..340b196705b53060b8d8ce85370aa1907179d0e9 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts @@ -43,7 +43,7 @@ import { ConfigurationTarget } from 'vs/workbench/services/configuration/common/ import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { VSash } from 'vs/base/browser/ui/sash/sash'; import { Widget } from 'vs/base/browser/ui/widget'; -import { IPreferencesRenderer, DefaultSettingsRenderer, UserSettingsRenderer, WorkspaceSettingsRenderer } from 'vs/workbench/parts/preferences/browser/preferencesRenderers'; +import { IPreferencesRenderer, DefaultSettingsRenderer, UserSettingsRenderer, WorkspaceSettingsRenderer, FolderSettingsRenderer } from 'vs/workbench/parts/preferences/browser/preferencesRenderers'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { getCodeEditor } from 'vs/editor/common/services/codeEditorService'; @@ -147,7 +147,7 @@ export class PreferencesEditor extends BaseEditor { this._register(this.searchWidget.onFocus(() => this.lastFocusedWidget = this.searchWidget)); this.lastFocusedWidget = this.searchWidget; - this.settingsTargetsWidget = this._register(this.instantiationService.createInstance(SettingsTargetsWidget, this.headerContainer, this.preferencesService.userSettingsResource)); + this.settingsTargetsWidget = this._register(this.instantiationService.createInstance(SettingsTargetsWidget, this.headerContainer, this.preferencesService.userSettingsResource, ConfigurationTarget.USER)); this._register(this.settingsTargetsWidget.onDidTargetChange(target => this.switchSettings(target))); const editorsContainer = DOM.append(parentElement, DOM.$('.preferences-editors-container')); @@ -211,7 +211,8 @@ export class PreferencesEditor extends BaseEditor { } private updateInput(oldInput: PreferencesEditorInput, newInput: PreferencesEditorInput, options?: EditorOptions): TPromise { - this.settingsTargetsWidget.setTarget(this.getSettingsConfigurationTarget(newInput)); + const resource = toResource(newInput.master); + this.settingsTargetsWidget.setTarget(resource, this.getSettingsConfigurationTarget(resource)); return this.sideBySidePreferencesWidget.setInput(newInput.details, newInput.master, options).then(({ defaultPreferencesRenderer, editablePreferencesRenderer }) => { this.preferencesRenderers.defaultPreferencesRenderer = defaultPreferencesRenderer; @@ -220,24 +221,26 @@ export class PreferencesEditor extends BaseEditor { }); } - private getSettingsConfigurationTarget(preferencesEditorInput: PreferencesEditorInput): ConfigurationTarget | URI { - const resource = toResource(preferencesEditorInput.master); + private getSettingsConfigurationTarget(resource: URI): ConfigurationTarget { if (this.preferencesService.userSettingsResource.fsPath === resource.fsPath) { return ConfigurationTarget.USER; } if (this.preferencesService.workspaceSettingsResource.fsPath === resource.fsPath) { return ConfigurationTarget.WORKSPACE; } - return this.workspaceContextService.getRoot(resource); + if (this.workspaceContextService.getRoot(resource)) { + return ConfigurationTarget.FOLDER; + } + return null; } - private switchSettings(target: ConfigurationTarget | URI): void { + private switchSettings(resource: URI): void { // Focus the editor if this editor is not active editor if (this.editorService.getActiveEditor() !== this) { this.focus(); } const promise = this.input.isDirty() ? this.input.save() : TPromise.as(true); - promise.done(value => this.preferencesService.switchSettings(target)); + promise.done(value => this.preferencesService.switchSettings(this.getSettingsConfigurationTarget(resource), resource)); } private filterPreferences(filter: string) { @@ -811,10 +814,14 @@ class SettingsEditorContribution extends Disposable implements ISettingsEditorCo this.preferencesRenderer = TPromise.join([this.preferencesService.createPreferencesEditorModel(this.preferencesService.defaultSettingsResource), this.preferencesService.createPreferencesEditorModel(this.editor.getModel().uri)]) .then(([defaultSettingsModel, settingsModel]) => { if (settingsModel instanceof SettingsEditorModel) { - if (ConfigurationTarget.USER === settingsModel.configurationTarget) { - return this.instantiationService.createInstance(UserSettingsRenderer, this.editor, settingsModel, defaultSettingsModel); + switch (settingsModel.configurationTarget) { + case ConfigurationTarget.USER: + return this.instantiationService.createInstance(UserSettingsRenderer, this.editor, settingsModel, defaultSettingsModel); + case ConfigurationTarget.WORKSPACE: + return this.instantiationService.createInstance(WorkspaceSettingsRenderer, this.editor, settingsModel, defaultSettingsModel); + case ConfigurationTarget.FOLDER: + return this.instantiationService.createInstance(FolderSettingsRenderer, this.editor, settingsModel, defaultSettingsModel); } - return this.instantiationService.createInstance(WorkspaceSettingsRenderer, this.editor, settingsModel, defaultSettingsModel); } return null; }) diff --git a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts index 76e6f1d4cc593289fc00721503353860e163ad2f..0756292a4a296884f9d44b294d305a25618f35da 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts @@ -13,7 +13,7 @@ import Event, { Emitter } from 'vs/base/common/event'; import { Registry } from 'vs/platform/registry/common/platform'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { Range, IRange } from 'vs/editor/common/core/range'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IPreferencesService, ISettingsGroup, ISetting, IPreferencesEditorModel, IFilterResult, ISettingsEditorModel } from 'vs/workbench/parts/preferences/common/preferences'; import { SettingsEditorModel, DefaultSettingsEditorModel } from 'vs/workbench/parts/preferences/common/preferencesModels'; @@ -94,7 +94,8 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend public updatePreference(key: string, value: any, source: ISetting): void { this.telemetryService.publicLog('defaultSettingsActions.copySetting', { userConfigurationKeys: [key] }); const overrideIdentifier = source.overrideOf ? overrideIdentifierFromKey(source.overrideOf.key) : null; - this.configurationEditingService.writeConfiguration(this.preferencesModel.configurationTarget, { key, value }, { donotSave: this.textFileService.isDirty(this.preferencesModel.uri), donotNotifyError: true, scopes: { overrideIdentifier } }) + const resource = this.preferencesModel.uri; + this.configurationEditingService.writeConfiguration(this.preferencesModel.configurationTarget, { key, value }, { donotSave: this.textFileService.isDirty(resource), donotNotifyError: true, scopes: { overrideIdentifier, resource } }) .then(() => this.onSettingUpdated(source), error => { this.messageService.show(Severity.Error, this.toErrorMessage(error, this.preferencesModel.configurationTarget)); }); @@ -183,6 +184,28 @@ export class WorkspaceSettingsRenderer extends UserSettingsRenderer implements I } } +export class FolderSettingsRenderer extends UserSettingsRenderer implements IPreferencesRenderer { + + private unsupportedWorkbenchSettingsRenderer: UnsupportedWorkbenchSettingsRenderer; + + constructor(editor: ICodeEditor, preferencesModel: SettingsEditorModel, associatedPreferencesModel: IPreferencesEditorModel, + @IPreferencesService preferencesService: IPreferencesService, + @ITelemetryService telemetryService: ITelemetryService, + @ITextFileService textFileService: ITextFileService, + @IConfigurationEditingService configurationEditingService: IConfigurationEditingService, + @IMessageService messageService: IMessageService, + @IInstantiationService instantiationService: IInstantiationService + ) { + super(editor, preferencesModel, associatedPreferencesModel, preferencesService, telemetryService, textFileService, configurationEditingService, messageService, instantiationService); + this.unsupportedWorkbenchSettingsRenderer = this._register(instantiationService.createInstance(UnsupportedWorkbenchSettingsRenderer, editor, preferencesModel)); + } + + public render(): void { + super.render(); + this.unsupportedWorkbenchSettingsRenderer.render(); + } +} + export class DefaultSettingsRenderer extends Disposable implements IPreferencesRenderer { private _associatedPreferencesModel: IPreferencesEditorModel; @@ -929,4 +952,62 @@ class UnsupportedWorkspaceSettingsRenderer extends Disposable { this.markerService.remove('preferencesEditor', [this.workspaceSettingsEditorModel.uri]); super.dispose(); } +} + +class UnsupportedWorkbenchSettingsRenderer extends Disposable { + + private decorationIds: string[] = []; + + constructor(private editor: editorCommon.ICommonCodeEditor, private workspaceSettingsEditorModel: SettingsEditorModel, + @IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService, + @IMarkerService private markerService: IMarkerService + ) { + super(); + this._register(this.configurationService.onDidUpdateConfiguration(() => this.render())); + } + + public render(): void { + this.editor.changeDecorations(changeAccessor => this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, [])); + + const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); + const folderKeys = this.configurationService.keys({ resource: this.workspaceSettingsEditorModel.uri }).folder; + const workbenchKeys = folderKeys.filter(key => configurationRegistry[key] && configurationRegistry[key].scope === ConfigurationScope.WORKBENCH); + if (workbenchKeys.length) { + const ranges: IRange[] = []; + for (const unsupportedKey of workbenchKeys) { + const setting = this.workspaceSettingsEditorModel.getPreference(unsupportedKey); + if (setting) { + ranges.push({ + startLineNumber: setting.keyRange.startLineNumber, + startColumn: setting.keyRange.startColumn - 1, + endLineNumber: setting.valueRange.endLineNumber, + endColumn: setting.valueRange.endColumn + }); + } + } + this.editor.changeDecorations(changeAccessor => this.decorationIds = changeAccessor.deltaDecorations(this.decorationIds, ranges.map(range => this.createDecoration(range, this.editor.getModel())))); + } + } + + private static _INVALID_SETTING_ = ModelDecorationOptions.register({ + stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + inlineClassName: 'invalidSetting', + hoverMessage: nls.localize('unsupportedWorkbenchSetting', "This setting is a workbench setting and cannot be applied for resources under folder") + }); + + private createDecoration(range: IRange, model: editorCommon.IModel): editorCommon.IModelDeltaDecoration { + return { + range, + options: UnsupportedWorkbenchSettingsRenderer._INVALID_SETTING_ + }; + } + + public dispose(): void { + if (this.decorationIds) { + this.decorationIds = this.editor.changeDecorations(changeAccessor => { + return changeAccessor.deltaDecorations(this.decorationIds, []); + }); + } + super.dispose(); + } } \ No newline at end of file diff --git a/src/vs/workbench/parts/preferences/browser/preferencesService.ts b/src/vs/workbench/parts/preferences/browser/preferencesService.ts index 10f314e0a27c8ec0198214ea1b290f7edb4fc50a..9b80dabbbe1b840f06d5f615db2e39b75257e165 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesService.ts @@ -146,29 +146,29 @@ export class PreferencesService extends Disposable implements IPreferencesServic } if (this.workspaceConfigSettingsResource.fsPath === uri.fsPath) { - promise = this.createEditableSettingsEditorModel(ConfigurationTarget.WORKSPACE); + promise = this.createEditableSettingsEditorModel(ConfigurationTarget.WORKSPACE, uri); this.defaultPreferencesEditorModels.set(uri, promise); return promise; } if (this.getEditableSettingsURI(ConfigurationTarget.USER).fsPath === uri.fsPath) { - return this.createEditableSettingsEditorModel(ConfigurationTarget.USER); + return this.createEditableSettingsEditorModel(ConfigurationTarget.USER, uri); } const workspaceSettingsUri = this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE); if (workspaceSettingsUri && workspaceSettingsUri.fsPath === uri.fsPath) { - return this.createEditableSettingsEditorModel(ConfigurationTarget.WORKSPACE); + return this.createEditableSettingsEditorModel(ConfigurationTarget.WORKSPACE, workspaceSettingsUri); } - return TPromise.wrap>(null); - } + if (this.contextService.hasMultiFolderWorkspace()) { + return this.createEditableSettingsEditorModel(ConfigurationTarget.FOLDER, uri); + } - openSettings(target: ConfigurationTarget | URI): TPromise { - return this.doOpenSettings(target); + return TPromise.wrap>(null); } openGlobalSettings(): TPromise { - return this.doOpenSettings(ConfigurationTarget.USER); + return this.doOpenSettings(ConfigurationTarget.USER, this.userSettingsResource); } openWorkspaceSettings(): TPromise { @@ -176,16 +176,20 @@ export class PreferencesService extends Disposable implements IPreferencesServic this.messageService.show(Severity.Info, nls.localize('openFolderFirst', "Open a folder first to create workspace settings")); return TPromise.as(null); } - return this.doOpenSettings(ConfigurationTarget.WORKSPACE); + return this.doOpenSettings(ConfigurationTarget.WORKSPACE, this.workspaceSettingsResource); } - switchSettings(target: URI | ConfigurationTarget): TPromise { + openFolderSettings(folder: URI): TPromise { + return this.doOpenSettings(ConfigurationTarget.FOLDER, this.getEditableSettingsURI(ConfigurationTarget.FOLDER, folder)); + } + + switchSettings(target: ConfigurationTarget, resource: URI): TPromise { const activeEditor = this.editorService.getActiveEditor(); const activeEditorInput = activeEditor.input; if (activeEditorInput instanceof PreferencesEditorInput) { - return this.getOrCreateEditableSettingsEditorInput(target) + return this.getOrCreateEditableSettingsEditorInput(target, this.getEditableSettingsURI(target, resource)) .then(toInput => { - const replaceWith = new PreferencesEditorInput(this.getPreferencesEditorInputName(target), toInput.getDescription(), this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.defaultSettingsResource), toInput); + const replaceWith = new PreferencesEditorInput(this.getPreferencesEditorInputName(target, resource), toInput.getDescription(), this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.defaultSettingsResource), toInput); return this.editorService.replaceEditors([{ toReplace: this.lastOpenedSettingsInput, replaceWith @@ -194,7 +198,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic }); }); } else { - this.openSettings(target); + this.doOpenSettings(target, resource); return undefined; } } @@ -231,13 +235,13 @@ export class PreferencesService extends Disposable implements IPreferencesServic }); } - private doOpenSettings(configurationTarget: ConfigurationTarget | URI): TPromise { + private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI): TPromise { const openDefaultSettings = !!this.configurationService.getConfiguration().workbench.settings.openDefaultSettings; - return this.getOrCreateEditableSettingsEditorInput(configurationTarget) + return this.getOrCreateEditableSettingsEditorInput(configurationTarget, resource) .then(editableSettingsEditorInput => { if (openDefaultSettings) { const defaultPreferencesEditorInput = this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.defaultSettingsResource); - const preferencesEditorInput = new PreferencesEditorInput(this.getPreferencesEditorInputName(configurationTarget), editableSettingsEditorInput.getDescription(), defaultPreferencesEditorInput, editableSettingsEditorInput); + const preferencesEditorInput = new PreferencesEditorInput(this.getPreferencesEditorInputName(configurationTarget, resource), editableSettingsEditorInput.getDescription(), defaultPreferencesEditorInput, editableSettingsEditorInput); this.lastOpenedSettingsInput = preferencesEditorInput; return this.editorService.openEditor(preferencesEditorInput, { pinned: true }); } @@ -245,22 +249,18 @@ export class PreferencesService extends Disposable implements IPreferencesServic }); } - private getPreferencesEditorInputName(target: ConfigurationTarget | URI): string { - const name = getSettingsTargetName(target, this.contextService); - return target instanceof URI ? nls.localize('folderSettingsName', "{0} (Folder Settings)", name) : name; + private getPreferencesEditorInputName(target: ConfigurationTarget, resource: URI): string { + const name = getSettingsTargetName(target, resource, this.contextService); + return target === ConfigurationTarget.FOLDER ? nls.localize('folderSettingsName', "{0} (Folder Settings)", name) : name; } - private getOrCreateEditableSettingsEditorInput(target: ConfigurationTarget | URI): TPromise { - const resource = this.getEditableSettingsURI(target); - if (resource) { - return this.createSettingsIfNotExists(target) - .then(() => this.editorService.createInput({ resource })); - } - return TPromise.wrapError(new Error('Unknown target ' + (target instanceof URI ? target.toString(false) : target.toString()))); + private getOrCreateEditableSettingsEditorInput(target: ConfigurationTarget, resource: URI): TPromise { + return this.createSettingsIfNotExists(target, resource) + .then(() => this.editorService.createInput({ resource })); } - private createEditableSettingsEditorModel(configurationTarget: ConfigurationTarget): TPromise { - const settingsUri = this.getEditableSettingsURI(configurationTarget); + private createEditableSettingsEditorModel(configurationTarget: ConfigurationTarget, resource: URI): TPromise { + const settingsUri = this.getEditableSettingsURI(configurationTarget, resource); if (settingsUri) { if (settingsUri.fsPath === this.workspaceConfigSettingsResource.fsPath) { return TPromise.join([this.textModelResolverService.createModelReference(settingsUri), this.textModelResolverService.createModelReference(this.contextService.getWorkspace().configuration)]) @@ -285,7 +285,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return TPromise.as(null); } - private getEmptyEditableSettingsContent(target: ConfigurationTarget | URI): string { + private getEmptyEditableSettingsContent(target: ConfigurationTarget): string { if (target === ConfigurationTarget.USER) { const emptySettingsHeader = nls.localize('emptySettingsHeader', "Place your settings in this file to overwrite the default settings"); return '// ' + emptySettingsHeader + '\n{\n}'; @@ -297,11 +297,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic ].join('\n'); } - private getEditableSettingsURI(configurationTarget: ConfigurationTarget | URI): URI { - if (configurationTarget instanceof URI) { - const root = this.contextService.getRoot(configurationTarget); - return root ? this.toResource(paths.join('.vscode', 'settings.json'), root) : null; - } + private getEditableSettingsURI(configurationTarget: ConfigurationTarget, resource?: URI): URI { switch (configurationTarget) { case ConfigurationTarget.USER: return URI.file(this.environmentService.appSettingsPath); @@ -314,6 +310,9 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.workspaceConfigSettingsResource; } return null; + case ConfigurationTarget.FOLDER: + const root = this.contextService.getRoot(resource); + return root ? this.toResource(paths.join('.vscode', 'settings.json'), root) : null; } return null; } @@ -322,8 +321,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return URI.file(paths.join(root.fsPath, relativePath)); } - private createSettingsIfNotExists(target: ConfigurationTarget | URI): TPromise { - const resource = this.getEditableSettingsURI(target); + private createSettingsIfNotExists(target: ConfigurationTarget, resource: URI): TPromise { if (this.contextService.hasMultiFolderWorkspace() && target === ConfigurationTarget.WORKSPACE) { return TPromise.as(null); } diff --git a/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts index 94abdd28880a1cb0f1e0f7f529029189d9e62530..bce0560ebaa97c10af329467fd4adceb765507f9 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts @@ -257,12 +257,12 @@ export class SettingsTargetsWidget extends Widget { private targetLabel: HTMLSelectElement; private targetDetails: HTMLSelectElement; - private _onDidTargetChange: Emitter = new Emitter(); - public readonly onDidTargetChange: Event = this._onDidTargetChange.event; + private _onDidTargetChange: Emitter = new Emitter(); + public readonly onDidTargetChange: Event = this._onDidTargetChange.event; private borderColor: Color; - constructor(parent: HTMLElement, private target: ConfigurationTarget | URI, + constructor(parent: HTMLElement, private uri: URI, private target: ConfigurationTarget, @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, @IPreferencesService private preferencesService: IPreferencesService, @IContextMenuService private contextMenuService: IContextMenuService, @@ -276,7 +276,8 @@ export class SettingsTargetsWidget extends Widget { })); } - public setTarget(target: ConfigurationTarget | URI): void { + public setTarget(uri: URI, target: ConfigurationTarget): void { + this.uri = uri; this.target = target; this.updateLabel(); } @@ -290,7 +291,7 @@ export class SettingsTargetsWidget extends Widget { this.targetDetails = DOM.append(targetElement, DOM.$('.settings-target-details')); this.updateLabel(); - this.onclick(this.settingsTargetsContainer, e => this.showContextMennu(e)); + this.onclick(this.settingsTargetsContainer, e => this.showContextMenu(e)); DOM.append(this.settingsTargetsContainer, DOM.$('.settings-target-dropdown-icon.octicon.octicon-triangle-down')); @@ -298,13 +299,13 @@ export class SettingsTargetsWidget extends Widget { } private updateLabel(): void { - this.targetLabel.textContent = getSettingsTargetName(this.target, this.workspaceContextService); - const details = this.target instanceof URI ? localize('folderSettingsDetails', "Folder Settings") : ''; + this.targetLabel.textContent = getSettingsTargetName(this.target, this.uri, this.workspaceContextService); + const details = ConfigurationTarget.FOLDER === this.target ? localize('folderSettingsDetails', "Folder Settings") : ''; this.targetDetails.textContent = details; DOM.toggleClass(this.targetDetails, 'empty', !details); } - private showContextMennu(event: IMouseEvent): void { + private showContextMenu(event: IMouseEvent): void { const actions = this.getSettingsTargetsActions(); this.contextMenuService.showContextMenu({ getAnchor: () => this.settingsTargetsContainer, @@ -316,21 +317,23 @@ export class SettingsTargetsWidget extends Widget { private getSettingsTargetsActions(): IAction[] { const actions: IAction[] = []; + const userSettingsResource = this.preferencesService.userSettingsResource; actions.push({ id: 'userSettingsTarget', - label: getSettingsTargetName(ConfigurationTarget.USER, this.workspaceContextService), - checked: this.target === ConfigurationTarget.USER, + label: getSettingsTargetName(ConfigurationTarget.USER, userSettingsResource, this.workspaceContextService), + checked: this.uri.fsPath === userSettingsResource.fsPath, enabled: true, - run: () => this.onTargetClicked(ConfigurationTarget.USER) + run: () => this.onTargetClicked(userSettingsResource) }); if (this.workspaceContextService.hasWorkspace()) { + const workspaceSettingsResource = this.preferencesService.workspaceSettingsResource; actions.push({ id: 'workspaceSettingsTarget', - label: getSettingsTargetName(ConfigurationTarget.WORKSPACE, this.workspaceContextService), - checked: this.target === ConfigurationTarget.WORKSPACE, + label: getSettingsTargetName(ConfigurationTarget.WORKSPACE, workspaceSettingsResource, this.workspaceContextService), + checked: this.uri.fsPath === workspaceSettingsResource.fsPath, enabled: true, - run: () => this.onTargetClicked(ConfigurationTarget.WORKSPACE) + run: () => this.onTargetClicked(workspaceSettingsResource) }); } @@ -339,8 +342,8 @@ export class SettingsTargetsWidget extends Widget { actions.push(...this.workspaceContextService.getWorkspace().roots.map((root, index) => { return { id: 'folderSettingsTarget' + index, - label: getSettingsTargetName(root, this.workspaceContextService), - checked: this.target instanceof URI && this.target.fsPath === root.fsPath, + label: getSettingsTargetName(ConfigurationTarget.FOLDER, root, this.workspaceContextService), + checked: this.uri instanceof URI && this.uri.fsPath === root.fsPath, enabled: true, run: () => this.onTargetClicked(root) }; @@ -350,11 +353,8 @@ export class SettingsTargetsWidget extends Widget { return actions; } - private onTargetClicked(target: ConfigurationTarget | URI): void { - if (this.target instanceof URI && target instanceof URI && this.target.fsPath === target.fsPath) { - return; - } - if (this.target === target) { + private onTargetClicked(target: URI): void { + if (this.uri.fsPath === target.fsPath) { return; } this._onDidTargetChange.fire(target); diff --git a/src/vs/workbench/parts/preferences/common/preferences.ts b/src/vs/workbench/parts/preferences/common/preferences.ts index e076d37b2de4cbc57f80eef24f19937a7c285bdc..78823a00c283bddee2da4032dd14b971242e9487 100644 --- a/src/vs/workbench/parts/preferences/common/preferences.ts +++ b/src/vs/workbench/parts/preferences/common/preferences.ts @@ -76,10 +76,10 @@ export interface IPreferencesService { resolveContent(uri: URI): TPromise; createPreferencesEditorModel(uri: URI): TPromise>; - openSettings(target: ConfigurationTarget | URI): TPromise; - switchSettings(target: URI | ConfigurationTarget): TPromise; openGlobalSettings(): TPromise; openWorkspaceSettings(): TPromise; + openFolderSettings(folder: URI): TPromise; + switchSettings(target: ConfigurationTarget, resource: URI): TPromise; openGlobalKeybindingSettings(textual: boolean): TPromise; configureSettingsForLanguage(language: string): void; @@ -99,16 +99,15 @@ export interface IKeybindingsEditor extends IEditor { showConflicts(keybindingEntry: IKeybindingItemEntry): TPromise; } -export function getSettingsTargetName(target: ConfigurationTarget | URI, workspaceContextService: IWorkspaceContextService): string { - if (target instanceof URI) { - const root = workspaceContextService.getRoot(target); - return root ? paths.basename(root.fsPath) : ''; - } +export function getSettingsTargetName(target: ConfigurationTarget, resource: URI, workspaceContextService: IWorkspaceContextService): string { switch (target) { case ConfigurationTarget.USER: return localize('userSettingsTarget', "User Settings"); case ConfigurationTarget.WORKSPACE: return localize('workspaceSettingsTarget', "Workspace Settings"); + case ConfigurationTarget.FOLDER: + const root = workspaceContextService.getRoot(resource); + return root ? paths.basename(root.fsPath) : ''; } } diff --git a/src/vs/workbench/services/configuration/common/configurationEditing.ts b/src/vs/workbench/services/configuration/common/configurationEditing.ts index 415e2e58d9ec24e8d7145d488f53a4333979a40c..96e9fb440daef34bdbc580d169be5b2ae9c6ebbc 100644 --- a/src/vs/workbench/services/configuration/common/configurationEditing.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditing.ts @@ -17,6 +17,11 @@ export enum ConfigurationEditingErrorCode { */ ERROR_UNKNOWN_KEY, + /** + * Error when trying to write a configuration key that is not supported for provided target. + */ + ERROR_INVALID_KEY, + /** * Error when trying to write to user target but not supported for provided key. */ @@ -54,7 +59,12 @@ export enum ConfigurationTarget { /** * Targets the workspace configuration file for writing. This only works if a workspace is opened. */ - WORKSPACE + WORKSPACE, + + /** + * Targets the folder configuration file for writing. This only works if a workspace is opened. + */ + FOLDER } export interface IConfigurationValue { diff --git a/src/vs/workbench/services/configuration/node/configurationEditingService.ts b/src/vs/workbench/services/configuration/node/configurationEditingService.ts index f6da2cfebb7175984abbc14a1ffd5bbbe6b5e345..b4fabc97b0fe9b4a0e90e1ebe4c401b8f8ed0643 100644 --- a/src/vs/workbench/services/configuration/node/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/node/configurationEditingService.ts @@ -18,6 +18,7 @@ import { Edit } from 'vs/base/common/jsonFormatter'; import { IReference } from 'vs/base/common/lifecycle'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { Registry } from 'vs/platform/registry/common/platform'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -29,7 +30,7 @@ import { WORKSPACE_CONFIG_DEFAULT_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS } fr import { IFileService } from 'vs/platform/files/common/files'; import { IConfigurationEditingService, ConfigurationEditingErrorCode, ConfigurationEditingError, ConfigurationTarget, IConfigurationValue, IConfigurationEditingOptions } from 'vs/workbench/services/configuration/common/configurationEditing'; import { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/resolverService'; -import { OVERRIDE_PROPERTY_PATTERN } from 'vs/platform/configuration/common/configurationRegistry'; +import { OVERRIDE_PROPERTY_PATTERN, IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IChoiceService, IMessageService, Severity } from 'vs/platform/message/common/message'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -84,7 +85,7 @@ export class ConfigurationEditingService implements IConfigurationEditingService const checkDirtyConfiguration = !(options.force || options.donotSave); const saveConfiguration = options.force || !options.donotSave; - return this.resolveAndValidate(target, operation, checkDirtyConfiguration) + return this.resolveAndValidate(target, operation, checkDirtyConfiguration, options.scopes || {}) .then(reference => this.writeToBuffer(reference.object.textEditorModel, operation, saveConfiguration)); } @@ -163,10 +164,11 @@ export class ConfigurationEditingService implements IConfigurationEditingService // API constraints case ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY: return nls.localize('errorUnknownKey', "Unable to write to the configuration file (Unknown Key)"); + case ConfigurationEditingErrorCode.ERROR_INVALID_KEY: return nls.localize('errorInvalidKey', "Unable to write to the configuration file (Invalid Key)"); case ConfigurationEditingErrorCode.ERROR_INVALID_TARGET: return nls.localize('errorInvalidTarget', "Unable to write to the configuration file (Invalid Target)"); // User issues - case ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED: return nls.localize('errorNoWorkspaceOpened', "Unable to write into settings because no folder is opened. Please open a folder first and try again."); + case ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED: return nls.localize('errorNoWorkspaceOpened', "Unable to write into settings because no workspace is opened. Please open a workspace first and try again."); case ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION: { if (target === ConfigurationTarget.USER) { return nls.localize('errorInvalidConfiguration', "Unable to write into settings. Please open **User Settings** to correct errors/warnings in the file and try again."); @@ -221,7 +223,7 @@ export class ConfigurationEditingService implements IConfigurationEditingService return parseErrors.length > 0; } - private resolveAndValidate(target: ConfigurationTarget, operation: IConfigurationEditOperation, checkDirty: boolean): TPromise> { + private resolveAndValidate(target: ConfigurationTarget, operation: IConfigurationEditOperation, checkDirty: boolean, overrides: IConfigurationOverrides): TPromise> { // Any key must be a known setting from the registry (unless this is a standalone config) if (!operation.isWorkspaceStandalone) { @@ -236,11 +238,22 @@ export class ConfigurationEditingService implements IConfigurationEditingService return this.wrapError(ConfigurationEditingErrorCode.ERROR_INVALID_TARGET, target); } - // Target cannot be workspace if no workspace opened - if (target === ConfigurationTarget.WORKSPACE && !this.contextService.hasWorkspace()) { + // Target cannot be workspace or folder if no workspace opened + if ((target === ConfigurationTarget.WORKSPACE || target === ConfigurationTarget.FOLDER) && !this.contextService.hasWorkspace()) { return this.wrapError(ConfigurationEditingErrorCode.ERROR_NO_WORKSPACE_OPENED, target); } + if (target === ConfigurationTarget.FOLDER) { + if (!operation.resource) { + return this.wrapError(ConfigurationEditingErrorCode.ERROR_INVALID_TARGET, target); + } + + const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); + if (configurationProperties[operation.key].scope !== ConfigurationScope.RESOURCE) { + return this.wrapError(ConfigurationEditingErrorCode.ERROR_INVALID_KEY, target); + } + } + return this.resolveModelReference(operation.resource) .then(reference => { const model = reference.object.textEditorModel; @@ -266,7 +279,7 @@ export class ConfigurationEditingService implements IConfigurationEditingService const standaloneConfigurationKeys = Object.keys(WORKSPACE_STANDALONE_CONFIGURATIONS); for (let i = 0; i < standaloneConfigurationKeys.length; i++) { const key = standaloneConfigurationKeys[i]; - const resource = this.getConfigurationFileResource(WORKSPACE_STANDALONE_CONFIGURATIONS[key], overrides.resource); + const resource = this.getConfigurationFileResource(target, WORKSPACE_STANDALONE_CONFIGURATIONS[key], overrides.resource); // Check for prefix if (config.key === key) { @@ -289,23 +302,34 @@ export class ConfigurationEditingService implements IConfigurationEditingService return { key, jsonPath, value: config.value, resource: URI.file(this.environmentService.appSettingsPath) }; } - const resource = this.getConfigurationFileResource(WORKSPACE_CONFIG_DEFAULT_PATH, overrides.resource); + const resource = this.getConfigurationFileResource(target, WORKSPACE_CONFIG_DEFAULT_PATH, overrides.resource); if (workspace && workspace.configuration && resource && workspace.configuration.fsPath === resource.fsPath) { jsonPath = ['settings', ...jsonPath]; } return { key, jsonPath, value: config.value, resource }; } - private getConfigurationFileResource(relativePath: string, resource: URI): URI { + private getConfigurationFileResource(target: ConfigurationTarget, relativePath: string, resource: URI): URI { + if (target === ConfigurationTarget.USER) { + return URI.file(this.environmentService.appSettingsPath); + } + const workspace = this.contextService.getWorkspace(); + if (workspace) { - if (resource) { - const root = this.contextService.getRoot(resource); - if (root) { - return this.toResource(relativePath, root); + + if (target === ConfigurationTarget.WORKSPACE) { + return this.contextService.hasMultiFolderWorkspace() ? workspace.configuration : this.toResource(relativePath, workspace.roots[0]); + } + + if (target === ConfigurationTarget.FOLDER) { + if (resource) { + const root = this.contextService.getRoot(resource); + if (root) { + return this.toResource(relativePath, root); + } } } - return workspace.configuration || this.toResource(relativePath, workspace.roots[0]); } return null; }