Skip to content

Commit 5f70fdd

Browse files
dlechalexdima
authored andcommitted
Add editor.indentSize option
This is an attempt to address issue microsoft#10339. Background: Currently, the `editor.tabSize` option does two things - it specifies the width of the tab character and it specifies how many columns to advance when the tab key is pressed. However, there is code in the wild that has a mix of spaces and tabs that expects these two values to be different. These generally use and indent size of 2 or 4 and spaces are used for indentation until the indent becomes >= 8. The tab character size is excpected to be 8 and groups of 8 spaces are replaced with a tab character. Indent levels end up looking like 2 spaces, 4 spaces, 6 spaces, 1 tab, 1 tab + 2 spaces, and so on. Implementation: In the editor options, a new option, `editor.indentSize` is added. This, in conjunction with `editor.tabSize` has the same semantics as `indent_size` and `tab_width` in the well known [EditorConfig specification][1]. > indent_size: a whole number defining the number of columns used for each indentation level and the width of soft tabs (when supported). When set to "tab", the value of tab_width (if specified) will be used. > > tab_width: a whole number defining the number of columns used to represent a tab character. This defaults to the value of indent_size and doesn't usually need to be specified. [1]: editorconfig.org The new `indentSize` option takes a numeric value or "tab" just as EditorConfig's `indent_size`. The default value is set to "tab" so that current default behavior of VS Code does not change and existing user settings will not break. When getting the new `indentSize` option programatically, it always returns a numeric value (just as `tabSize` does when set to the deprecated "auto" value). In the text editor model, a new property is added for `indentSize`. Unlike the configuration options where the value of one property influences the other, In this code `tabSize` now should only mean "the width of the tab character" and `indentSize` should only mean "how may columns is one indent". The cursor operations and shift command are updated to reflect these new semantics.
1 parent 3d662dd commit 5f70fdd

25 files changed

Lines changed: 367 additions & 87 deletions

File tree

