diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts
index e5786f31335..be200c6d0ce 100644
--- a/src/harness/fourslash.ts
+++ b/src/harness/fourslash.ts
@@ -310,6 +310,7 @@ namespace FourSlash {
}
this.formatCodeOptions = {
+ BaseIndentSize: 0,
IndentSize: 4,
TabSize: 4,
NewLineCharacter: Harness.IO.newLine(),
@@ -1659,24 +1660,25 @@ namespace FourSlash {
}
}
- private getIndentation(fileName: string, position: number, indentStyle: ts.IndentStyle): number {
+ private getIndentation(fileName: string, position: number, indentStyle: ts.IndentStyle, baseIndentSize: number): number {
const formatOptions = ts.clone(this.formatCodeOptions);
formatOptions.IndentStyle = indentStyle;
+ formatOptions.BaseIndentSize = baseIndentSize;
return this.languageService.getIndentationAtPosition(fileName, position, formatOptions);
}
- public verifyIndentationAtCurrentPosition(numberOfSpaces: number, indentStyle: ts.IndentStyle = ts.IndentStyle.Smart) {
- const actual = this.getIndentation(this.activeFile.fileName, this.currentCaretPosition, indentStyle);
+ public verifyIndentationAtCurrentPosition(numberOfSpaces: number, indentStyle: ts.IndentStyle = ts.IndentStyle.Smart, baseIndentSize = 0) {
+ const actual = this.getIndentation(this.activeFile.fileName, this.currentCaretPosition, indentStyle, baseIndentSize);
const lineCol = this.getLineColStringAtPosition(this.currentCaretPosition);
if (actual !== numberOfSpaces) {
this.raiseError(`verifyIndentationAtCurrentPosition failed at ${lineCol} - expected: ${numberOfSpaces}, actual: ${actual}`);
}
}
- public verifyIndentationAtPosition(fileName: string, position: number, numberOfSpaces: number, indentStyle: ts.IndentStyle = ts.IndentStyle.Smart) {
- const actual = this.getIndentation(fileName, position, indentStyle);
+ public verifyIndentationAtPosition(fileName: string, position: number, numberOfSpaces: number, indentStyle: ts.IndentStyle = ts.IndentStyle.Smart, baseIndentSize = 0) {
+ const actual = this.getIndentation(fileName, position, indentStyle, baseIndentSize);
const lineCol = this.getLineColStringAtPosition(position);
if (actual !== numberOfSpaces) {
this.raiseError(`verifyIndentationAtPosition failed at ${lineCol} - expected: ${numberOfSpaces}, actual: ${actual}`);
@@ -2938,8 +2940,8 @@ namespace FourSlashInterface {
this.state.verifyIndentationAtCurrentPosition(numberOfSpaces);
}
- public indentationAtPositionIs(fileName: string, position: number, numberOfSpaces: number, indentStyle = ts.IndentStyle.Smart) {
- this.state.verifyIndentationAtPosition(fileName, position, numberOfSpaces, indentStyle);
+ public indentationAtPositionIs(fileName: string, position: number, numberOfSpaces: number, indentStyle = ts.IndentStyle.Smart, baseIndentSize = 0) {
+ this.state.verifyIndentationAtPosition(fileName, position, numberOfSpaces, indentStyle, baseIndentSize);
}
public textAtCaretIs(text: string) {
diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts
index 9c0662e5534..e48d6192017 100644
--- a/src/server/editorServices.ts
+++ b/src/server/editorServices.ts
@@ -1566,6 +1566,7 @@ namespace ts.server {
static getDefaultFormatCodeOptions(host: ServerHost): ts.FormatCodeOptions {
return ts.clone({
+ BaseIndentSize: 0,
IndentSize: 4,
TabSize: 4,
NewLineCharacter: host.newLine || "\n",
diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts
index 62162b3237c..6442848abbe 100644
--- a/src/server/protocol.d.ts
+++ b/src/server/protocol.d.ts
@@ -438,6 +438,9 @@ declare namespace ts.server.protocol {
/** Number of spaces to indent during formatting. Default value is 4. */
indentSize?: number;
+ /** Number of additional spaces to indent during formatting to preserve base indentation (ex. script block indentation). Default value is 0. */
+ baseIndentSize?: number;
+
/** The new line character to be used. Default value is the OS line delimiter. */
newLineCharacter?: string;
@@ -478,7 +481,7 @@ declare namespace ts.server.protocol {
placeOpenBraceOnNewLineForControlBlocks?: boolean;
/** Index operator */
- [key: string]: string | number | boolean;
+ [key: string]: string | number | boolean | undefined;
}
/**
diff --git a/src/server/session.ts b/src/server/session.ts
index 7a779383352..7e1ca81e2af 100644
--- a/src/server/session.ts
+++ b/src/server/session.ts
@@ -703,6 +703,7 @@ namespace ts.server {
if (lineText.search("\\S") < 0) {
// TODO: get these options from host
const editorOptions: ts.EditorOptions = {
+ BaseIndentSize: formatOptions.BaseIndentSize,
IndentSize: formatOptions.IndentSize,
TabSize: formatOptions.TabSize,
NewLineCharacter: formatOptions.NewLineCharacter,
diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts
index ef8fddcfb3a..5dae6393b99 100644
--- a/src/services/formatting/formatting.ts
+++ b/src/services/formatting/formatting.ts
@@ -394,7 +394,10 @@ namespace ts.formatting {
const startLinePosition = getLineStartPositionForPosition(startPos, sourceFile);
const column = SmartIndenter.findFirstNonWhitespaceColumn(startLinePosition, startPos, sourceFile, options);
if (startLine !== parentStartLine || startPos === column) {
- return column;
+ // Use the base indent size if it is greater than
+ // the indentation of the inherited predecessor.
+ const baseIndentSize = SmartIndenter.getBaseIndentation(options);
+ return baseIndentSize > column ? baseIndentSize : column;
}
}
diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts
index 23a1d937869..d25182c73df 100644
--- a/src/services/formatting/smartIndenter.ts
+++ b/src/services/formatting/smartIndenter.ts
@@ -10,7 +10,7 @@ namespace ts.formatting {
export function getIndentation(position: number, sourceFile: SourceFile, options: EditorOptions): number {
if (position > sourceFile.text.length) {
- return 0; // past EOF
+ return getBaseIndentation(options); // past EOF
}
// no indentation when the indent style is set to none,
@@ -21,7 +21,7 @@ namespace ts.formatting {
const precedingToken = findPrecedingToken(position, sourceFile);
if (!precedingToken) {
- return 0;
+ return getBaseIndentation(options);
}
// no indentation in string \regex\template literals
@@ -96,13 +96,17 @@ namespace ts.formatting {
}
if (!current) {
- // no parent was found - return 0 to be indented on the level of SourceFile
- return 0;
+ // no parent was found - return the base indentation of the SourceFile
+ return getBaseIndentation(options);
}
return getIndentationForNodeWorker(current, currentStart, /*ignoreActualIndentationRange*/ undefined, indentationDelta, sourceFile, options);
}
+ export function getBaseIndentation(options: EditorOptions) {
+ return options.BaseIndentSize || 0;
+ }
+
export function getIndentationForNode(n: Node, ignoreActualIndentationRange: TextRange, sourceFile: SourceFile, options: FormatCodeOptions): number {
const start = sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile));
return getIndentationForNodeWorker(n, start, ignoreActualIndentationRange, /*indentationDelta*/ 0, sourceFile, options);
@@ -162,7 +166,7 @@ namespace ts.formatting {
parent = current.parent;
}
- return indentationDelta;
+ return indentationDelta + getBaseIndentation(options);
}
diff --git a/src/services/services.ts b/src/services/services.ts
index 492be6f1719..d9425c9df50 100644
--- a/src/services/services.ts
+++ b/src/services/services.ts
@@ -1244,6 +1244,7 @@ namespace ts {
}
export interface EditorOptions {
+ BaseIndentSize?: number;
IndentSize: number;
TabSize: number;
NewLineCharacter: string;
@@ -1268,7 +1269,7 @@ namespace ts {
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: boolean;
PlaceOpenBraceOnNewLineForFunctions: boolean;
PlaceOpenBraceOnNewLineForControlBlocks: boolean;
- [s: string]: boolean | number | string;
+ [s: string]: boolean | number | string | undefined;
}
export interface DefinitionInfo {
diff --git a/tests/cases/fourslash/formatWithBaseIndent.ts b/tests/cases/fourslash/formatWithBaseIndent.ts
new file mode 100644
index 00000000000..660131996c9
--- /dev/null
+++ b/tests/cases/fourslash/formatWithBaseIndent.ts
@@ -0,0 +1,264 @@
+///
+
+////
+/////*1*/ module classes {
+/////*2*/ class Bar {
+////
+/////*3*/ constructor() {
+/////*4*/ }
+////
+/////*5*/private foo: string = "";
+////
+/////*6*/ private f() {
+/////*7*/ var a: any[] = [[1, 2], [3, 4], 5];
+/////*8*/ return ((1 + 1));
+/////*9*/ }
+////
+/////*10*/ private f2() {
+/////*11*/ if (true) { } { };
+/////*12*/ }
+/////*13*/ }
+/////*14*/ }
+////
+////
+/////*15*/ module interfaces {
+/////*16*/ interface Foo {
+////
+/////*17*/ x: number;
+////
+/////*18*/ foo(): number;
+/////*19*/ }
+/////*20*/ }
+////
+////
+/////*21*/ module nestedModules {
+/////*22*/ module Foo2 {
+/////*23*/ function f() {
+/////*24*/ }
+/////*25*/ var x: number;
+/////*26*/}
+/////*27*/ }
+////
+////
+/////*28*/ module Enums {
+/////*29*/ enum Foo3 {
+/////*30*/ val1 ,
+/////*31*/ val2,
+/////*32*/ }
+/////*33*/ }
+////
+////
+/////*34*/ function controlStatements() {
+/////*35*/ for (var i = 0; i < 10; i++) {
+/////*36*/ }
+////
+/////*37*/ for (var e in foo.bar) {
+/////*38*/ }
+////
+/////*39*/with (foo.bar) {
+/////*40*/ }
+////
+/////*41*/ while (false) {
+/////*42*/ }
+////
+/////*43*/ do {
+/////*44*/ } while (false);
+////
+/////*45*/ switch (foo.bar) {
+/////*46*/ }
+////
+/////*47*/ switch (foo.bar) {
+/////*48*/ case 1:
+/////*49*/ break;
+/////*50*/ default:
+/////*51*/ break;
+/////*52*/ }
+/////*53*/ }
+////
+////
+/////*54*/ function tryCatch() {
+/////*55*/try {
+/////*56*/}
+/////*57*/catch (err) {
+/////*58*/ }
+/////*59*/ }
+////
+////
+/////*60*/ function tryFinally() {
+/////*61*/ try {
+/////*62*/ }
+/////*63*/ finally {
+/////*64*/ }
+/////*65*/ }
+////
+////
+/////*66*/ function tryCatchFinally() {
+/////*67*/ try {
+/////*68*/ }
+/////*69*/ catch (err) {
+/////*70*/ }
+/////*71*/ finally {
+/////*72*/ }
+/////*73*/ }
+////
+////
+/////*74*/ class indentBeforeCurly
+/////*75*/ {
+/////*76*/ }
+////
+////
+/////*77*/ function argumentsListIndentation(bar,
+/////*78*/ blah,
+/////*79*/ );
+////
+////
+/////*80*/ function blockIndentAfterIndentedParameter1(bar,
+/////*81*/ blah) {
+/////*82*/ }
+////
+////
+/////*83*/ function blockIndentAfterIndentedParameter2(bar,
+/////*84*/ blah) {
+/////*85*/ if (foo) {
+/////*86*/ }
+/////*87*/}
+////
+/////*88*/ var templateLiterals = `abcdefghi
+/////*89*/jklmnop
+/////*90*/qrstuvwxyz`;
+
+var originalOptions = format.copyFormatOptions();
+var copy = format.copyFormatOptions();
+copy.BaseIndentSize = 10;
+copy.IndentSize = 4;
+
+format.setFormatOptions(copy);
+format.document();
+
+verify.currentFileContentIs(`
+ module classes {
+ class Bar {
+
+ constructor() {
+ }
+
+ private foo: string = "";
+
+ private f() {
+ var a: any[] = [[1, 2], [3, 4], 5];
+ return ((1 + 1));
+ }
+
+ private f2() {
+ if (true) { } { };
+ }
+ }
+ }
+
+
+ module interfaces {
+ interface Foo {
+
+ x: number;
+
+ foo(): number;
+ }
+ }
+
+
+ module nestedModules {
+ module Foo2 {
+ function f() {
+ }
+ var x: number;
+ }
+ }
+
+
+ module Enums {
+ enum Foo3 {
+ val1,
+ val2,
+ }
+ }
+
+
+ function controlStatements() {
+ for (var i = 0; i < 10; i++) {
+ }
+
+ for (var e in foo.bar) {
+ }
+
+ with (foo.bar) {
+ }
+
+ while (false) {
+ }
+
+ do {
+ } while (false);
+
+ switch (foo.bar) {
+ }
+
+ switch (foo.bar) {
+ case 1:
+ break;
+ default:
+ break;
+ }
+ }
+
+
+ function tryCatch() {
+ try {
+ }
+ catch (err) {
+ }
+ }
+
+
+ function tryFinally() {
+ try {
+ }
+ finally {
+ }
+ }
+
+
+ function tryCatchFinally() {
+ try {
+ }
+ catch (err) {
+ }
+ finally {
+ }
+ }
+
+
+ class indentBeforeCurly {
+ }
+
+
+ function argumentsListIndentation(bar,
+ blah,
+ );
+
+
+ function blockIndentAfterIndentedParameter1(bar,
+ blah) {
+ }
+
+
+ function blockIndentAfterIndentedParameter2(bar,
+ blah) {
+ if (foo) {
+ }
+ }
+
+ var templateLiterals = \`abcdefghi
+jklmnop
+qrstuvwxyz\`;`);
+
+format.setFormatOptions(originalOptions);
\ No newline at end of file
diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts
index 9cb816a13bd..3a1d2e9839d 100644
--- a/tests/cases/fourslash/fourslash.ts
+++ b/tests/cases/fourslash/fourslash.ts
@@ -68,6 +68,7 @@ declare namespace FourSlashInterface {
data?: any;
}
interface EditorOptions {
+ BaseIndentSize?: number,
IndentSize: number;
TabSize: number;
NewLineCharacter: string;
@@ -84,7 +85,7 @@ declare namespace FourSlashInterface {
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: boolean;
PlaceOpenBraceOnNewLineForFunctions: boolean;
PlaceOpenBraceOnNewLineForControlBlocks: boolean;
- [s: string]: boolean | number | string;
+ [s: string]: boolean | number | string | undefined;
}
interface Range {
fileName: string;
@@ -141,7 +142,7 @@ declare namespace FourSlashInterface {
assertHasRanges(ranges: Range[]): void;
caretAtMarker(markerName?: string): void;
indentationIs(numberOfSpaces: number): void;
- indentationAtPositionIs(fileName: string, position: number, numberOfSpaces: number, indentStyle?: ts.IndentStyle): void;
+ indentationAtPositionIs(fileName: string, position: number, numberOfSpaces: number, indentStyle?: ts.IndentStyle, baseIndentSize?: number): void;
textAtCaretIs(text: string): void;
/**
* Compiles the current file and evaluates 'expr' in a context containing
diff --git a/tests/cases/fourslash/indentationWithBaseIndent.ts b/tests/cases/fourslash/indentationWithBaseIndent.ts
new file mode 100644
index 00000000000..53ee01c9a3c
--- /dev/null
+++ b/tests/cases/fourslash/indentationWithBaseIndent.ts
@@ -0,0 +1,211 @@
+///
+
+////
+////{| "indent": 10 , "baseIndentSize": 10 |}
+//// module classes {
+////{| "indent": 14 , "baseIndentSize": 10 |}
+//// class Bar {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+////
+//// constructor() {
+////{| "indent": 22, "baseIndentSize": 10 |}
+//// }
+////
+//// private foo: string = "";
+////{| "indent": 18, "baseIndentSize": 10 |}
+////
+//// private f() {
+//// var a: any[] = [[1, 2], [3, 4], 5];
+////{| "indent": 22, "baseIndentSize": 10 |}
+//// return ((1 + 1));
+//// }
+////
+////{| "indent": 18, "baseIndentSize": 10 |}
+//// private f2() {
+//// if (true) { } { };
+//// }
+//// }
+//// }
+////
+////
+//// module interfaces {
+////{| "indent": 14 , "baseIndentSize": 10 |}
+//// interface Foo {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+////
+//// x: number;
+////{| "indent": 18 , "baseIndentSize": 10 |}
+////
+//// foo(): number;
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// }
+//// }
+////
+////
+//// module nestedModules {
+//// module Foo2 {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// function f() {
+//// }
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// var x: number;
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// }
+//// }
+////
+////
+//// module Enums {
+//// enum Foo3 {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// val1,
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// val2,
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// }
+////{| "indent": 14 , "baseIndentSize": 10 |}
+//// }
+////
+////
+//// function controlStatements() {
+//// for (var i = 0; i < 10; i++) {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// }
+////
+//// for (var e in foo.bar) {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// }
+////
+//// with (foo.bar) {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// }
+////
+//// while (false) {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// }
+////
+//// do {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// } while (false);
+////
+//// switch (foo.bar) {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// }
+////
+//// switch (foo.bar) {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// case 1:
+////{| "indent": 22 , "baseIndentSize": 10 |}
+//// break;
+//// default:
+////{| "indent": 22 , "baseIndentSize": 10 |}
+//// break;
+//// }
+//// }
+////
+////
+//// function tryCatch() {
+////{| "indent": 14 , "baseIndentSize": 10 |}
+//// try {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// }
+////{| "indent": 14 , "baseIndentSize": 10 |}
+//// catch (err) {
+////{| "indent": 18, "baseIndentSize": 10 |}
+//// }
+////{| "indent": 14, "baseIndentSize": 10 |}
+//// }
+////
+////
+//// function tryFinally() {
+////{| "indent": 14 , "baseIndentSize": 10 |}
+//// try {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// }
+////{| "indent": 14 , "baseIndentSize": 10 |}
+//// finally {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// }
+////{| "indent": 14 , "baseIndentSize": 10 |}
+//// }
+////
+////
+//// function tryCatchFinally() {
+////{| "indent": 14 , "baseIndentSize": 10 |}
+//// try {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// }
+////{| "indent": 14 , "baseIndentSize": 10 |}
+//// catch (err) {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// }
+////{| "indent": 14 , "baseIndentSize": 10 |}
+//// finally {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// }
+////{| "indent": 14 , "baseIndentSize": 10 |}
+//// }
+////
+////
+//// class indentBeforeCurly
+////{| "indent": 10 , "baseIndentSize": 10 |}
+////{| "indent": 10 , "baseIndentSize": 10 |}
+//// {
+////{| "indent": 14 , "baseIndentSize": 10 |}
+//// }
+////{| "indent": 10 , "baseIndentSize": 10 |}
+////
+////
+//// function argumentsListIndentation(bar,
+//// blah,
+//// {| "indent": 14 , "baseIndentSize": 10 |}
+//// );
+////
+////
+//// function blockIndentAfterIndentedParameter1(bar,
+//// blah) {
+////{| "indent": 14 , "baseIndentSize": 10 |}
+//// }
+////
+////
+//// function blockIndentAfterIndentedParameter2(bar,
+//// blah) {
+//// if (foo) {
+////{| "indent": 18 , "baseIndentSize": 10 |}
+//// }
+////}
+////{| "indent": 10 , "baseIndentSize": 10 |}
+////
+//// var templateLiterals = `abcdefghi
+////{| "indent": 0 , "baseIndentSize": 10 |}
+////jklmnop
+////{| "indent": 0 , "baseIndentSize": 10 |}
+////qrstuvwxyz`;
+////{| "indent": 10 , "baseIndentSize": 10 |}
+////
+////
+//// module changeBaseIndentSizeInSameFile {
+////{| "indent": 21 , "baseIndentSize": 17 |}
+//// interface Foo {
+////{| "indent": 25 , "baseIndentSize": 17 |}
+////
+//// x: number;
+////{| "indent": 25 , "baseIndentSize": 10 |}
+////
+//// foo(): number;
+////{| "indent": 25 , "baseIndentSize": 10 |}
+//// }
+////{| "indent": 21 , "baseIndentSize": 10 |}
+//// }
+////{| "indent": 17 , "baseIndentSize": 17 |}
+////
+////
+////// Note: Do not add more tests at the end of this file, as
+////// the purpose of this test is to verify smart indent
+////// works for unterminated function arguments at the end of a file.
+//// function unterminatedListIndentation(a,
+////{| "indent": 14 , "baseIndentSize": 10 |}
+
+debugger;
+test.markers().forEach(marker => {
+ verify.indentationAtPositionIs(marker.fileName, marker.position, marker.data.indent, ts.IndentStyle.Smart, marker.data.baseIndentSize);
+});