diff --git a/src/vs/editor/common/config/editorConfigurationSchema.ts b/src/vs/editor/common/config/editorConfigurationSchema.ts index 50f727fa7dc..1e43222cb7c 100644 --- a/src/vs/editor/common/config/editorConfigurationSchema.ts +++ b/src/vs/editor/common/config/editorConfigurationSchema.ts @@ -26,20 +26,20 @@ const editorConfiguration: IConfigurationNode = { minimum: 1, markdownDescription: nls.localize('tabSize', "The number of spaces a tab is equal to. This setting is overridden based on the file contents when `#editor.detectIndentation#` is on.") }, - // 'editor.indentSize': { - // 'anyOf': [ - // { - // type: 'string', - // enum: ['tabSize'] - // }, - // { - // type: 'number', - // minimum: 1 - // } - // ], - // default: 'tabSize', - // markdownDescription: nls.localize('indentSize', "The number of spaces used for indentation or 'tabSize' to use the value from `#editor.tabSize#`. This setting is overridden based on the file contents when `#editor.detectIndentation#` is on.") - // }, + 'editor.indentSize': { + 'anyOf': [ + { + type: 'string', + enum: ['tabSize'] + }, + { + type: 'number', + minimum: 1 + } + ], + default: 'tabSize', + markdownDescription: nls.localize('indentSize', "The number of spaces used for indentation or 'tabSize' to use the value from `#editor.tabSize#`. This setting is overridden based on the file contents when `#editor.detectIndentation#` is on.") + }, 'editor.insertSpaces': { type: 'boolean', default: EDITOR_MODEL_DEFAULTS.insertSpaces, diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index 354026fdcb5..05896a2bf7f 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -436,7 +436,7 @@ export class TextModelResolvedOptions { bracketPairColorizationOptions: BracketPairColorizationOptions; }) { this.tabSize = Math.max(1, src.tabSize | 0); - this.indentSize = src.tabSize | 0; + this.indentSize = Math.max(1, src.indentSize | 0); this.insertSpaces = Boolean(src.insertSpaces); this.defaultEOL = src.defaultEOL | 0; this.trimAutoWhitespace = Boolean(src.trimAutoWhitespace); diff --git a/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts b/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts index 137dfc2bb9f..aab47e06c9a 100644 --- a/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts +++ b/src/vs/editor/contrib/snippet/test/browser/snippetSession.test.ts @@ -706,7 +706,7 @@ suite('SnippetSession', function () { test('Tabs don\'t get replaced with spaces in snippet transformations #103818', function () { const model = editor.getModel()!; model.setValue('\n{\n \n}'); - model.updateOptions({ insertSpaces: true, tabSize: 2 }); + model.updateOptions({ insertSpaces: true, indentSize: 2 }); editor.setSelections([new Selection(1, 1, 1, 1), new Selection(3, 6, 3, 6)]); const session = new SnippetSession(editor, [ 'function animate () {', diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index 4d4bfcafcc4..39add3ef287 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -699,7 +699,7 @@ suite('TextModelWithTokens regression tests', () => { }); suite('TextModel.getLineIndentGuide', () => { - function assertIndentGuides(lines: [number, number, number, number, string][], tabSize: number): void { + function assertIndentGuides(lines: [number, number, number, number, string][], indentSize: number): void { const languageId = 'testLang'; const disposables = new DisposableStore(); const instantiationService = createModelServices(disposables); @@ -708,7 +708,7 @@ suite('TextModel.getLineIndentGuide', () => { const text = lines.map(l => l[4]).join('\n'); const model = disposables.add(instantiateTextModel(instantiationService, text, languageId)); - model.updateOptions({ tabSize: tabSize }); + model.updateOptions({ indentSize: indentSize }); const actualIndents = model.guides.getLinesIndentGuides(1, model.getLineCount()); diff --git a/src/vs/workbench/api/browser/mainThreadEditor.ts b/src/vs/workbench/api/browser/mainThreadEditor.ts index 638fe8aa474..cbc28f4d953 100644 --- a/src/vs/workbench/api/browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/browser/mainThreadEditor.ts @@ -80,6 +80,7 @@ export class MainThreadTextEditorProperties { return { insertSpaces: modelOptions.insertSpaces, tabSize: modelOptions.tabSize, + indentSize: modelOptions.indentSize, cursorStyle: cursorStyle, lineNumbers: lineNumbers }; @@ -146,6 +147,7 @@ export class MainThreadTextEditorProperties { } return ( a.tabSize === b.tabSize + && a.indentSize === b.indentSize && a.insertSpaces === b.insertSpaces && a.cursorStyle === b.cursorStyle && a.lineNumbers === b.lineNumbers @@ -376,6 +378,13 @@ export class MainThreadTextEditor { if (typeof newConfiguration.tabSize !== 'undefined') { newOpts.tabSize = newConfiguration.tabSize; } + if (typeof newConfiguration.indentSize !== 'undefined') { + if (newConfiguration.indentSize === 'tabSize') { + newOpts.indentSize = newOpts.tabSize || creationOpts.tabSize; + } else { + newOpts.indentSize = newConfiguration.indentSize; + } + } this._model.updateOptions(newOpts); } diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 8df19f84b76..7e90c0dd100 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -203,6 +203,7 @@ export interface MainThreadDocumentsShape extends IDisposable { export interface ITextEditorConfigurationUpdate { tabSize?: number | 'auto'; + indentSize?: number | 'tabSize'; insertSpaces?: boolean | 'auto'; cursorStyle?: TextEditorCursorStyle; lineNumbers?: RenderLineNumbersType; @@ -210,6 +211,7 @@ export interface ITextEditorConfigurationUpdate { export interface IResolvedTextEditorConfiguration { tabSize: number; + indentSize: number; insertSpaces: boolean; cursorStyle: TextEditorCursorStyle; lineNumbers: RenderLineNumbersType; diff --git a/src/vs/workbench/api/common/extHostTextEditor.ts b/src/vs/workbench/api/common/extHostTextEditor.ts index f025cc805c4..427c6f5a9f9 100644 --- a/src/vs/workbench/api/common/extHostTextEditor.ts +++ b/src/vs/workbench/api/common/extHostTextEditor.ts @@ -143,6 +143,7 @@ export class ExtHostTextEditorOptions { private _logService: ILogService; private _tabSize!: number; + private _indentSize!: number; private _insertSpaces!: boolean; private _cursorStyle!: TextEditorCursorStyle; private _lineNumbers!: TextEditorLineNumbersStyle; @@ -164,6 +165,12 @@ export class ExtHostTextEditorOptions { set tabSize(value: number | string) { that._setTabSize(value); }, + get indentSize(): number | string { + return that._indentSize; + }, + set indentSize(value: number | string) { + that._setIndentSize(value); + }, get insertSpaces(): boolean | string { return that._insertSpaces; }, @@ -187,6 +194,7 @@ export class ExtHostTextEditorOptions { public _accept(source: IResolvedTextEditorConfiguration): void { this._tabSize = source.tabSize; + this._indentSize = source.indentSize; this._insertSpaces = source.insertSpaces; this._cursorStyle = source.cursorStyle; this._lineNumbers = TypeConverters.TextEditorLineNumbersStyle.to(source.lineNumbers); @@ -231,6 +239,45 @@ export class ExtHostTextEditorOptions { })); } + // --- internal: indentSize + + private _validateIndentSize(value: number | string): number | 'tabSize' | null { + if (value === 'tabSize') { + return 'tabSize'; + } + if (typeof value === 'number') { + const r = Math.floor(value); + return (r > 0 ? r : null); + } + if (typeof value === 'string') { + const r = parseInt(value, 10); + if (isNaN(r)) { + return null; + } + return (r > 0 ? r : null); + } + return null; + } + + private _setIndentSize(value: number | string) { + const indentSize = this._validateIndentSize(value); + if (indentSize === null) { + // ignore invalid call + return; + } + if (typeof indentSize === 'number') { + if (this._indentSize === indentSize) { + // nothing to do + return; + } + // reflect the new indentSize value immediately + this._indentSize = indentSize; + } + this._warnOnError('setIndentSize', this._proxy.$trySetOptions(this._id, { + indentSize: indentSize + })); + } + // --- internal: insert spaces private _validateInsertSpaces(value: boolean | string): boolean | 'auto' { @@ -298,18 +345,18 @@ export class ExtHostTextEditorOptions { } } - // if (typeof newOptions.indentSize !== 'undefined') { - // const indentSize = this._validateIndentSize(newOptions.indentSize); - // if (indentSize === 'tabSize') { - // hasUpdate = true; - // bulkConfigurationUpdate.indentSize = indentSize; - // } else if (typeof indentSize === 'number' && this._indentSize !== indentSize) { - // // reflect the new indentSize value immediately - // this._indentSize = indentSize; - // hasUpdate = true; - // bulkConfigurationUpdate.indentSize = indentSize; - // } - // } + if (typeof newOptions.indentSize !== 'undefined') { + const indentSize = this._validateIndentSize(newOptions.indentSize); + if (indentSize === 'tabSize') { + hasUpdate = true; + bulkConfigurationUpdate.indentSize = indentSize; + } else if (typeof indentSize === 'number' && this._indentSize !== indentSize) { + // reflect the new indentSize value immediately + this._indentSize = indentSize; + hasUpdate = true; + bulkConfigurationUpdate.indentSize = indentSize; + } + } if (typeof newOptions.insertSpaces !== 'undefined') { const insertSpaces = this._validateInsertSpaces(newOptions.insertSpaces); diff --git a/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts b/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts index accded46c5d..d5d4b12f3ed 100644 --- a/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTextEditor.test.ts @@ -21,7 +21,7 @@ suite('ExtHostTextEditor', () => { ], '\n', 1, 'text', false); setup(() => { - editor = new ExtHostTextEditor('fake', null!, new NullLogService(), new Lazy(() => doc.document), [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4 }, [], 1); + editor = new ExtHostTextEditor('fake', null!, new NullLogService(), new Lazy(() => doc.document), [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1); }); test('disposed editor', () => { @@ -48,7 +48,7 @@ suite('ExtHostTextEditor', () => { applyCount += 1; return Promise.resolve(true); } - }, new NullLogService(), new Lazy(() => doc.document), [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4 }, [], 1); + }, new NullLogService(), new Lazy(() => doc.document), [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1); await editor.value.edit(edit => { }); assert.strictEqual(applyCount, 0); @@ -90,6 +90,7 @@ suite('ExtHostTextEditorOptions', () => { }; opts = new ExtHostTextEditorOptions(mockProxy, '1', { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -104,6 +105,7 @@ suite('ExtHostTextEditorOptions', () => { function assertState(opts: ExtHostTextEditorOptions, expected: IResolvedTextEditorConfiguration): void { const actual = { tabSize: opts.value.tabSize, + indentSize: opts.value.indentSize, insertSpaces: opts.value.insertSpaces, cursorStyle: opts.value.cursorStyle, lineNumbers: opts.value.lineNumbers @@ -115,6 +117,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.tabSize = 4; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -126,6 +129,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.tabSize = 1; assertState(opts, { tabSize: 1, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -137,6 +141,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.tabSize = 2.3; assertState(opts, { tabSize: 2, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -148,6 +153,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.tabSize = '2'; assertState(opts, { tabSize: 2, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -159,6 +165,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.tabSize = 'auto'; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -170,6 +177,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.tabSize = null!; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -181,6 +189,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.tabSize = -5; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -192,6 +201,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.tabSize = 'hello'; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -203,6 +213,127 @@ suite('ExtHostTextEditorOptions', () => { opts.value.tabSize = '-17'; assertState(opts, { tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: RenderLineNumbersType.On + }); + assert.deepStrictEqual(calls, []); + }); + + test('can set indentSize to the same value', () => { + opts.value.indentSize = 4; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: RenderLineNumbersType.On + }); + assert.deepStrictEqual(calls, []); + }); + + test('can change indentSize to positive integer', () => { + opts.value.indentSize = 1; + assertState(opts, { + tabSize: 4, + indentSize: 1, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: RenderLineNumbersType.On + }); + assert.deepStrictEqual(calls, [{ indentSize: 1 }]); + }); + + test('can change indentSize to positive float', () => { + opts.value.indentSize = 2.3; + assertState(opts, { + tabSize: 4, + indentSize: 2, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: RenderLineNumbersType.On + }); + assert.deepStrictEqual(calls, [{ indentSize: 2 }]); + }); + + test('can change indentSize to a string number', () => { + opts.value.indentSize = '2'; + assertState(opts, { + tabSize: 4, + indentSize: 2, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: RenderLineNumbersType.On + }); + assert.deepStrictEqual(calls, [{ indentSize: 2 }]); + }); + + test('indentSize can request to use tabSize', () => { + opts.value.indentSize = 'tabSize'; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: RenderLineNumbersType.On + }); + assert.deepStrictEqual(calls, [{ indentSize: 'tabSize' }]); + }); + + test('indentSize cannot request indentation detection', () => { + opts.value.indentSize = 'auto'; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: RenderLineNumbersType.On + }); + assert.deepStrictEqual(calls, []); + }); + + test('ignores invalid indentSize 1', () => { + opts.value.indentSize = null!; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: RenderLineNumbersType.On + }); + assert.deepStrictEqual(calls, []); + }); + + test('ignores invalid indentSize 2', () => { + opts.value.indentSize = -5; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: RenderLineNumbersType.On + }); + assert.deepStrictEqual(calls, []); + }); + + test('ignores invalid indentSize 3', () => { + opts.value.indentSize = 'hello'; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: RenderLineNumbersType.On + }); + assert.deepStrictEqual(calls, []); + }); + + test('ignores invalid indentSize 4', () => { + opts.value.indentSize = '-17'; + assertState(opts, { + tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -214,6 +345,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.insertSpaces = false; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -225,6 +357,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.insertSpaces = true; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: true, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -236,6 +369,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.insertSpaces = 'false'; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -247,6 +381,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.insertSpaces = 'hello'; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: true, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -258,6 +393,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.insertSpaces = 'auto'; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -269,6 +405,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.cursorStyle = TextEditorCursorStyle.Line; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -280,6 +417,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.cursorStyle = TextEditorCursorStyle.Block; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Block, lineNumbers: RenderLineNumbersType.On @@ -291,6 +429,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.lineNumbers = TextEditorLineNumbersStyle.On; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -302,6 +441,7 @@ suite('ExtHostTextEditorOptions', () => { opts.value.lineNumbers = TextEditorLineNumbersStyle.Off; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.Off @@ -312,12 +452,14 @@ suite('ExtHostTextEditorOptions', () => { test('can do bulk updates 0', () => { opts.assign({ tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On }); assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -332,6 +474,7 @@ suite('ExtHostTextEditorOptions', () => { }); assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: true, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -346,6 +489,7 @@ suite('ExtHostTextEditorOptions', () => { }); assertState(opts, { tabSize: 3, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: RenderLineNumbersType.On @@ -360,6 +504,7 @@ suite('ExtHostTextEditorOptions', () => { }); assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Block, lineNumbers: RenderLineNumbersType.Relative diff --git a/src/vscode-dts/vscode.d.ts b/src/vscode-dts/vscode.d.ts index 8ddc0b0377f..9476595d896 100644 --- a/src/vscode-dts/vscode.d.ts +++ b/src/vscode-dts/vscode.d.ts @@ -641,13 +641,22 @@ declare module 'vscode' { /** * The size in spaces a tab takes. This is used for two purposes: * - the rendering width of a tab character; - * - the number of spaces to insert when {@link TextEditorOptions.insertSpaces insertSpaces} is true. + * - the number of spaces to insert when {@link TextEditorOptions.insertSpaces insertSpaces} is true + * and `indentSize` is set to `"tab"`. * * When getting a text editor's options, this property will always be a number (resolved). * When setting a text editor's options, this property is optional and it can be a number or `"auto"`. */ tabSize?: number | string; + /** + * The number of spaces to insert when [insertSpaces](#TextEditorOptions.insertSpaces) is true. + * + * When getting a text editor's options, this property will always be a number (resolved). + * When setting a text editor's options, this property is optional and it can be a number or `"tabSize"`. + */ + indentSize?: number | string; + /** * When pressing Tab insert {@link TextEditorOptions.tabSize n} spaces. * When getting a text editor's options, this property will always be a boolean (resolved).