src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,11 @@ export class IndentGuidesOverlay extends DynamicViewOverlay {
103103

104104
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
105105
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
106-
const tabSize = this._context.model.getTabSize();
107-
const tabWidth = tabSize * this._spaceWidth;
106+
const indentSize = this._context.model.getIndentSize();
107+
const indentWidth = indentSize * this._spaceWidth;
108108
const scrollWidth = ctx.scrollWidth;
109109
const lineHeight = this._lineHeight;
110-
const indentGuideWidth = tabWidth;
110+
const indentGuideWidth = indentWidth;
111111

112112
const indents = this._context.model.getLinesIndentGuides(visibleStartLineNumber, visibleEndLineNumber);
113113

@@ -133,7 +133,7 @@ export class IndentGuidesOverlay extends DynamicViewOverlay {
133133
for (let i = 1; i <= indent; i++) {
134134
let className = (containsActiveIndentGuide && i === activeIndentLevel ? 'cigra' : 'cigr');
135135
result += `<div class="${className}" style="left:${left}px;height:${lineHeight}px;width:${indentGuideWidth}px"></div>`;
136-
left += tabWidth;
136+
left += indentWidth;
137137
if (left > scrollWidth) {
138138
break;
139139
}

src/vs/editor/common/commands/shiftCommand.ts

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,31 @@ import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageCo
1414

1515
export interface IShiftCommandOpts {
1616
isUnshift: boolean;
17-
tabSize: number;
17+
indentSize: number;
1818
oneIndent: string;
1919
useTabStops: boolean;
2020
}
2121

2222
export class ShiftCommand implements ICommand {
2323

24-
public static unshiftIndentCount(line: string, column: number, tabSize: number): number {
24+
public static unshiftIndentCount(line: string, column: number, indentSize: number): number {
2525
// Determine the visible column where the content starts
26-
let contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(line, column, tabSize);
26+
let contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(line, column, indentSize);
2727

28-
let desiredTabStop = CursorColumns.prevTabStop(contentStartVisibleColumn, tabSize);
28+
let desiredTabStop = CursorColumns.prevTabStop(contentStartVisibleColumn, indentSize);
2929

30-
// The `desiredTabStop` is a multiple of `tabSize` => determine the number of indents
31-
return desiredTabStop / tabSize;
30+
// The `desiredTabStop` is a multiple of `indentSize` => determine the number of indents
31+
return desiredTabStop / indentSize;
3232
}
3333

34-
public static shiftIndentCount(line: string, column: number, tabSize: number): number {
34+
public static shiftIndentCount(line: string, column: number, indentSize: number): number {
3535
// Determine the visible column where the content starts
36-
let contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(line, column, tabSize);
36+
let contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(line, column, indentSize);
3737

38-
let desiredTabStop = CursorColumns.nextTabStop(contentStartVisibleColumn, tabSize);
38+
let desiredTabStop = CursorColumns.nextTabStop(contentStartVisibleColumn, indentSize);
3939

40-
// The `desiredTabStop` is a multiple of `tabSize` => determine the number of indents
41-
return desiredTabStop / tabSize;
40+
// The `desiredTabStop` is a multiple of `indentSize` => determine the number of indents
41+
return desiredTabStop / indentSize;
4242
}
4343

4444
private _opts: IShiftCommandOpts;
@@ -70,7 +70,7 @@ export class ShiftCommand implements ICommand {
7070
endLine = endLine - 1;
7171
}
7272

73-
const tabSize = this._opts.tabSize;
73+
const indentSize = this._opts.indentSize;
7474
const oneIndent = this._opts.oneIndent;
7575
const shouldIndentEmptyLines = (startLine === endLine);
7676

@@ -108,16 +108,16 @@ export class ShiftCommand implements ICommand {
108108
}
109109

110110
if (lineNumber > 1) {
111-
let contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(lineText, indentationEndIndex + 1, tabSize);
112-
if (contentStartVisibleColumn % tabSize !== 0) {
111+
let contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(lineText, indentationEndIndex + 1, indentSize);
112+
if (contentStartVisibleColumn % indentSize !== 0) {
113113
// The current line is "miss-aligned", so let's see if this is expected...
114114
// This can only happen when it has trailing commas in the indent
115115
if (model.isCheapToTokenize(lineNumber - 1)) {
116116
let enterAction = LanguageConfigurationRegistry.getRawEnterActionAtPosition(model, lineNumber - 1, model.getLineMaxColumn(lineNumber - 1));
117117
if (enterAction) {
118118
extraSpaces = previousLineExtraSpaces;
119119
if (enterAction.appendText) {
120-
for (let j = 0, lenJ = enterAction.appendText.length; j < lenJ && extraSpaces < tabSize; j++) {
120+
for (let j = 0, lenJ = enterAction.appendText.length; j < lenJ && extraSpaces < indentSize; j++) {
121121
if (enterAction.appendText.charCodeAt(j) === CharCode.Space) {
122122
extraSpaces++;
123123
} else {
@@ -149,9 +149,9 @@ export class ShiftCommand implements ICommand {
149149

150150
let desiredIndentCount: number;
151151
if (this._opts.isUnshift) {
152-
desiredIndentCount = ShiftCommand.unshiftIndentCount(lineText, indentationEndIndex + 1, tabSize);
152+
desiredIndentCount = ShiftCommand.unshiftIndentCount(lineText, indentationEndIndex + 1, indentSize);
153153
} else {
154-
desiredIndentCount = ShiftCommand.shiftIndentCount(lineText, indentationEndIndex + 1, tabSize);
154+
desiredIndentCount = ShiftCommand.shiftIndentCount(lineText, indentationEndIndex + 1, indentSize);
155155
}
156156

157157
// Fill `indents`, as needed
@@ -193,7 +193,7 @@ export class ShiftCommand implements ICommand {
193193

194194
if (this._opts.isUnshift) {
195195

196-
indentationEndIndex = Math.min(indentationEndIndex, tabSize);
196+
indentationEndIndex = Math.min(indentationEndIndex, indentSize);
197197
for (let i = 0; i < indentationEndIndex; i++) {
198198
const chr = lineText.charCodeAt(i);
199199
if (chr === CharCode.Tab) {

src/vs/editor/common/config/commonEditorConfig.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,20 @@ const editorConfiguration: IConfigurationNode = {
288288
'minimum': 1,
289289
'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.")
290290
},
291+
'editor.indentSize': {
292+
'anyOf': [
293+
{
294+
'type': 'string',
295+
'enum': ['tab']
296+
},
297+
{
298+
'type': 'number',
299+
'minimum': 1
300+
}
301+
],
302+
'default': 'tab',
303+
'markdownDescription': nls.localize('indentSize', "The number of spaces used for indentation or 'tab' to use the value from `#editor.tabSize#`. This setting is overridden based on the file contents when `#editor.detectIndentation#` is on.")
304+
},
291305
'editor.insertSpaces': {
292306
'type': 'boolean',
293307
'default': EDITOR_MODEL_DEFAULTS.insertSpaces,

src/vs/editor/common/config/editorOptions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2541,6 +2541,7 @@ export const EDITOR_FONT_DEFAULTS = {
25412541
*/
25422542
export const EDITOR_MODEL_DEFAULTS = {
25432543
tabSize: 4,
2544+
indentSize: 4,
25442545
insertSpaces: true,
25452546
detectIndentation: true,
25462547
trimAutoWhitespace: true,

src/vs/editor/common/controller/cursorCommon.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class CursorConfiguration {
7373
_cursorMoveConfigurationBrand: void;
7474

7575
public readonly readOnly: boolean;
76-
public readonly tabSize: number;
76+
public readonly indentSize: number;
7777
public readonly insertSpaces: boolean;
7878
public readonly oneIndent: string;
7979
public readonly pageSize: number;
@@ -121,7 +121,7 @@ export class CursorConfiguration {
121121
let c = configuration.editor;
122122

123123
this.readOnly = c.readOnly;
124-
this.tabSize = modelOptions.tabSize;
124+
this.indentSize = modelOptions.indentSize;
125125
this.insertSpaces = modelOptions.insertSpaces;
126126
this.oneIndent = oneIndent;
127127
this.pageSize = Math.max(1, Math.floor(c.layoutInfo.height / c.fontInfo.lineHeight) - 2);
@@ -176,7 +176,7 @@ export class CursorConfiguration {
176176
}
177177

178178
public normalizeIndentation(str: string): string {
179-
return TextModel.normalizeIndentation(str, this.tabSize, this.insertSpaces);
179+
return TextModel.normalizeIndentation(str, this.indentSize, this.insertSpaces);
180180
}
181181

182182
private static _getElectricCharacters(languageIdentifier: LanguageIdentifier): string[] | null {
@@ -508,7 +508,7 @@ export class CursorColumns {
508508
return this.isHighSurrogate(model, lineNumber, column - 2);
509509
}
510510

511-
public static visibleColumnFromColumn(lineContent: string, column: number, tabSize: number): number {
511+
public static visibleColumnFromColumn(lineContent: string, column: number, indentSize: number): number {
512512
let endOffset = lineContent.length;
513513
if (endOffset > column - 1) {
514514
endOffset = column - 1;
@@ -518,7 +518,7 @@ export class CursorColumns {
518518
for (let i = 0; i < endOffset; i++) {
519519
let charCode = lineContent.charCodeAt(i);
520520
if (charCode === CharCode.Tab) {
521-
result = this.nextTabStop(result, tabSize);
521+
result = this.nextTabStop(result, indentSize);
522522
} else if (strings.isFullWidthCharacter(charCode)) {
523523
result = result + 2;
524524
} else {
@@ -529,10 +529,10 @@ export class CursorColumns {
529529
}
530530

531531
public static visibleColumnFromColumn2(config: CursorConfiguration, model: ICursorSimpleModel, position: Position): number {
532-
return this.visibleColumnFromColumn(model.getLineContent(position.lineNumber), position.column, config.tabSize);
532+
return this.visibleColumnFromColumn(model.getLineContent(position.lineNumber), position.column, config.indentSize);
533533
}
534534

535-
public static columnFromVisibleColumn(lineContent: string, visibleColumn: number, tabSize: number): number {
535+
public static columnFromVisibleColumn(lineContent: string, visibleColumn: number, indentSize: number): number {
536536
if (visibleColumn <= 0) {
537537
return 1;
538538
}
@@ -545,7 +545,7 @@ export class CursorColumns {
545545

546546
let afterVisibleColumn: number;
547547
if (charCode === CharCode.Tab) {
548-
afterVisibleColumn = this.nextTabStop(beforeVisibleColumn, tabSize);
548+
afterVisibleColumn = this.nextTabStop(beforeVisibleColumn, indentSize);
549549
} else if (strings.isFullWidthCharacter(charCode)) {
550550
afterVisibleColumn = beforeVisibleColumn + 2;
551551
} else {
@@ -570,7 +570,7 @@ export class CursorColumns {
570570
}
571571

572572
public static columnFromVisibleColumn2(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, visibleColumn: number): number {
573-
let result = this.columnFromVisibleColumn(model.getLineContent(lineNumber), visibleColumn, config.tabSize);
573+
let result = this.columnFromVisibleColumn(model.getLineContent(lineNumber), visibleColumn, config.indentSize);
574574

575575
let minColumn = model.getLineMinColumn(lineNumber);
576576
if (result < minColumn) {
@@ -588,15 +588,15 @@ export class CursorColumns {
588588
/**
589589
* ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns)
590590
*/
591-
public static nextTabStop(visibleColumn: number, tabSize: number): number {
592-
return visibleColumn + tabSize - visibleColumn % tabSize;
591+
public static nextTabStop(visibleColumn: number, indentSize: number): number {
592+
return visibleColumn + indentSize - visibleColumn % indentSize;
593593
}
594594

595595
/**
596596
* ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns)
597597
*/
598-
public static prevTabStop(column: number, tabSize: number): number {
599-
return column - 1 - (column - 1) % tabSize;
598+
public static prevTabStop(column: number, indentSize: number): number {
599+
return column - 1 - (column - 1) % indentSize;
600600
}
601601
}
602602

src/vs/editor/common/controller/cursorDeleteOperations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export class DeleteOperations {
131131

132132
if (position.column <= lastIndentationColumn) {
133133
let fromVisibleColumn = CursorColumns.visibleColumnFromColumn2(config, model, position);
134-
let toVisibleColumn = CursorColumns.prevTabStop(fromVisibleColumn, config.tabSize);
134+
let toVisibleColumn = CursorColumns.prevTabStop(fromVisibleColumn, config.indentSize);
135135
let toColumn = CursorColumns.columnFromVisibleColumn2(config, model, position.lineNumber, toVisibleColumn);
136136
deleteSelection = new Range(position.lineNumber, toColumn, position.lineNumber, position.column);
137137
} else {

src/vs/editor/common/controller/cursorMoveOperations.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export class MoveOperations {
9292
}
9393

9494
public static down(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnLastLine: boolean): CursorPosition {
95-
const currentVisibleColumn = CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize) + leftoverVisibleColumns;
95+
const currentVisibleColumn = CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.indentSize) + leftoverVisibleColumns;
9696

9797
lineNumber = lineNumber + count;
9898
let lineCount = model.getLineCount();
@@ -113,7 +113,7 @@ export class MoveOperations {
113113
}
114114
}
115115

116-
leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize);
116+
leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.indentSize);
117117

118118
return new CursorPosition(lineNumber, column, leftoverVisibleColumns);
119119
}
@@ -151,7 +151,7 @@ export class MoveOperations {
151151
}
152152

153153
public static up(config: CursorConfiguration, model: ICursorSimpleModel, lineNumber: number, column: number, leftoverVisibleColumns: number, count: number, allowMoveOnFirstLine: boolean): CursorPosition {
154-
const currentVisibleColumn = CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize) + leftoverVisibleColumns;
154+
const currentVisibleColumn = CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.indentSize) + leftoverVisibleColumns;
155155

156156
lineNumber = lineNumber - count;
157157
if (lineNumber < 1) {
@@ -171,7 +171,7 @@ export class MoveOperations {
171171
}
172172
}
173173

174-
leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.tabSize);
174+
leftoverVisibleColumns = currentVisibleColumn - CursorColumns.visibleColumnFromColumn(model.getLineContent(lineNumber), column, config.indentSize);
175175

176176
return new CursorPosition(lineNumber, column, leftoverVisibleColumns);
177177
}

src/vs/editor/common/controller/cursorTypeOperations.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class TypeOperations {
3030
for (let i = 0, len = selections.length; i < len; i++) {
3131
commands[i] = new ShiftCommand(selections[i], {
3232
isUnshift: false,
33-
tabSize: config.tabSize,
33+
indentSize: config.indentSize,
3434
oneIndent: config.oneIndent,
3535
useTabStops: config.useTabStops
3636
});
@@ -43,7 +43,7 @@ export class TypeOperations {
4343
for (let i = 0, len = selections.length; i < len; i++) {
4444
commands[i] = new ShiftCommand(selections[i], {
4545
isUnshift: true,
46-
tabSize: config.tabSize,
46+
indentSize: config.indentSize,
4747
oneIndent: config.oneIndent,
4848
useTabStops: config.useTabStops
4949
});
@@ -53,7 +53,7 @@ export class TypeOperations {
5353

5454
public static shiftIndent(config: CursorConfiguration, indentation: string, count?: number): string {
5555
count = count || 1;
56-
let desiredIndentCount = ShiftCommand.shiftIndentCount(indentation, indentation.length + count, config.tabSize);
56+
let desiredIndentCount = ShiftCommand.shiftIndentCount(indentation, indentation.length + count, config.indentSize);
5757
let newIndentation = '';
5858
for (let i = 0; i < desiredIndentCount; i++) {
5959
newIndentation += '\t';
@@ -64,7 +64,7 @@ export class TypeOperations {
6464

6565
public static unshiftIndent(config: CursorConfiguration, indentation: string, count?: number): string {
6666
count = count || 1;
67-
let desiredIndentCount = ShiftCommand.unshiftIndentCount(indentation, indentation.length + count, config.tabSize);
67+
let desiredIndentCount = ShiftCommand.unshiftIndentCount(indentation, indentation.length + count, config.indentSize);
6868
let newIndentation = '';
6969
for (let i = 0; i < desiredIndentCount; i++) {
7070
newIndentation += '\t';
@@ -209,8 +209,8 @@ export class TypeOperations {
209209
let position = selection.getStartPosition();
210210
if (config.insertSpaces) {
211211
let visibleColumnFromColumn = CursorColumns.visibleColumnFromColumn2(config, model, position);
212-
let tabSize = config.tabSize;
213-
let spacesCnt = tabSize - (visibleColumnFromColumn % tabSize);
212+
let indentSize = config.indentSize;
213+
let spacesCnt = indentSize - (visibleColumnFromColumn % indentSize);
214214
for (let i = 0; i < spacesCnt; i++) {
215215
typeText += ' ';
216216
}
@@ -253,7 +253,7 @@ export class TypeOperations {
253253

254254
commands[i] = new ShiftCommand(selection, {
255255
isUnshift: false,
256-
tabSize: config.tabSize,
256+
indentSize: config.indentSize,
257257
oneIndent: config.oneIndent,
258258
useTabStops: config.useTabStops
259259
});
@@ -377,7 +377,7 @@ export class TypeOperations {
377377
let offset = 0;
378378
if (oldEndColumn <= firstNonWhitespace + 1) {
379379
if (!config.insertSpaces) {
380-
oldEndViewColumn = Math.ceil(oldEndViewColumn / config.tabSize);
380+
oldEndViewColumn = Math.ceil(oldEndViewColumn / config.indentSize);
381381
}
382382
offset = Math.min(oldEndViewColumn + 1 - config.normalizeIndentation(ir.afterEnter).length - 1, 0);
383383
}

src/vs/editor/common/model.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ export class TextModelResolvedOptions {
346346
_textModelResolvedOptionsBrand: void;
347347

348348
readonly tabSize: number;
349+
readonly indentSize: number;
349350
readonly insertSpaces: boolean;
350351
readonly defaultEOL: DefaultEndOfLine;
351352
readonly trimAutoWhitespace: boolean;
@@ -355,11 +356,13 @@ export class TextModelResolvedOptions {
355356
*/
356357
constructor(src: {
357358
tabSize: number;
359+
indentSize: number;
358360
insertSpaces: boolean;
359361
defaultEOL: DefaultEndOfLine;
360362
trimAutoWhitespace: boolean;
361363
}) {
362364
this.tabSize = src.tabSize | 0;
365+
this.indentSize = src.indentSize | 0;
363366
this.insertSpaces = Boolean(src.insertSpaces);
364367
this.defaultEOL = src.defaultEOL | 0;
365368
this.trimAutoWhitespace = Boolean(src.trimAutoWhitespace);
@@ -383,6 +386,7 @@ export class TextModelResolvedOptions {
383386
public createChangeEvent(newOpts: TextModelResolvedOptions): IModelOptionsChangedEvent {
384387
return {
385388
tabSize: this.tabSize !== newOpts.tabSize,
389+
indentSize: this.indentSize !== newOpts.indentSize,
386390
insertSpaces: this.insertSpaces !== newOpts.insertSpaces,
387391
trimAutoWhitespace: this.trimAutoWhitespace !== newOpts.trimAutoWhitespace,
388392
};
@@ -394,6 +398,7 @@ export class TextModelResolvedOptions {
394398
*/
395399
export interface ITextModelCreationOptions {
396400
tabSize: number;
401+
indentSize: number;
397402
insertSpaces: boolean;
398403
detectIndentation: boolean;
399404
trimAutoWhitespace: boolean;
@@ -404,6 +409,7 @@ export interface ITextModelCreationOptions {
404409

405410
export interface ITextModelUpdateOptions {
406411
tabSize?: number;
412+
indentSize?: number;
407413
insertSpaces?: boolean;
408414
trimAutoWhitespace?: boolean;
409415
}

0 commit comments

Comments
 (0)