diff --git a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css index 259f9a10bab00000b40bdebb4a470397da62a044..01b21cac4344f16588773220644d0883741f3cdb 100644 --- a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css @@ -223,6 +223,10 @@ position: relative; } +.settings-editor > .settings-body > .settings-tree-container .monaco-tree-row { + overflow: visible; /* so validation messages dont get clipped */ +} + .settings-editor > .settings-body > .settings-tree-container .setting-item { padding-top: 12px; padding-bottom: 18px; @@ -262,7 +266,7 @@ opacity: 0.9; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-validation-message, +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-deprecation-message, .settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description { margin-top: 3px; overflow: hidden; @@ -274,6 +278,31 @@ transform: translate3d(0px, 0px, 0px); } +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-deprecation-message { + position: absolute; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-validation-message { + display: none; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item.invalid-input .setting-item-validation-message { + display: block; + position: absolute; + padding: 5px; + box-sizing: border-box; + margin-top: -1px; + z-index: 1; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-text .setting-item-validation-message { + width: 500px; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-number .setting-item-validation-message { + width: 200px; +} + .settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown * { margin: 0px; } @@ -294,8 +323,6 @@ } .settings-editor > .settings-body > .settings-tree-container .setting-item.is-expanded .setting-item-description, -.settings-editor > .settings-body > .settings-tree-container .setting-item.is-expanded .setting-item-validation-message, -.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-measure-helper .setting-item-validation-message, .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-measure-helper .setting-item-description { height: initial; -webkit-line-clamp: initial; diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts index e0c88c9d6d9bc560c8fb0f49559e12c040f71a3d..42c928f3dd8d49151eb5b80d566a051da933d137 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts @@ -9,7 +9,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { Button } from 'vs/base/browser/ui/button/button'; import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; -import { InputBox, MessageType, IInputValidator } from 'vs/base/browser/ui/inputbox/inputBox'; +import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox'; import * as arrays from 'vs/base/common/arrays'; import { Color, RGBA } from 'vs/base/common/color'; @@ -32,7 +32,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IListService, WorkbenchTreeController } from 'vs/platform/list/browser/listService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { editorBackground, focusBorder, foreground, errorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { editorBackground, focusBorder, foreground, errorForeground, inputValidationErrorBackground, inputValidationErrorBorder } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler, attachStyler } from 'vs/platform/theme/common/styler'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { SettingsTarget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; @@ -504,6 +504,7 @@ interface ISettingBoolItemTemplate extends ISettingItemTemplate { interface ISettingTextItemTemplate extends ISettingItemTemplate { inputBox: InputBox; + validationErrorMessageElement: HTMLElement; } type ISettingNumberItemTemplate = ISettingTextItemTemplate; @@ -741,7 +742,7 @@ export class SettingsRenderer implements ITreeRenderer { const valueElement = DOM.append(container, $('.setting-item-value')); const controlElement = DOM.append(valueElement, $('div.setting-item-control')); - const deprecationWarningElement = DOM.append(container, $('.setting-item-validation-message')); + const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message')); const toDispose = []; const template: ISettingItemTemplate = { @@ -780,6 +781,7 @@ export class SettingsRenderer implements ITreeRenderer { private renderSettingTextTemplate(tree: ITree, container: HTMLElement, type = 'text'): ISettingTextItemTemplate { const common = this.renderCommonTemplate(tree, container, 'text'); + const validationErrorMessageElement = DOM.append(container, $('.setting-item-validation-message')); const inputBox = new InputBox(common.controlElement, this.contextViewService); common.toDispose.push(inputBox); @@ -799,7 +801,8 @@ export class SettingsRenderer implements ITreeRenderer { const template: ISettingTextItemTemplate = { ...common, - inputBox + inputBox, + validationErrorMessageElement }; this.addSettingElementFocusHandler(template); @@ -809,6 +812,7 @@ export class SettingsRenderer implements ITreeRenderer { private renderSettingNumberTemplate(tree: ITree, container: HTMLElement): ISettingNumberItemTemplate { const common = this.renderCommonTemplate(tree, container, 'number'); + const validationErrorMessageElement = DOM.append(container, $('.setting-item-validation-message')); const inputBox = new InputBox(common.controlElement, this.contextViewService); common.toDispose.push(inputBox); @@ -828,7 +832,8 @@ export class SettingsRenderer implements ITreeRenderer { const template: ISettingNumberItemTemplate = { ...common, - inputBox + inputBox, + validationErrorMessageElement }; this.addSettingElementFocusHandler(template); @@ -850,7 +855,7 @@ export class SettingsRenderer implements ITreeRenderer { const controlElement = DOM.append(descriptionAndValueElement, $('.setting-item-bool-control')); const descriptionElement = DOM.append(descriptionAndValueElement, $('.setting-item-description')); - const deprecationWarningElement = DOM.append(container, $('.setting-item-validation-message')); + const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message')); const toDispose = []; const checkbox = new Checkbox({ actionClassName: 'setting-value-checkbox', isChecked: true, title: '', inputActiveOptionBorder: null }); @@ -1204,10 +1209,9 @@ export class SettingsRenderer implements ITreeRenderer { private renderText(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate, onChange: (value: string) => void): void { template.onChange = null; template.inputBox.value = dataElement.value; - template.inputBox.attachValidator(makeValidator(dataElement)); - template.inputBox.validate(); // for some reason this is needed on text but not number. TODO: figure out why - template.onChange = value => onChange(value); + template.onChange = value => { renderValidations(dataElement, template); onChange(value); }; + renderValidations(dataElement, template); // Setup and add ARIA attributes // Create id and label for control/input element - parent is wrapper div const id = (dataElement.displayCategory + '_' + dataElement.displayLabel).replace(/ /g, '_'); @@ -1229,13 +1233,13 @@ export class SettingsRenderer implements ITreeRenderer { private renderNumber(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate, onChange: (value: number) => void): void { + const parseFn = dataElement.valueType === 'integer' ? parseInt : parseFloat; + template.onChange = null; template.inputBox.value = dataElement.value; - template.inputBox.attachValidator(makeValidator(dataElement)); - template.onChange = value => onChange(parseFn(value)); - - const parseFn = dataElement.valueType === 'integer' ? parseInt : parseFloat; + template.onChange = value => { renderValidations(dataElement, template); onChange(parseFn(value)); }; + renderValidations(dataElement, template); // Setup and add ARIA attributes // Create id and label for control/input element - parent is wrapper div const id = (dataElement.displayCategory + '_' + dataElement.displayLabel).replace(/ /g, '_'); @@ -1270,11 +1274,16 @@ export class SettingsRenderer implements ITreeRenderer { } } -function makeValidator(dataElement: SettingsTreeSettingElement): IInputValidator | null { - return value => { - let message = dataElement.setting.validator(value); - return message ? { content: message, type: MessageType.ERROR } : null; - }; +function renderValidations(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate) { + if (dataElement.setting.validator) { + let errMsg = dataElement.setting.validator(template.inputBox.value); + if (errMsg) { + DOM.addClass(template.containerElement, 'invalid-input'); + template.validationErrorMessageElement.innerText = errMsg; + return; + } + } + DOM.removeClass(template.containerElement, 'invalid-input'); } function cleanRenderedMarkdown(element: Node): void { @@ -1573,7 +1582,18 @@ export class SettingsTree extends NonExpandableOrSelectableTree { const errorColor = theme.getColor(errorForeground); if (errorColor) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-validation-message { color: ${errorColor}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-deprecation-message { color: ${errorColor}; }`); + } + + const invalidInputBackground = theme.getColor(inputValidationErrorBackground); + if (invalidInputBackground) { + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-validation-message { background-color: ${invalidInputBackground}; }`); + } + + const invalidInputBorder = theme.getColor(inputValidationErrorBorder); + if (invalidInputBorder) { + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-validation-message { border-style:solid; border-width: 1px; border-color: ${invalidInputBorder}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.invalid-input .setting-item-control .monaco-inputbox.idle { outline-width: 0; border-style:solid; border-width: 1px; border-color: ${invalidInputBorder}; }`); } const headerForegroundColor = theme.getColor(settingsHeaderForeground); diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 78da97898e53fba9db40cb1a1f82089791cae5ec..fbc31b4875fdeaf0c2175628db7d651ae80a351d 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -555,12 +555,6 @@ export class DefaultSettings extends Disposable { if (this.matchesScope(prop)) { const value = prop.default; const description = (prop.description || prop.markdownDescription || '').split('\n'); - if (prop.deprecationMessage) { - description.push( - '', - prop.deprecationMessage, - nls.localize('deprecatedSetting.unstable', "This setting should not be used, and will be removed in a future release.")); - } const overrides = OVERRIDE_PROPERTY_PATTERN.test(key) ? this.parseOverrideSettings(prop.default) : []; result.push({ key, @@ -773,7 +767,7 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements } private copySetting(setting: ISetting): ISetting { - return { + return { description: setting.description, type: setting.type, enum: setting.enum, @@ -783,7 +777,12 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements range: setting.range, overrides: [], overrideOf: setting.overrideOf, - tags: setting.tags + tags: setting.tags, + deprecationMessage: setting.deprecationMessage, + keyRange: undefined, + valueRange: undefined, + descriptionIsMarkdown: undefined, + descriptionRanges: undefined }; } @@ -910,8 +909,7 @@ class SettingsContentBuilder { setting.descriptionRanges = []; const descriptionPreValue = indent + '// '; - for (let line of setting.description) { - // Remove setting link tag + for (let line of (setting.deprecationMessage ? [setting.deprecationMessage] : setting.description)) { line = fixSettingLink(line); this._contentByLines.push(descriptionPreValue + line); @@ -964,7 +962,7 @@ class SettingsContentBuilder { } } -function createValidator(prop: IConfigurationPropertySchema): (value: any) => string { +function createValidator(prop: IConfigurationPropertySchema): ((value: any) => string) | null { let exclusiveMax: number | undefined; let exclusiveMin: number | undefined; @@ -1040,6 +1038,9 @@ function createValidator(prop: IConfigurationPropertySchema): (value: any) => st }, ].filter(validation => validation.enabled); + if ((prop.type === 'number' || prop.type === 'integer') && numericValidations.length === 0) { return null; } + if (prop.type === 'string' && stringValidations.length === 0) { return null; } + return value => { let errors = [];