diff --git a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css index 368bec216c8890f814c57a45cb0952a16a8da869..67c8790f01a07f8b4c9118df05cf66aee67ca5ae 100644 --- a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css @@ -281,17 +281,24 @@ text-overflow: ellipsis; } -.settings-editor > .settings-body > .settings-tree-container .setting-item.is-configured::after { + +.settings-editor > .settings-body > .settings-tree-container .setting-item > .setting-item-modified-indicator { + display: none; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item.is-configured > .setting-item-modified-indicator { display: block; content: ' '; position: absolute; - width: 2px; + width: 6px; + border-left-width: 2px; + border-left-style: solid; left: 0px; top: 15px; bottom: 16px; } -.settings-editor > .settings-body > .settings-tree-container .setting-item-bool.setting-item.is-configured::after { +.settings-editor > .settings-body > .settings-tree-container .setting-item-bool.is-configured > .setting-item-modified-indicator { bottom: 23px; } @@ -363,6 +370,11 @@ .settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown a:focus { outline: 1px solid -webkit-focus-ring-color; outline-offset: -1px; + text-decoration: underline; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown a:hover { + text-decoration: underline; } .settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown code { diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index 90bd7ee07c71f13ed2103b3be2411494875ef3aa..e39c293743a759eb7df76eac9fcd181604c4313a 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -26,11 +26,15 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { WorkbenchTree } from 'vs/platform/list/browser/listService'; import { ILogService } from 'vs/platform/log/common/log'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { badgeBackground, badgeForeground, contrastBorder, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { EditorOptions, IEditor } from 'vs/workbench/common/editor'; +import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; import { SuggestEnabledInput } from 'vs/workbench/parts/codeEditor/browser/suggestEnabledInput'; import { PreferencesEditor } from 'vs/workbench/parts/preferences/browser/preferencesEditor'; import { SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; @@ -43,8 +47,6 @@ import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsSer import { IPreferencesService, ISearchResult, ISettingsEditorModel } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { DefaultSettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; -import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; -import { badgeBackground, contrastBorder, badgeForeground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; const $ = DOM.$; @@ -111,7 +113,9 @@ export class SettingsEditor2 extends BaseEditor { @ILogService private logService: ILogService, @IEnvironmentService private environmentService: IEnvironmentService, @IContextKeyService contextKeyService: IContextKeyService, - @IContextMenuService private contextMenuService: IContextMenuService + @IContextMenuService private contextMenuService: IContextMenuService, + @IStorageService private storageService: IStorageService, + @INotificationService private notificationService: INotificationService ) { super(SettingsEditor2.ID, telemetryService, themeService); this.delayedFilterLogging = new Delayer(1000); @@ -484,7 +488,16 @@ export class SettingsEditor2 extends BaseEditor { })); } + public notifyNoSaveNeeded(force: boolean = true) { + if (force || !this.storageService.getBoolean('hasNotifiedOfSettingsAutosave', StorageScope.GLOBAL, false)) { + this.storageService.store('hasNotifiedOfSettingsAutosave', true, StorageScope.GLOBAL); + this.notificationService.info(localize('settingsNoSaveNeeded', "Your changes are automatically saved as you edit.")); + } + } + private onDidChangeSetting(key: string, value: any): void { + this.notifyNoSaveNeeded(false); + if (this.pendingSettingUpdate && this.pendingSettingUpdate.key !== key) { this.updateChangedSetting(key, value); } @@ -728,7 +741,9 @@ export class SettingsEditor2 extends BaseEditor { // If a single setting is being refreshed, it's ok to refresh now if that is not the focused setting if (key) { const focusedKey = focusedSetting.getAttribute(SettingsRenderer.SETTING_KEY_ATTR); - if (focusedKey === key) { + if (focusedKey === key && + !DOM.hasClass(focusedSetting, 'setting-item-exclude')) { // update `exclude`s live, as they have a separate "submit edit" step built in before this + this.updateModifiedLabelForKey(key); this.scheduleRefresh(focusedSetting, key); return TPromise.wrap(null); diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts index 02addf156b658234176eaa57d5f0da9f96f18b52..30418278f38577884db2738e914c405c6e3f4771 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts @@ -21,7 +21,6 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import * as objects from 'vs/base/common/objects'; import { escapeRegExpCharacters, startsWith } from 'vs/base/common/strings'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -48,7 +47,7 @@ const $ = DOM.$; function getExcludeDisplayValue(element: SettingsTreeSettingElement): IExcludeDataItem[] { const data = element.isConfigured ? - objects.mixin({ ...element.scopeValue }, element.defaultValue, false) : + { ...element.defaultValue, ...element.scopeValue } : element.defaultValue; return Object.keys(data) @@ -318,17 +317,16 @@ export class SettingsRenderer implements ITreeRenderer { this.descriptionMeasureContainer))); this.settingActions = [ - this.instantiationService.createInstance(CopySettingNameAction), - this.instantiationService.createInstance(CopySettingIdAction), - this.instantiationService.createInstance(CopySettingAsJSONAction), - new Separator(), new Action('settings.resetSetting', localize('resetSettingLabel', "Reset Setting"), undefined, undefined, (context: SettingsTreeSettingElement) => { if (context) { this._onDidChangeSetting.fire({ key: context.setting.key, value: undefined }); } return TPromise.wrap(null); - }) + }), + new Separator(), + this.instantiationService.createInstance(CopySettingIdAction), + this.instantiationService.createInstance(CopySettingAsJSONAction), ]; } @@ -522,6 +520,8 @@ export class SettingsRenderer implements ITreeRenderer { const labelElement = DOM.append(labelCategoryContainer, $('span.setting-item-label')); const otherOverridesElement = DOM.append(titleElement, $('span.setting-item-overrides')); const descriptionElement = DOM.append(container, $('.setting-item-description')); + const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator')); + modifiedIndicatorElement.title = localize('modified', "Modified"); const valueElement = DOM.append(container, $('.setting-item-value')); const controlElement = DOM.append(valueElement, $('div.setting-item-control')); @@ -661,6 +661,9 @@ export class SettingsRenderer implements ITreeRenderer { const descriptionAndValueElement = DOM.append(container, $('.setting-item-value-description')); const controlElement = DOM.append(descriptionAndValueElement, $('.setting-item-bool-control')); const descriptionElement = DOM.append(descriptionAndValueElement, $('.setting-item-description')); + const modifiedIndicatorElement = DOM.append(container, $('.setting-item-modified-indicator')); + modifiedIndicatorElement.title = localize('modified', "Modified"); + const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message')); @@ -791,36 +794,43 @@ export class SettingsRenderer implements ITreeRenderer { common.toDispose.push(excludeWidget.onDidChangeExclude(e => { if (template.context) { - const newValue = { - ...template.context.scopeValue - }; + let newValue = { ...template.context.scopeValue }; - if (e.pattern) { - if (e.originalPattern in newValue) { - // editing something present in the value - newValue[e.pattern] = newValue[e.originalPattern]; - delete newValue[e.originalPattern]; - } else if (e.originalPattern) { - // editing a default + // first delete the existing entry, if present + if (e.originalPattern) { + if (e.originalPattern in template.context.defaultValue) { + // delete a default by overriding it newValue[e.originalPattern] = false; - newValue[e.pattern] = template.context.defaultValue[e.originalPattern]; } else { - // adding a new pattern - newValue[e.pattern] = true; - } - } else { - if (e.originalPattern in newValue) { - // deleting a configured pattern delete newValue[e.originalPattern]; - } else if (e.originalPattern) { - // "deleting" a default by overriding it - newValue[e.originalPattern] = false; } } + // then add the new or updated entry, if present + if (e.pattern) { + if (e.pattern in template.context.defaultValue && !e.sibling) { + // add a default by deleting its override + delete newValue[e.pattern]; + } else { + newValue[e.pattern] = e.sibling ? { when: e.sibling } : true; + } + } + + const sortKeys = (obj) => { + const keyArray = Object.keys(obj) + .map(key => ({ key, val: obj[key] })) + .sort((a, b) => a.key.localeCompare(b.key)); + + const retVal = {}; + keyArray.forEach(pair => { + retVal[pair.key] = pair.val; + }); + return retVal; + }; + this._onDidChangeSetting.fire({ key: template.context.setting.key, - value: newValue + value: Object.keys(newValue).length === 0 ? undefined : sortKeys(newValue) }); } })); @@ -1483,24 +1493,4 @@ class CopySettingAsJSONAction extends Action { return TPromise.as(null); } -} - -class CopySettingNameAction extends Action { - static readonly ID = 'settings.copySettingName'; - static readonly LABEL = localize('copySettingNameLabel', "Copy Setting Name"); - - constructor( - @IClipboardService private clipboardService: IClipboardService - ) { - super(CopySettingNameAction.ID, CopySettingNameAction.LABEL); - } - - run(context: SettingsTreeSettingElement): TPromise { - if (context) { - const name = `${context.displayCategory}: ${context.displayLabel}`; - this.clipboardService.writeText(name); - } - - return TPromise.as(null); - } -} +} \ No newline at end of file diff --git a/src/vs/workbench/parts/preferences/browser/settingsWidgets.ts b/src/vs/workbench/parts/preferences/browser/settingsWidgets.ts index 998e7f67552a0399ff5bef810786f4f2c4b898ad..ae0ad8a2d15fbcffdbc699048ea3710e3d1251ca 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsWidgets.ts @@ -118,7 +118,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const modifiedItemIndicatorColor = theme.getColor(modifiedItemIndicator); if (modifiedItemIndicatorColor) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.is-configured::after { background-color: ${modifiedItemIndicatorColor}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item > .setting-item-modified-indicator { border-color: ${modifiedItemIndicatorColor}; }`); } }); @@ -403,9 +403,8 @@ export class ExcludeSettingWidget extends Disposable { pattern, sibling: siblingInput && siblingInput.value.trim() }); - } else { - this.renderList(); } + this.renderList(); }; const onKeydown = (e: StandardKeyboardEvent) => {