Merge pull request #671 from Microsoft/new_smart_indentation
LS: smart indentation
This commit is contained in:
commit
fdc9b9d285
4
Jakefile
4
Jakefile
|
@ -318,7 +318,7 @@ function exec(cmd, completeHandler) {
|
||||||
complete();
|
complete();
|
||||||
})
|
})
|
||||||
try{
|
try{
|
||||||
ex.run();
|
ex.run();
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.log('Exception: ' + e)
|
console.log('Exception: ' + e)
|
||||||
}
|
}
|
||||||
|
@ -385,7 +385,7 @@ desc("Generates code coverage data via instanbul")
|
||||||
task("generate-code-coverage", ["tests", builtLocalDirectory], function () {
|
task("generate-code-coverage", ["tests", builtLocalDirectory], function () {
|
||||||
var cmd = 'istanbul cover node_modules/mocha/bin/_mocha -- -R min -t ' + testTimeout + ' ' + run;
|
var cmd = 'istanbul cover node_modules/mocha/bin/_mocha -- -R min -t ' + testTimeout + ' ' + run;
|
||||||
console.log(cmd);
|
console.log(cmd);
|
||||||
exec(cmd);
|
exec(cmd);
|
||||||
}, { async: true });
|
}, { async: true });
|
||||||
|
|
||||||
// Browser tests
|
// Browser tests
|
||||||
|
|
|
@ -50,8 +50,8 @@ module ts {
|
||||||
return node.pos;
|
return node.pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTokenPosOfNode(node: Node): number {
|
export function getTokenPosOfNode(node: Node, sourceFile?: SourceFile): number {
|
||||||
return skipTrivia(getSourceFileOfNode(node).text, node.pos);
|
return skipTrivia((sourceFile || getSourceFileOfNode(node)).text, node.pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSourceTextOfNodeFromSourceText(sourceText: string, node: Node): string {
|
export function getSourceTextOfNodeFromSourceText(sourceText: string, node: Node): string {
|
||||||
|
@ -487,6 +487,32 @@ module ts {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isStatement(n: Node): boolean {
|
||||||
|
switch(n.kind) {
|
||||||
|
case SyntaxKind.BreakStatement:
|
||||||
|
case SyntaxKind.ContinueStatement:
|
||||||
|
case SyntaxKind.DebuggerStatement:
|
||||||
|
case SyntaxKind.DoStatement:
|
||||||
|
case SyntaxKind.ExpressionStatement:
|
||||||
|
case SyntaxKind.EmptyStatement:
|
||||||
|
case SyntaxKind.ForInStatement:
|
||||||
|
case SyntaxKind.ForStatement:
|
||||||
|
case SyntaxKind.IfStatement:
|
||||||
|
case SyntaxKind.LabeledStatement:
|
||||||
|
case SyntaxKind.ReturnStatement:
|
||||||
|
case SyntaxKind.SwitchStatement:
|
||||||
|
case SyntaxKind.ThrowKeyword:
|
||||||
|
case SyntaxKind.TryStatement:
|
||||||
|
case SyntaxKind.VariableStatement:
|
||||||
|
case SyntaxKind.WhileStatement:
|
||||||
|
case SyntaxKind.WithStatement:
|
||||||
|
case SyntaxKind.ExportAssignment:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// True if the given identifier, string literal, or number literal is the name of a declaration node
|
// True if the given identifier, string literal, or number literal is the name of a declaration node
|
||||||
export function isDeclarationOrFunctionExpressionOrCatchVariableName(name: Node): boolean {
|
export function isDeclarationOrFunctionExpressionOrCatchVariableName(name: Node): boolean {
|
||||||
if (name.kind !== SyntaxKind.Identifier && name.kind !== SyntaxKind.StringLiteral && name.kind !== SyntaxKind.NumericLiteral) {
|
if (name.kind !== SyntaxKind.Identifier && name.kind !== SyntaxKind.StringLiteral && name.kind !== SyntaxKind.NumericLiteral) {
|
||||||
|
|
|
@ -223,6 +223,8 @@ module ts {
|
||||||
LastTypeNode = TupleType,
|
LastTypeNode = TupleType,
|
||||||
FirstPunctuation = OpenBraceToken,
|
FirstPunctuation = OpenBraceToken,
|
||||||
LastPunctuation = CaretEqualsToken,
|
LastPunctuation = CaretEqualsToken,
|
||||||
|
FirstToken = EndOfFileToken,
|
||||||
|
LastToken = StringKeyword
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NodeFlags {
|
export enum NodeFlags {
|
||||||
|
@ -988,6 +990,15 @@ module ts {
|
||||||
AMD,
|
AMD,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface LineAndCharacter {
|
||||||
|
line: number;
|
||||||
|
/*
|
||||||
|
* This value denotes the character position in line and is different from the 'column' because of tab characters.
|
||||||
|
*/
|
||||||
|
character: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export enum ScriptTarget {
|
export enum ScriptTarget {
|
||||||
ES3,
|
ES3,
|
||||||
ES5,
|
ES5,
|
||||||
|
|
|
@ -36,5 +36,4 @@
|
||||||
///<reference path='indentationNodeContextPool.ts' />
|
///<reference path='indentationNodeContextPool.ts' />
|
||||||
///<reference path='indentationTrackingWalker.ts' />
|
///<reference path='indentationTrackingWalker.ts' />
|
||||||
///<reference path='multipleTokenIndenter.ts' />
|
///<reference path='multipleTokenIndenter.ts' />
|
||||||
///<reference path='singleTokenIndenter.ts' />
|
|
||||||
///<reference path='formatter.ts' />
|
///<reference path='formatter.ts' />
|
|
@ -1,46 +0,0 @@
|
||||||
//
|
|
||||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
//
|
|
||||||
|
|
||||||
///<reference path='formatting.ts' />
|
|
||||||
|
|
||||||
module TypeScript.Services.Formatting {
|
|
||||||
export class SingleTokenIndenter extends IndentationTrackingWalker {
|
|
||||||
private indentationAmount: number = null;
|
|
||||||
private indentationPosition: number;
|
|
||||||
|
|
||||||
constructor(indentationPosition: number, sourceUnit: SourceUnitSyntax, snapshot: ITextSnapshot, indentFirstToken: boolean, options: FormattingOptions) {
|
|
||||||
super(new TextSpan(indentationPosition, 1), sourceUnit, snapshot, indentFirstToken, options);
|
|
||||||
|
|
||||||
this.indentationPosition = indentationPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getIndentationAmount(position: number, sourceUnit: SourceUnitSyntax, snapshot: ITextSnapshot, options: FormattingOptions): number {
|
|
||||||
var walker = new SingleTokenIndenter(position, sourceUnit, snapshot, true, options);
|
|
||||||
visitNodeOrToken(walker, sourceUnit);
|
|
||||||
return walker.indentationAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public indentToken(token: ISyntaxToken, indentationAmount: number, commentIndentationAmount: number): void {
|
|
||||||
// Compute an indentation string for this token
|
|
||||||
if (token.fullWidth() === 0 || (this.indentationPosition - this.position() < token.leadingTriviaWidth())) {
|
|
||||||
// The position is in the leading trivia, use comment indentation
|
|
||||||
this.indentationAmount = commentIndentationAmount;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.indentationAmount = indentationAmount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
522
src/services/formatting/smartIndenter.ts
Normal file
522
src/services/formatting/smartIndenter.ts
Normal file
|
@ -0,0 +1,522 @@
|
||||||
|
///<reference path='..\services.ts' />
|
||||||
|
|
||||||
|
module ts.formatting {
|
||||||
|
export module SmartIndenter {
|
||||||
|
|
||||||
|
export function getIndentation(position: number, sourceFile: SourceFile, options: TypeScript.FormattingOptions): number {
|
||||||
|
if (position > sourceFile.text.length) {
|
||||||
|
return 0; // past EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
var precedingToken = findPrecedingToken(position, sourceFile);
|
||||||
|
if (!precedingToken) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no indentation in string \regex literals
|
||||||
|
if ((precedingToken.kind === SyntaxKind.StringLiteral || precedingToken.kind === SyntaxKind.RegularExpressionLiteral) &&
|
||||||
|
precedingToken.getStart(sourceFile) <= position &&
|
||||||
|
precedingToken.end > position) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lineAtPosition = sourceFile.getLineAndCharacterFromPosition(position).line;
|
||||||
|
|
||||||
|
if (precedingToken.kind === SyntaxKind.CommaToken && precedingToken.parent.kind !== SyntaxKind.BinaryExpression) {
|
||||||
|
// previous token is comma that separates items in list - find the previous item and try to derive indentation from it
|
||||||
|
var actualIndentation = getActualIndentationForListItemBeforeComma(precedingToken, sourceFile, options);
|
||||||
|
if (actualIndentation !== -1) {
|
||||||
|
return actualIndentation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to find node that can contribute to indentation and includes 'position' starting from 'precedingToken'
|
||||||
|
// if such node is found - compute initial indentation for 'position' inside this node
|
||||||
|
var previous: Node;
|
||||||
|
var current = precedingToken;
|
||||||
|
var currentStart: LineAndCharacter;
|
||||||
|
var indentationDelta: number;
|
||||||
|
|
||||||
|
while (current) {
|
||||||
|
if (positionBelongsToNode(current, position, sourceFile) && nodeContentIsIndented(current, previous)) {
|
||||||
|
currentStart = getStartLineAndCharacterForNode(current, sourceFile);
|
||||||
|
|
||||||
|
if (nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken, current, lineAtPosition, sourceFile)) {
|
||||||
|
indentationDelta = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
indentationDelta = lineAtPosition !== currentStart.line ? options.indentSpaces : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if current node is a list item - if yes, take indentation from it
|
||||||
|
var actualIndentation = getActualIndentationForListItem(current, sourceFile, options);
|
||||||
|
if (actualIndentation !== -1) {
|
||||||
|
return actualIndentation;
|
||||||
|
}
|
||||||
|
|
||||||
|
previous = current;
|
||||||
|
current = current.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!current) {
|
||||||
|
// no parent was found - return 0 to be indented on the level of SourceFile
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var parent: Node = current.parent;
|
||||||
|
var parentStart: LineAndCharacter;
|
||||||
|
|
||||||
|
// walk upwards and collect indentations for pairs of parent-child nodes
|
||||||
|
// indentation is not added if parent and child nodes start on the same line or if parent is IfStatement and child starts on the same line with 'else clause'
|
||||||
|
while (parent) {
|
||||||
|
// check if current node is a list item - if yes, take indentation from it
|
||||||
|
var actualIndentation = getActualIndentationForListItem(current, sourceFile, options);
|
||||||
|
if (actualIndentation !== -1) {
|
||||||
|
return actualIndentation + indentationDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
parentStart = sourceFile.getLineAndCharacterFromPosition(parent.getStart(sourceFile));
|
||||||
|
var parentAndChildShareLine =
|
||||||
|
parentStart.line === currentStart.line ||
|
||||||
|
childStartsOnTheSameLineWithElseInIfStatement(parent, current, currentStart.line, sourceFile);
|
||||||
|
|
||||||
|
// try to fetch actual indentation for current node from source text
|
||||||
|
var actualIndentation = getActualIndentationForNode(current, parent, currentStart, parentAndChildShareLine, sourceFile, options);
|
||||||
|
if (actualIndentation !== -1) {
|
||||||
|
return actualIndentation + indentationDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
// increase indentation if parent node wants its content to be indented and parent and child nodes don't start on the same line
|
||||||
|
if (nodeContentIsIndented(parent, current) && !parentAndChildShareLine) {
|
||||||
|
indentationDelta += options.indentSpaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = parent;
|
||||||
|
currentStart = parentStart;
|
||||||
|
parent = current.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return indentationDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function returns -1 if indentation cannot be determined
|
||||||
|
*/
|
||||||
|
function getActualIndentationForListItemBeforeComma(commaToken: Node, sourceFile: SourceFile, options: TypeScript.FormattingOptions): number {
|
||||||
|
// previous token is comma that separates items in list - find the previous item and try to derive indentation from it
|
||||||
|
var itemInfo = findPrecedingListItem(commaToken);
|
||||||
|
return deriveActualIndentationFromList(itemInfo.list.getChildren(), itemInfo.listItemIndex, sourceFile, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function returns -1 if actual indentation for node should not be used (i.e because node is nested expression)
|
||||||
|
*/
|
||||||
|
function getActualIndentationForNode(current: Node,
|
||||||
|
parent: Node,
|
||||||
|
currentLineAndChar: LineAndCharacter,
|
||||||
|
parentAndChildShareLine: boolean,
|
||||||
|
sourceFile: SourceFile,
|
||||||
|
options: TypeScript.FormattingOptions): number {
|
||||||
|
|
||||||
|
// actual indentation is used for statements\declarations if one of cases below is true:
|
||||||
|
// - parent is SourceFile - by default immediate children of SourceFile are not indented except when user indents them manually
|
||||||
|
// - parent and child are not on the same line
|
||||||
|
var useActualIndentation =
|
||||||
|
(isDeclaration(current) || isStatement(current)) &&
|
||||||
|
(parent.kind === SyntaxKind.SourceFile || !parentAndChildShareLine);
|
||||||
|
|
||||||
|
if (!useActualIndentation) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return findColumnForFirstNonWhitespaceCharacterInLine(currentLineAndChar, sourceFile, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken: Node, current: Node, lineAtPosition: number, sourceFile: SourceFile): boolean {
|
||||||
|
var nextToken = findNextToken(precedingToken, current);
|
||||||
|
if (!nextToken) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextToken.kind === SyntaxKind.OpenBraceToken) {
|
||||||
|
// open braces are always indented at the parent level
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (nextToken.kind === SyntaxKind.CloseBraceToken) {
|
||||||
|
// close braces are indented at the parent level if they are located on the same line with cursor
|
||||||
|
// this means that if new line will be added at $ position, this case will be indented
|
||||||
|
// class A {
|
||||||
|
// $
|
||||||
|
// }
|
||||||
|
/// and this one - not
|
||||||
|
// class A {
|
||||||
|
// $}
|
||||||
|
|
||||||
|
var nextTokenStartLine = getStartLineAndCharacterForNode(nextToken, sourceFile).line;
|
||||||
|
return lineAtPosition === nextTokenStartLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStartLineAndCharacterForNode(n: Node, sourceFile: SourceFile): LineAndCharacter {
|
||||||
|
return sourceFile.getLineAndCharacterFromPosition(n.getStart(sourceFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPrecedingListItem(commaToken: Node): { listItemIndex: number; list: Node } {
|
||||||
|
// CommaToken node is synthetic and thus will be stored in SyntaxList, however parent of the CommaToken points to the container of the SyntaxList skipping the list.
|
||||||
|
// In order to find the preceding list item we first need to locate SyntaxList itself and then search for the position of CommaToken
|
||||||
|
var syntaxList = forEach(commaToken.parent.getChildren(), c => {
|
||||||
|
// find syntax list that covers the span of CommaToken
|
||||||
|
if (c.kind == SyntaxKind.SyntaxList && c.pos <= commaToken.end && c.end >= commaToken.end) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Debug.assert(syntaxList);
|
||||||
|
|
||||||
|
var children = syntaxList.getChildren();
|
||||||
|
var commaIndex = indexOf(children, commaToken);
|
||||||
|
Debug.assert(commaIndex !== -1 && commaIndex !== 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
listItemIndex: commaIndex - 1,
|
||||||
|
list: syntaxList
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function positionBelongsToNode(candidate: Node, position: number, sourceFile: SourceFile): boolean {
|
||||||
|
return candidate.end > position || !isCompletedNode(candidate, sourceFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
function childStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: Node, childStartLine: number, sourceFile: SourceFile): boolean {
|
||||||
|
if (parent.kind === SyntaxKind.IfStatement && (<IfStatement>parent).elseStatement === child) {
|
||||||
|
var elseKeyword = forEach(parent.getChildren(), c => c.kind === SyntaxKind.ElseKeyword && c);
|
||||||
|
Debug.assert(elseKeyword);
|
||||||
|
|
||||||
|
var elseKeywordStartLine = getStartLineAndCharacterForNode(elseKeyword, sourceFile).line;
|
||||||
|
return elseKeywordStartLine === childStartLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getActualIndentationForListItem(node: Node, sourceFile: SourceFile, options: TypeScript.FormattingOptions): number {
|
||||||
|
if (node.parent) {
|
||||||
|
switch (node.parent.kind) {
|
||||||
|
case SyntaxKind.TypeReference:
|
||||||
|
if ((<TypeReferenceNode>node.parent).typeArguments) {
|
||||||
|
return getActualIndentationFromList((<TypeReferenceNode>node.parent).typeArguments);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SyntaxKind.ObjectLiteral:
|
||||||
|
return getActualIndentationFromList((<ObjectLiteral>node.parent).properties);
|
||||||
|
case SyntaxKind.TypeLiteral:
|
||||||
|
return getActualIndentationFromList((<TypeLiteralNode>node.parent).members);
|
||||||
|
case SyntaxKind.ArrayLiteral:
|
||||||
|
return getActualIndentationFromList((<ArrayLiteral>node.parent).elements);
|
||||||
|
case SyntaxKind.FunctionDeclaration:
|
||||||
|
case SyntaxKind.FunctionExpression:
|
||||||
|
case SyntaxKind.ArrowFunction:
|
||||||
|
case SyntaxKind.Method:
|
||||||
|
case SyntaxKind.CallSignature:
|
||||||
|
case SyntaxKind.ConstructSignature:
|
||||||
|
if ((<SignatureDeclaration>node.parent).typeParameters && node.end < (<SignatureDeclaration>node.parent).typeParameters.end) {
|
||||||
|
return getActualIndentationFromList((<SignatureDeclaration>node.parent).typeParameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getActualIndentationFromList((<SignatureDeclaration>node.parent).parameters);
|
||||||
|
case SyntaxKind.NewExpression:
|
||||||
|
case SyntaxKind.CallExpression:
|
||||||
|
if ((<CallExpression>node.parent).typeArguments && node.end < (<CallExpression>node.parent).typeArguments.end) {
|
||||||
|
return getActualIndentationFromList((<CallExpression>node.parent).typeArguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getActualIndentationFromList((<CallExpression>node.parent).arguments);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
function getActualIndentationFromList(list: Node[]): number {
|
||||||
|
var index = indexOf(list, node);
|
||||||
|
return index !== -1 ? deriveActualIndentationFromList(list, index, sourceFile, options) : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function deriveActualIndentationFromList(list: Node[], index: number, sourceFile: SourceFile, options: TypeScript.FormattingOptions): number {
|
||||||
|
Debug.assert(index >= 0 && index < list.length);
|
||||||
|
var node = list[index];
|
||||||
|
|
||||||
|
// walk toward the start of the list starting from current node and check if the line is the same for all items.
|
||||||
|
// if end line for item [i - 1] differs from the start line for item [i] - find column of the first non-whitespace character on the line of item [i]
|
||||||
|
var lineAndCharacter = getStartLineAndCharacterForNode(node, sourceFile);
|
||||||
|
for (var i = index - 1; i >= 0; --i) {
|
||||||
|
if (list[i].kind === SyntaxKind.CommaToken) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// skip list items that ends on the same line with the current list element
|
||||||
|
var prevEndLine = sourceFile.getLineAndCharacterFromPosition(list[i].end).line;
|
||||||
|
if (prevEndLine !== lineAndCharacter.line) {
|
||||||
|
return findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter, sourceFile, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
lineAndCharacter = getStartLineAndCharacterForNode(list[i], sourceFile);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findColumnForFirstNonWhitespaceCharacterInLine(lineAndCharacter: LineAndCharacter, sourceFile: SourceFile, options: TypeScript.FormattingOptions): number {
|
||||||
|
var lineStart = sourceFile.getPositionFromLineAndCharacter(lineAndCharacter.line, 1);
|
||||||
|
var column = 0;
|
||||||
|
for (var i = 0; i < lineAndCharacter.character; ++i) {
|
||||||
|
var charCode = sourceFile.text.charCodeAt(lineStart + i);
|
||||||
|
if (!isWhiteSpace(charCode)) {
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charCode === CharacterCodes.tab) {
|
||||||
|
column += options.spacesPerTab;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
column++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return column;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNextToken(previousToken: Node, parent: Node): Node {
|
||||||
|
return find(parent);
|
||||||
|
|
||||||
|
function find(n: Node): Node {
|
||||||
|
if (isToken(n) && n.pos === previousToken.end) {
|
||||||
|
// this is token that starts at the end of previous token - return it
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
var children = n.getChildren();
|
||||||
|
for (var i = 0, len = children.length; i < len; ++i) {
|
||||||
|
var child = children[i];
|
||||||
|
var shouldDiveInChildNode =
|
||||||
|
// previous token is enclosed somewhere in the child
|
||||||
|
(child.pos <= previousToken.pos && child.end > previousToken.end) ||
|
||||||
|
// previous token ends exactly at the beginning of child
|
||||||
|
(child.pos === previousToken.end);
|
||||||
|
|
||||||
|
if (shouldDiveInChildNode && nodeHasTokens(child)) {
|
||||||
|
return find(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPrecedingToken(position: number, sourceFile: SourceFile): Node {
|
||||||
|
return find(sourceFile);
|
||||||
|
|
||||||
|
function findRightmostToken(n: Node): Node {
|
||||||
|
if (isToken(n)) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
var children = n.getChildren();
|
||||||
|
var candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length);
|
||||||
|
return candidate && findRightmostToken(candidate);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function find(n: Node): Node {
|
||||||
|
if (isToken(n)) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
var children = n.getChildren();
|
||||||
|
for (var i = 0, len = children.length; i < len; ++i) {
|
||||||
|
var child = children[i];
|
||||||
|
if (nodeHasTokens(child)) {
|
||||||
|
if (position < child.end) {
|
||||||
|
if (child.getStart(sourceFile) >= position) {
|
||||||
|
// actual start of the node is past the position - previous token should be at the end of previous child
|
||||||
|
var candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ i);
|
||||||
|
return candidate && findRightmostToken(candidate)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// candidate should be in this node
|
||||||
|
return find(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Debug.assert(n.kind === SyntaxKind.SourceFile);
|
||||||
|
|
||||||
|
// Here we know that none of child token nodes embrace the position,
|
||||||
|
// the only known case is when position is at the end of the file.
|
||||||
|
// Try to find the rightmost token in the file without filtering.
|
||||||
|
// Namely we are skipping the check: 'position < node.end'
|
||||||
|
if (children.length) {
|
||||||
|
var candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length);
|
||||||
|
return candidate && findRightmostToken(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// finds last node that is considered as candidate for search (isCandidate(node) === true) starting from 'exclusiveStartPosition'
|
||||||
|
function findRightmostChildNodeWithTokens(children: Node[], exclusiveStartPosition: number): Node {
|
||||||
|
for (var i = exclusiveStartPosition - 1; i >= 0; --i) {
|
||||||
|
if (nodeHasTokens(children[i])) {
|
||||||
|
return children[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checks if node is something that can contain tokens (except EOF) - filters out EOF tokens, Missing\Omitted expressions, empty SyntaxLists and expression statements that wrap any of listed nodes.
|
||||||
|
*/
|
||||||
|
function nodeHasTokens(n: Node): boolean {
|
||||||
|
if (n.kind === SyntaxKind.ExpressionStatement) {
|
||||||
|
return nodeHasTokens((<ExpressionStatement>n).expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n.kind === SyntaxKind.EndOfFileToken || n.kind === SyntaxKind.OmittedExpression || n.kind === SyntaxKind.Missing) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyntaxList is already realized so getChildCount should be fast and non-expensive
|
||||||
|
return n.kind !== SyntaxKind.SyntaxList || n.getChildCount() !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isToken(n: Node): boolean {
|
||||||
|
return n.kind >= SyntaxKind.FirstToken && n.kind <= SyntaxKind.LastToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nodeContentIsIndented(parent: Node, child: Node): boolean {
|
||||||
|
switch (parent.kind) {
|
||||||
|
case SyntaxKind.ClassDeclaration:
|
||||||
|
case SyntaxKind.InterfaceDeclaration:
|
||||||
|
case SyntaxKind.EnumDeclaration:
|
||||||
|
return true;
|
||||||
|
case SyntaxKind.ModuleDeclaration:
|
||||||
|
// ModuleBlock should take care of indentation
|
||||||
|
return false;
|
||||||
|
case SyntaxKind.FunctionDeclaration:
|
||||||
|
case SyntaxKind.Method:
|
||||||
|
case SyntaxKind.FunctionExpression:
|
||||||
|
case SyntaxKind.GetAccessor:
|
||||||
|
case SyntaxKind.SetAccessor:
|
||||||
|
case SyntaxKind.Constructor:
|
||||||
|
// FunctionBlock should take care of indentation
|
||||||
|
return false;
|
||||||
|
case SyntaxKind.DoStatement:
|
||||||
|
case SyntaxKind.WhileStatement:
|
||||||
|
case SyntaxKind.ForInStatement:
|
||||||
|
case SyntaxKind.ForStatement:
|
||||||
|
return child && child.kind !== SyntaxKind.Block;
|
||||||
|
case SyntaxKind.IfStatement:
|
||||||
|
return child && child.kind !== SyntaxKind.Block;
|
||||||
|
case SyntaxKind.TryStatement:
|
||||||
|
// TryBlock\CatchBlock\FinallyBlock should take care of indentation
|
||||||
|
return false;
|
||||||
|
case SyntaxKind.ArrayLiteral:
|
||||||
|
case SyntaxKind.Block:
|
||||||
|
case SyntaxKind.FunctionBlock:
|
||||||
|
case SyntaxKind.TryBlock:
|
||||||
|
case SyntaxKind.CatchBlock:
|
||||||
|
case SyntaxKind.FinallyBlock:
|
||||||
|
case SyntaxKind.ModuleBlock:
|
||||||
|
case SyntaxKind.ObjectLiteral:
|
||||||
|
case SyntaxKind.TypeLiteral:
|
||||||
|
case SyntaxKind.SwitchStatement:
|
||||||
|
case SyntaxKind.DefaultClause:
|
||||||
|
case SyntaxKind.CaseClause:
|
||||||
|
case SyntaxKind.ParenExpression:
|
||||||
|
case SyntaxKind.CallExpression:
|
||||||
|
case SyntaxKind.NewExpression:
|
||||||
|
case SyntaxKind.VariableStatement:
|
||||||
|
case SyntaxKind.VariableDeclaration:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Checks if node ends with 'expectedLastToken'.
|
||||||
|
* If child at position 'length - 1' is 'SemicolonToken' it is skipped and 'expectedLastToken' is compared with child at position 'length - 2'.
|
||||||
|
*/
|
||||||
|
function nodeEndsWith(n: Node, expectedLastToken: SyntaxKind, sourceFile: SourceFile): boolean {
|
||||||
|
var children = n.getChildren(sourceFile);
|
||||||
|
if (children.length) {
|
||||||
|
var last = children[children.length - 1];
|
||||||
|
if (last.kind === expectedLastToken) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (last.kind === SyntaxKind.SemicolonToken && children.length !== 1) {
|
||||||
|
return children[children.length - 2].kind === expectedLastToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This function is always called when position of the cursor is located after the node
|
||||||
|
*/
|
||||||
|
function isCompletedNode(n: Node, sourceFile: SourceFile): boolean {
|
||||||
|
switch (n.kind) {
|
||||||
|
case SyntaxKind.ClassDeclaration:
|
||||||
|
case SyntaxKind.InterfaceDeclaration:
|
||||||
|
case SyntaxKind.EnumDeclaration:
|
||||||
|
case SyntaxKind.ObjectLiteral:
|
||||||
|
case SyntaxKind.Block:
|
||||||
|
case SyntaxKind.CatchBlock:
|
||||||
|
case SyntaxKind.FinallyBlock:
|
||||||
|
case SyntaxKind.FunctionBlock:
|
||||||
|
case SyntaxKind.ModuleBlock:
|
||||||
|
case SyntaxKind.SwitchStatement:
|
||||||
|
return nodeEndsWith(n, SyntaxKind.CloseBraceToken, sourceFile);
|
||||||
|
case SyntaxKind.ParenExpression:
|
||||||
|
case SyntaxKind.CallSignature:
|
||||||
|
case SyntaxKind.CallExpression:
|
||||||
|
case SyntaxKind.ConstructSignature:
|
||||||
|
return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile);
|
||||||
|
case SyntaxKind.FunctionDeclaration:
|
||||||
|
case SyntaxKind.FunctionExpression:
|
||||||
|
case SyntaxKind.Method:
|
||||||
|
case SyntaxKind.ArrowFunction:
|
||||||
|
return !(<FunctionDeclaration>n).body || isCompletedNode((<FunctionDeclaration>n).body, sourceFile);
|
||||||
|
case SyntaxKind.ModuleDeclaration:
|
||||||
|
return (<ModuleDeclaration>n).body && isCompletedNode((<ModuleDeclaration>n).body, sourceFile);
|
||||||
|
case SyntaxKind.IfStatement:
|
||||||
|
if ((<IfStatement>n).elseStatement) {
|
||||||
|
return isCompletedNode((<IfStatement>n).elseStatement, sourceFile);
|
||||||
|
}
|
||||||
|
return isCompletedNode((<IfStatement>n).thenStatement, sourceFile);
|
||||||
|
case SyntaxKind.ExpressionStatement:
|
||||||
|
return isCompletedNode((<ExpressionStatement>n).expression, sourceFile);
|
||||||
|
case SyntaxKind.ArrayLiteral:
|
||||||
|
return nodeEndsWith(n, SyntaxKind.CloseBracketToken, sourceFile);
|
||||||
|
case SyntaxKind.Missing:
|
||||||
|
return false;
|
||||||
|
case SyntaxKind.CaseClause:
|
||||||
|
case SyntaxKind.DefaultClause:
|
||||||
|
// there is no such thing as terminator token for CaseClause\DefaultClause so for simplicitly always consider them non-completed
|
||||||
|
return false;
|
||||||
|
case SyntaxKind.WhileStatement:
|
||||||
|
return isCompletedNode((<WhileStatement>n).statement, sourceFile);
|
||||||
|
case SyntaxKind.DoStatement:
|
||||||
|
// rough approximation: if DoStatement has While keyword - then if node is completed is checking the presence of ')';
|
||||||
|
var hasWhileKeyword = forEach(n.getChildren(), c => c.kind === SyntaxKind.WhileKeyword && c);
|
||||||
|
if(hasWhileKeyword) {
|
||||||
|
return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile);
|
||||||
|
}
|
||||||
|
return isCompletedNode((<DoStatement>n).statement, sourceFile);
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,7 @@
|
||||||
/// <reference path='breakpoints.ts' />
|
/// <reference path='breakpoints.ts' />
|
||||||
/// <reference path='indentation.ts' />
|
/// <reference path='indentation.ts' />
|
||||||
/// <reference path='formatting\formatting.ts' />
|
/// <reference path='formatting\formatting.ts' />
|
||||||
|
/// <reference path='formatting\smartIndenter.ts' />
|
||||||
|
|
||||||
/// <reference path='core\references.ts' />
|
/// <reference path='core\references.ts' />
|
||||||
/// <reference path='resources\references.ts' />
|
/// <reference path='resources\references.ts' />
|
||||||
|
@ -27,18 +28,18 @@
|
||||||
module ts {
|
module ts {
|
||||||
export interface Node {
|
export interface Node {
|
||||||
getSourceFile(): SourceFile;
|
getSourceFile(): SourceFile;
|
||||||
getChildCount(): number;
|
getChildCount(sourceFile?: SourceFile): number;
|
||||||
getChildAt(index: number): Node;
|
getChildAt(index: number, sourceFile?: SourceFile): Node;
|
||||||
getChildren(): Node[];
|
getChildren(sourceFile?: SourceFile): Node[];
|
||||||
getStart(): number;
|
getStart(sourceFile?: SourceFile): number;
|
||||||
getFullStart(): number;
|
getFullStart(): number;
|
||||||
getEnd(): number;
|
getEnd(): number;
|
||||||
getWidth(): number;
|
getWidth(sourceFile?: SourceFile): number;
|
||||||
getFullWidth(): number;
|
getFullWidth(): number;
|
||||||
getLeadingTriviaWidth(): number;
|
getLeadingTriviaWidth(sourceFile?: SourceFile): number;
|
||||||
getFullText(): string;
|
getFullText(sourceFile?: SourceFile): string;
|
||||||
getFirstToken(): Node;
|
getFirstToken(sourceFile?: SourceFile): Node;
|
||||||
getLastToken(): Node;
|
getLastToken(sourceFile?: SourceFile): Node;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Symbol {
|
export interface Symbol {
|
||||||
|
@ -100,8 +101,8 @@ module ts {
|
||||||
return <SourceFile>node;
|
return <SourceFile>node;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getStart(): number {
|
public getStart(sourceFile?: SourceFile): number {
|
||||||
return getTokenPosOfNode(this);
|
return getTokenPosOfNode(this, sourceFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getFullStart(): number {
|
public getFullStart(): number {
|
||||||
|
@ -112,20 +113,20 @@ module ts {
|
||||||
return this.end;
|
return this.end;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getWidth(): number {
|
public getWidth(sourceFile?: SourceFile): number {
|
||||||
return this.getEnd() - this.getStart();
|
return this.getEnd() - this.getStart(sourceFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getFullWidth(): number {
|
public getFullWidth(): number {
|
||||||
return this.end - this.getFullStart();
|
return this.end - this.getFullStart();
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLeadingTriviaWidth(): number {
|
public getLeadingTriviaWidth(sourceFile?: SourceFile): number {
|
||||||
return this.getStart() - this.pos;
|
return this.getStart(sourceFile) - this.pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getFullText(): string {
|
public getFullText(sourceFile?: SourceFile): string {
|
||||||
return this.getSourceFile().text.substring(this.pos, this.end);
|
return (sourceFile || this.getSourceFile()).text.substring(this.pos, this.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
private addSyntheticNodes(nodes: Node[], pos: number, end: number): number {
|
private addSyntheticNodes(nodes: Node[], pos: number, end: number): number {
|
||||||
|
@ -157,9 +158,9 @@ module ts {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createChildren() {
|
private createChildren(sourceFile?: SourceFile) {
|
||||||
if (this.kind > SyntaxKind.Missing) {
|
if (this.kind > SyntaxKind.Missing) {
|
||||||
scanner.setText(this.getSourceFile().text);
|
scanner.setText((sourceFile || this.getSourceFile()).text);
|
||||||
var children: Node[] = [];
|
var children: Node[] = [];
|
||||||
var pos = this.pos;
|
var pos = this.pos;
|
||||||
var processNode = (node: Node) => {
|
var processNode = (node: Node) => {
|
||||||
|
@ -185,36 +186,36 @@ module ts {
|
||||||
this._children = children || emptyArray;
|
this._children = children || emptyArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getChildCount(): number {
|
public getChildCount(sourceFile?: SourceFile): number {
|
||||||
if (!this._children) this.createChildren();
|
if (!this._children) this.createChildren(sourceFile);
|
||||||
return this._children.length;
|
return this._children.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getChildAt(index: number): Node {
|
public getChildAt(index: number, sourceFile?: SourceFile): Node {
|
||||||
if (!this._children) this.createChildren();
|
if (!this._children) this.createChildren(sourceFile);
|
||||||
return this._children[index];
|
return this._children[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
public getChildren(): Node[] {
|
public getChildren(sourceFile?: SourceFile): Node[] {
|
||||||
if (!this._children) this.createChildren();
|
if (!this._children) this.createChildren(sourceFile);
|
||||||
return this._children;
|
return this._children;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getFirstToken(): Node {
|
public getFirstToken(sourceFile?: SourceFile): Node {
|
||||||
var children = this.getChildren();
|
var children = this.getChildren(sourceFile);
|
||||||
for (var i = 0; i < children.length; i++) {
|
for (var i = 0; i < children.length; i++) {
|
||||||
var child = children[i];
|
var child = children[i];
|
||||||
if (child.kind < SyntaxKind.Missing) return child;
|
if (child.kind < SyntaxKind.Missing) return child;
|
||||||
if (child.kind > SyntaxKind.Missing) return child.getFirstToken();
|
if (child.kind > SyntaxKind.Missing) return child.getFirstToken(sourceFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLastToken(): Node {
|
public getLastToken(sourceFile?: SourceFile): Node {
|
||||||
var children = this.getChildren();
|
var children = this.getChildren(sourceFile);
|
||||||
for (var i = children.length - 1; i >= 0; i--) {
|
for (var i = children.length - 1; i >= 0; i--) {
|
||||||
var child = children[i];
|
var child = children[i];
|
||||||
if (child.kind < SyntaxKind.Missing) return child;
|
if (child.kind < SyntaxKind.Missing) return child;
|
||||||
if (child.kind > SyntaxKind.Missing) return child.getLastToken();
|
if (child.kind > SyntaxKind.Missing) return child.getLastToken(sourceFile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3450,15 +3451,11 @@ module ts {
|
||||||
|
|
||||||
function getIndentationAtPosition(filename: string, position: number, editorOptions: EditorOptions) {
|
function getIndentationAtPosition(filename: string, position: number, editorOptions: EditorOptions) {
|
||||||
filename = TypeScript.switchToForwardSlashes(filename);
|
filename = TypeScript.switchToForwardSlashes(filename);
|
||||||
|
|
||||||
var syntaxTree = getSyntaxTree(filename);
|
var sourceFile = getCurrentSourceFile(filename);
|
||||||
|
|
||||||
var scriptSnapshot = syntaxTreeCache.getCurrentScriptSnapshot(filename);
|
|
||||||
var scriptText = TypeScript.SimpleText.fromScriptSnapshot(scriptSnapshot);
|
|
||||||
var textSnapshot = new TypeScript.Services.Formatting.TextSnapshot(scriptText);
|
|
||||||
var options = new TypeScript.FormattingOptions(!editorOptions.ConvertTabsToSpaces, editorOptions.TabSize, editorOptions.IndentSize, editorOptions.NewLineCharacter)
|
var options = new TypeScript.FormattingOptions(!editorOptions.ConvertTabsToSpaces, editorOptions.TabSize, editorOptions.IndentSize, editorOptions.NewLineCharacter)
|
||||||
|
|
||||||
return TypeScript.Services.Formatting.SingleTokenIndenter.getIndentationAmount(position, syntaxTree.sourceUnit(), textSnapshot, options);
|
return formatting.SmartIndenter.getIndentation(position, sourceFile, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFormattingManager(filename: string, options: FormatCodeOptions) {
|
function getFormattingManager(filename: string, options: FormatCodeOptions) {
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
//// declare var $: any;
|
//// declare var $: any;
|
||||||
//// $(".contentDiv").each(function (index, element) {/**/
|
//// $(".contentDiv").each(function (index, element) {/**/
|
||||||
//// // <-- ensure cursor is here after return on above
|
//// // <-- ensure cursor is here after return on above
|
||||||
//// }); // Ensure indent is 0 after LBrace
|
//// });
|
||||||
|
|
||||||
goTo.marker();
|
goTo.marker();
|
||||||
edit.insert("\n");
|
edit.insert("\n");
|
||||||
verify.indentationIs(4);
|
verify.indentationIs(4);
|
||||||
edit.insert("}");
|
edit.insert("}");
|
||||||
verify.indentationIs(0);
|
verify.indentationIs(4); // keep arguments indented
|
||||||
|
|
|
@ -154,7 +154,7 @@
|
||||||
////
|
////
|
||||||
////function argumentsListIndentation(bar,
|
////function argumentsListIndentation(bar,
|
||||||
//// blah,
|
//// blah,
|
||||||
////{| "indent": 0 |}
|
//// {| "indent": 13 |}
|
||||||
////);
|
////);
|
||||||
////
|
////
|
||||||
////
|
////
|
||||||
|
|
|
@ -8,5 +8,4 @@
|
||||||
|
|
||||||
goTo.marker("1");
|
goTo.marker("1");
|
||||||
edit.insert("\r\n");
|
edit.insert("\r\n");
|
||||||
// Won't-fixed: Disable SmartIndent inside multiline string in TS. Should be 0
|
verify.indentationIs(0);
|
||||||
verify.indentationIs(8);
|
|
17
tests/cases/fourslash/smartIndentActualIndentation.ts
Normal file
17
tests/cases/fourslash/smartIndentActualIndentation.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/// <reference path='fourslash.ts'/>
|
||||||
|
|
||||||
|
//// class A {
|
||||||
|
//// /*1*/
|
||||||
|
//// }
|
||||||
|
|
||||||
|
////module M {
|
||||||
|
//// class C {
|
||||||
|
//// /*2*/
|
||||||
|
//// }
|
||||||
|
////}
|
||||||
|
|
||||||
|
goTo.marker("1");
|
||||||
|
verify.indentationIs(12);
|
||||||
|
|
||||||
|
goTo.marker("2");
|
||||||
|
verify.indentationIs(16);
|
|
@ -1,9 +1,10 @@
|
||||||
/// <reference path='fourslash.ts'/>
|
/// <reference path='fourslash.ts'/>
|
||||||
|
|
||||||
////function foo(bar,
|
////function foo(bar,
|
||||||
//// blah,
|
//// blah, baz,
|
||||||
//// /**/
|
//// /**/
|
||||||
////) { };
|
////) { };
|
||||||
|
|
||||||
goTo.marker();
|
goTo.marker();
|
||||||
verify.indentationIs(0);
|
// keep indentation of 'blah'
|
||||||
|
verify.indentationIs(13);
|
||||||
|
|
11
tests/cases/fourslash/smartIndentCloseBrace.ts
Normal file
11
tests/cases/fourslash/smartIndentCloseBrace.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/// <reference path='fourslash.ts'/>
|
||||||
|
|
||||||
|
////class A {
|
||||||
|
//// {| "indentation": 0|} }
|
||||||
|
////class B {
|
||||||
|
//// var x = 1;
|
||||||
|
//// {| "indentation": 0|} }
|
||||||
|
|
||||||
|
test.markers().forEach((marker) => {
|
||||||
|
verify.indentationAtPositionIs(marker.fileName, marker.position, marker.data.indentation);
|
||||||
|
});
|
17
tests/cases/fourslash/smartIndentDoStatement.ts
Normal file
17
tests/cases/fourslash/smartIndentDoStatement.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
/// <reference path='fourslash.ts'/>
|
||||||
|
//// do /*1*/ {
|
||||||
|
//// } while (true)
|
||||||
|
////
|
||||||
|
//// do { /*2*/
|
||||||
|
//// } /*3*/while (true)/*4*/
|
||||||
|
|
||||||
|
function verifyIndentationAfterNewLine(marker: string, indentation: number): void {
|
||||||
|
goTo.marker(marker);
|
||||||
|
edit.insert("\r\n");
|
||||||
|
verify.indentationIs(indentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyIndentationAfterNewLine("1", 0);
|
||||||
|
verifyIndentationAfterNewLine("2", 4);
|
||||||
|
verifyIndentationAfterNewLine("3", 0);
|
||||||
|
verifyIndentationAfterNewLine("4", 0);
|
20
tests/cases/fourslash/smartIndentIfStatement.ts
Normal file
20
tests/cases/fourslash/smartIndentIfStatement.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/// <reference path='fourslash.ts'/>
|
||||||
|
|
||||||
|
//// if /*1*/(true) { }
|
||||||
|
////
|
||||||
|
//// if (true) /*2*/ { /*3*/
|
||||||
|
//// } /*4*/
|
||||||
|
////
|
||||||
|
//// if (1 === /*5*/ 2) { }
|
||||||
|
|
||||||
|
function verifyIndentationAfterNewLine(marker: string, indentation: number): void {
|
||||||
|
goTo.marker(marker);
|
||||||
|
edit.insert("\r\n");
|
||||||
|
verify.indentationIs(indentation);
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyIndentationAfterNewLine("1", 4);
|
||||||
|
verifyIndentationAfterNewLine("2", 0);
|
||||||
|
verifyIndentationAfterNewLine("3", 4);
|
||||||
|
verifyIndentationAfterNewLine("4", 0);
|
||||||
|
verifyIndentationAfterNewLine("5", 4);
|
|
@ -21,15 +21,11 @@
|
||||||
//// }
|
//// }
|
||||||
////}
|
////}
|
||||||
|
|
||||||
// Behavior below is not ideal, ideal is in comments
|
|
||||||
goTo.marker("1");
|
goTo.marker("1");
|
||||||
//verify.indentationIs(0);
|
verify.indentationIs(0);
|
||||||
verify.indentationIs(8);
|
|
||||||
|
|
||||||
goTo.marker("2");
|
goTo.marker("2");
|
||||||
//verify.indentationIs(0);
|
verify.indentationIs(0);
|
||||||
verify.indentationIs(4);
|
|
||||||
|
|
||||||
goTo.marker("3");
|
goTo.marker("3");
|
||||||
//verify.indentationIs(0);
|
verify.indentationIs(0);
|
||||||
verify.indentationIs(12);
|
|
14
tests/cases/fourslash/smartIndentListItem.ts
Normal file
14
tests/cases/fourslash/smartIndentListItem.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
/// <reference path='fourslash.ts'/>
|
||||||
|
////[1,
|
||||||
|
//// 2
|
||||||
|
//// + 3, 4,
|
||||||
|
//// /*1*/
|
||||||
|
////[1,
|
||||||
|
//// 2
|
||||||
|
//// + 3, 4
|
||||||
|
//// /*2*/
|
||||||
|
|
||||||
|
goTo.marker("1");
|
||||||
|
verify.indentationIs(4)
|
||||||
|
goTo.marker("2");
|
||||||
|
verify.indentationIs(4)
|
|
@ -22,7 +22,7 @@ goTo.marker("4");
|
||||||
verify.currentLineContentIs(" c");
|
verify.currentLineContentIs(" c");
|
||||||
goTo.marker("1");
|
goTo.marker("1");
|
||||||
edit.insert("\r\n");
|
edit.insert("\r\n");
|
||||||
verify.indentationIs(0);
|
verify.indentationIs(4);
|
||||||
goTo.marker("5");
|
goTo.marker("5");
|
||||||
verify.currentLineContentIs(" //comment");
|
verify.currentLineContentIs(" //comment");
|
||||||
goTo.marker("6");
|
goTo.marker("6");
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
//// case 1:
|
//// case 1:
|
||||||
//// {| "indentation": 12 |}
|
//// {| "indentation": 12 |}
|
||||||
//// break;
|
//// break;
|
||||||
//// {| "indentation": 8 |}
|
//// {| "indentation": 12 |} // content of case clauses is always indented relatively to case clause
|
||||||
//// }
|
//// }
|
||||||
//// {| "indentation": 4 |}
|
//// {| "indentation": 4 |}
|
||||||
////}
|
////}
|
||||||
|
|
8
tests/cases/fourslash/smartIndentTypeArgumentList.ts
Normal file
8
tests/cases/fourslash/smartIndentTypeArgumentList.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
/// <reference path='fourslash.ts'/>
|
||||||
|
|
||||||
|
////interface T1 extends T<number,
|
||||||
|
//// string
|
||||||
|
//// /**/>
|
||||||
|
|
||||||
|
goTo.marker();
|
||||||
|
verify.indentationIs(32);
|
|
@ -8,6 +8,4 @@
|
||||||
goTo.marker();
|
goTo.marker();
|
||||||
edit.insert('case 1:\n');
|
edit.insert('case 1:\n');
|
||||||
|
|
||||||
// ideally would be 8
|
verify.indentationIs(8);
|
||||||
//verify.indentationIs(8);
|
|
||||||
verify.indentationIs(4);
|
|
Loading…
Reference in a new issue