diff --git a/src/vs/base/common/objects.ts b/src/vs/base/common/objects.ts index 897a9fd824971..6fccb6f85898e 100644 --- a/src/vs/base/common/objects.ts +++ b/src/vs/base/common/objects.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isTypedArray, isObject, isUndefinedOrNull } from 'vs/base/common/types'; +import { isTypedArray, isObject, isUndefinedOrNull, OptionalBooleanKey, OptionalNumberKey, OptionalStringKey } from 'vs/base/common/types'; export function deepClone(obj: T): T { if (!obj || typeof obj !== 'object') { @@ -261,3 +261,34 @@ export function createProxyObject(methodNames: string[], invok } return result; } + +export function ensureOptionalBooleanValue(obj: T, key: OptionalBooleanKey, defaultValue: boolean | undefined): void { + if (typeof key !== 'string') { + return; + } + + if (obj[key] !== undefined && typeof obj[key] !== 'boolean') { + obj[key] = defaultValue as any; + } +} + +export function ensureOptionalNumberValue(obj: T, key: OptionalNumberKey, defaultValue: number | undefined): void { + if (typeof key !== 'string') { + return; + } + + if (obj[key] !== undefined && typeof obj[key] !== 'number') { + obj[key] = defaultValue as any; + } +} + +export function ensureOptionalStringValue(obj: T, key: OptionalStringKey, allowed: string[], defaultValue: string | undefined): void { + if (typeof key !== 'string') { + return; + } + + const value = obj[key]; + if (value !== undefined && (typeof value !== 'string' || !allowed.includes(value))) { + obj[key] = defaultValue as any; + } +} diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 529d5e4e7be82..fc53cb11abb03 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -227,3 +227,15 @@ export type Mutable = { * A single object or an array of the objects. */ export type SingleOrMany = T | T[]; + +export type OptionalBooleanKey = { + [K in keyof T]: T[K] extends boolean | undefined ? K : never; +}[keyof T]; + +export type OptionalNumberKey = { + [K in keyof T]: T[K] extends number | undefined ? K : never; +}[keyof T]; + +export type OptionalStringKey = { + [K in keyof T]: T[K] extends string | undefined ? K : never; +}[keyof T]; diff --git a/src/vs/base/test/common/objects.test.ts b/src/vs/base/test/common/objects.test.ts index 867615abce6d9..af797f47eea1a 100644 --- a/src/vs/base/test/common/objects.test.ts +++ b/src/vs/base/test/common/objects.test.ts @@ -227,4 +227,84 @@ suite('Objects', () => { assert.strictEqual(obj1.mIxEdCaSe, objects.getCaseInsensitive(obj1, 'MIXEDCASE')); assert.strictEqual(obj1.mIxEdCaSe, objects.getCaseInsensitive(obj1, 'mixedcase')); }); + + test('ensureOptionalBooleanValue', () => { + const obj: any = { + a: true, + b: false, + c: undefined, + d: 5, + e: 'foo' + }; + + objects.ensureOptionalBooleanValue(obj, 'a', false); + assert.strictEqual(obj.a, true); + + objects.ensureOptionalBooleanValue(obj, 'b', true); + assert.strictEqual(obj.b, false); + + objects.ensureOptionalBooleanValue(obj, 'c', true); + assert.strictEqual(obj.c, undefined); + + objects.ensureOptionalBooleanValue(obj, 'd', true); + assert.strictEqual(obj.d, true); + + objects.ensureOptionalBooleanValue(obj, 'e', true); + assert.strictEqual(obj.e, true); + }); + + test('ensureOptionalNumberValue', () => { + const obj: any = { + a: 1, + b: 0, + c: undefined, + d: true, + e: 'foo' + }; + + objects.ensureOptionalNumberValue(obj, 'a', 0); + assert.strictEqual(obj.a, 1); + + objects.ensureOptionalNumberValue(obj, 'b', 1); + assert.strictEqual(obj.b, 0); + + objects.ensureOptionalNumberValue(obj, 'c', 1); + assert.strictEqual(obj.c, undefined); + + objects.ensureOptionalNumberValue(obj, 'd', 1); + assert.strictEqual(obj.d, 1); + + objects.ensureOptionalNumberValue(obj, 'e', 1); + assert.strictEqual(obj.e, 1); + }); + + test('ensureOptionalStringValue', () => { + const obj: any = { + a: 'hello', + b: 'world', + c: undefined, + d: 'earth', + e: 5, + f: true + }; + + objects.ensureOptionalStringValue(obj, 'a', ['hello', 'world'], 'world'); + assert.strictEqual(obj.a, 'hello'); + + objects.ensureOptionalStringValue(obj, 'b', ['hello', 'world'], 'hello'); + assert.strictEqual(obj.b, 'world'); + + objects.ensureOptionalStringValue(obj, 'c', ['hello', 'world'], 'world'); + assert.strictEqual(obj.c, undefined); + + objects.ensureOptionalStringValue(obj, 'd', ['hello', 'world'], 'world'); + assert.strictEqual(obj.d, 'world'); + + objects.ensureOptionalStringValue(obj, 'e', ['hello', 'world'], 'world'); + assert.strictEqual(obj.e, 'world'); + + objects.ensureOptionalStringValue(obj, 'f', ['hello', 'world'], 'world'); + assert.strictEqual(obj.f, 'world'); + + }); }); diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 6fecf20c3097c..425343b6cf3dd 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorIdentifier, IEditorCloseEvent, IEditorPartOptions, IEditorPartOptionsChangeEvent, SideBySideEditor, EditorCloseContext, IEditorPane } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorIdentifier, IEditorCloseEvent, IEditorPartOptions, IEditorPartOptionsChangeEvent, SideBySideEditor, EditorCloseContext, IEditorPane, IEditorPartLimitConfiguration, IEditorPartDecorationsConfiguration } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IEditorGroup, GroupDirection, IMergeGroupOptions, GroupsOrder, GroupsArrangement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -13,9 +13,10 @@ import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/co import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ISerializableView } from 'vs/base/browser/ui/grid/grid'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { isObject } from 'vs/base/common/types'; +import { OptionalBooleanKey, OptionalNumberKey, OptionalStringKey, isObject } from 'vs/base/common/types'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IWindowsConfiguration } from 'vs/platform/window/common/window'; +import { ensureOptionalBooleanValue, ensureOptionalNumberValue, ensureOptionalStringValue } from 'vs/base/common/objects'; export interface IEditorPartCreationOptions { readonly restorePreviousState: boolean; @@ -90,13 +91,102 @@ export function getEditorPartOptions(configurationService: IConfigurationService return options; } -function validateEditorPartOptions(options: IEditorPartOptions) { - // showTabs ensure correct enum value +function validateEditorPartOptions(options: IEditorPartOptions): void { + + // Migrate: Show tabs (config migration kicks in very late and can cause flicker otherwise) if (typeof options.showTabs === 'boolean') { - // Migration service kicks in very late and can cause a flicker otherwise options.showTabs = options.showTabs ? 'multiple' : 'single'; - } else if (options.showTabs !== 'multiple' && options.showTabs !== 'single' && options.showTabs !== 'none') { - options.showTabs = 'multiple'; + } + + // Boolean options + const booleanOptions: Array> = [ + 'wrapTabs', + 'scrollToSwitchTabs', + 'highlightModifiedTabs', + 'pinnedTabsOnSeparateRow', + 'focusRecentEditorAfterClose', + 'showIcons', + 'enablePreview', + 'enablePreviewFromQuickOpen', + 'enablePreviewFromCodeNavigation', + 'closeOnFileDelete', + 'closeEmptyGroups', + 'revealIfOpen', + 'mouseBackForwardToNavigate', + 'restoreViewState', + 'splitOnDragAndDrop', + 'centeredLayoutFixedWidth', + 'doubleClickTabToToggleEditorGroupSizes' + ]; + for (const option of booleanOptions) { + if (typeof option === 'string') { + ensureOptionalBooleanValue(options, option, Boolean(DEFAULT_EDITOR_PART_OPTIONS[option])); + } + } + + // Number options + const numberOptions: Array> = [ + 'tabSizingFixedMinWidth', + 'tabSizingFixedMaxWidth' + ]; + for (const option of numberOptions) { + if (typeof option === 'string') { + ensureOptionalNumberValue(options, option, Number(DEFAULT_EDITOR_PART_OPTIONS[option])); + } + } + + // String options + const stringOptions: Array<[OptionalStringKey, Array]> = [ + ['showTabs', ['multiple', 'single', 'none']], + ['tabCloseButton', ['left', 'right', 'off']], + ['tabSizing', ['fit', 'shrink', 'fixed']], + ['pinnedTabSizing', ['normal', 'compact', 'shrink']], + ['tabHeight', ['default', 'compact']], + ['preventPinnedEditorClose', ['keyboardAndMouse', 'keyboard', 'mouse', 'never']], + ['titleScrollbarSizing', ['default', 'large']], + ['openPositioning', ['left', 'right', 'first', 'last']], + ['openSideBySideDirection', ['right', 'down']], + ['labelFormat', ['default', 'short', 'medium', 'long']], + ['splitInGroupLayout', ['vertical', 'horizontal']], + ['splitSizing', ['distribute', 'split', 'auto']], + ]; + for (const [option, allowed] of stringOptions) { + if (typeof option === 'string') { + ensureOptionalStringValue(options, option, allowed, String(DEFAULT_EDITOR_PART_OPTIONS[option])); + } + } + + // Complex options + if (options.autoLockGroups && !(options.autoLockGroups instanceof Set)) { + options.autoLockGroups = undefined; + } + if (options.limit && !isObject(options.limit)) { + options.limit = undefined; + } else if (options.limit) { + const booleanLimitOptions: Array> = [ + 'enabled', + 'excludeDirty', + 'perEditorGroup' + ]; + for (const option of booleanLimitOptions) { + if (typeof option === 'string') { + ensureOptionalBooleanValue(options.limit, option, undefined); + } + } + ensureOptionalNumberValue(options.limit, 'value', undefined); + } + if (options.decorations && !isObject(options.decorations)) { + options.decorations = undefined; + } else if (options.decorations) { + const booleanDecorationOptions: Array> = [ + 'badges', + 'colors' + ]; + for (const option of booleanDecorationOptions) { + if (typeof option === 'string') { + ensureOptionalBooleanValue(options.decorations, option, undefined); + } + } } } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 36c62f1a36203..60e2e472be269 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -1095,6 +1095,18 @@ export interface IWorkbenchEditorConfiguration { }; } +export interface IEditorPartLimitConfiguration { + enabled?: boolean; + excludeDirty?: boolean; + value?: number; + perEditorGroup?: boolean; +} + +export interface IEditorPartDecorationsConfiguration { + badges?: boolean; + colors?: boolean; +} + interface IEditorPartConfiguration { showTabs?: 'multiple' | 'single' | 'none'; wrapTabs?: boolean; @@ -1128,16 +1140,8 @@ interface IEditorPartConfiguration { splitOnDragAndDrop?: boolean; centeredLayoutFixedWidth?: boolean; doubleClickTabToToggleEditorGroupSizes?: boolean; - limit?: { - enabled?: boolean; - excludeDirty?: boolean; - value?: number; - perEditorGroup?: boolean; - }; - decorations?: { - badges?: boolean; - colors?: boolean; - }; + limit?: IEditorPartLimitConfiguration; + decorations?: IEditorPartDecorationsConfiguration; } export interface IEditorPartOptions extends IEditorPartConfiguration {