From c27e07a69b57dcc99a82fa127f7e1f626eaf3139 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Tue, 17 Mar 2015 00:33:58 -0700 Subject: [PATCH] Moved logic from smart indenter; use 'scope nodes' for completions. --- src/services/formatting/smartIndenter.ts | 123 +---------------------- src/services/services.ts | 11 +- src/services/utilities.ts | 118 ++++++++++++++++++++++ 3 files changed, 129 insertions(+), 123 deletions(-) diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts index 0932ad042e7..050468cb9da 100644 --- a/src/services/formatting/smartIndenter.ts +++ b/src/services/formatting/smartIndenter.ts @@ -215,11 +215,7 @@ module ts.formatting { function getStartLineAndCharacterForNode(n: Node, sourceFile: SourceFile): LineAndCharacter { return sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)); } - - function positionBelongsToNode(candidate: Node, position: number, sourceFile: SourceFile): boolean { - return candidate.end > position || !isCompletedNode(candidate, sourceFile); - } - + export function childStartsOnTheSameLineWithElseInIfStatement(parent: Node, child: TextRangeWithKind, childStartLine: number, sourceFile: SourceFile): boolean { if (parent.kind === SyntaxKind.IfStatement && (parent).elseStatement === child) { var elseKeyword = findChildOfKind(parent, SyntaxKind.ElseKeyword, sourceFile); @@ -401,122 +397,5 @@ module ts.formatting { 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 { - if (n.getFullWidth() === 0) { - return false; - } - - switch (n.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.ObjectLiteralExpression: - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.TypeLiteral: - case SyntaxKind.Block: - case SyntaxKind.ModuleBlock: - case SyntaxKind.CaseBlock: - return nodeEndsWith(n, SyntaxKind.CloseBraceToken, sourceFile); - case SyntaxKind.CatchClause: - return isCompletedNode((n).block, sourceFile); - case SyntaxKind.NewExpression: - if (!(n).arguments) { - return true; - } - // fall through - case SyntaxKind.CallExpression: - case SyntaxKind.ParenthesizedExpression: - case SyntaxKind.ParenthesizedType: - return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile); - - case SyntaxKind.FunctionType: - case SyntaxKind.ConstructorType: - return isCompletedNode((n).type, sourceFile); - - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.CallSignature: - case SyntaxKind.ArrowFunction: - if ((n).body) { - return isCompletedNode((n).body, sourceFile); - } - - if ((n).type) { - return isCompletedNode((n).type, sourceFile); - } - - // Even though type parameters can be unclosed, we can get away with - // having at least a closing paren. - return hasChildOfKind(n, SyntaxKind.CloseParenToken, sourceFile); - - case SyntaxKind.ModuleDeclaration: - return (n).body && isCompletedNode((n).body, sourceFile); - - case SyntaxKind.IfStatement: - if ((n).elseStatement) { - return isCompletedNode((n).elseStatement, sourceFile); - } - return isCompletedNode((n).thenStatement, sourceFile); - - case SyntaxKind.ExpressionStatement: - return isCompletedNode((n).expression, sourceFile); - - case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.ArrayBindingPattern: - case SyntaxKind.IndexSignature: - case SyntaxKind.ComputedPropertyName: - case SyntaxKind.TupleType: - return nodeEndsWith(n, SyntaxKind.CloseBracketToken, sourceFile); - - 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.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.WhileStatement: - return isCompletedNode((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 = findChildOfKind(n, SyntaxKind.WhileKeyword, sourceFile); - if (hasWhileKeyword) { - return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile); - } - return isCompletedNode((n).statement, sourceFile); - - default: - return true; - } - } } } \ No newline at end of file diff --git a/src/services/services.ts b/src/services/services.ts index 61d7a0736b0..e9d207bddea 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2595,8 +2595,9 @@ module ts { isNewIdentifierLocation = isNewIdentifierDefinitionLocation(previousToken); /// TODO filter meaning based on the current context + var scopeNode = getScopeNode(previousToken, position, sourceFile); var symbolMeanings = SymbolFlags.Type | SymbolFlags.Value | SymbolFlags.Namespace | SymbolFlags.Alias; - var symbols = typeInfoResolver.getSymbolsInScope(node, symbolMeanings); + var symbols = typeInfoResolver.getSymbolsInScope(scopeNode, symbolMeanings); getCompletionEntriesFromSymbols(symbols, activeCompletionSession); } @@ -2615,6 +2616,14 @@ module ts { entries: activeCompletionSession.entries }; + function getScopeNode(initialToken: Node, position: number, sourceFile: SourceFile) { + var scope = initialToken; + while (scope && (isToken(scope) || !positionBelongsToNode(scope, position, sourceFile))) { + scope = scope.parent; + } + return scope; + } + function getCompletionEntriesFromSymbols(symbols: Symbol[], session: CompletionSession): void { var start = new Date().getTime(); forEach(symbols, symbol => { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 70c8eada5b7..11337b40e9d 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -59,6 +59,124 @@ module ts { return start < end; } + export function positionBelongsToNode(candidate: Node, position: number, sourceFile: SourceFile): boolean { + return candidate.end > position || !isCompletedNode(candidate, sourceFile); + } + + export function isCompletedNode(n: Node, sourceFile: SourceFile): boolean { + if (n.getFullWidth() === 0) { + return false; + } + + switch (n.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ObjectLiteralExpression: + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.TypeLiteral: + case SyntaxKind.Block: + case SyntaxKind.ModuleBlock: + case SyntaxKind.CaseBlock: + return nodeEndsWith(n, SyntaxKind.CloseBraceToken, sourceFile); + case SyntaxKind.CatchClause: + return isCompletedNode((n).block, sourceFile); + case SyntaxKind.NewExpression: + if (!(n).arguments) { + return true; + } + // fall through + case SyntaxKind.CallExpression: + case SyntaxKind.ParenthesizedExpression: + case SyntaxKind.ParenthesizedType: + return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile); + + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + return isCompletedNode((n).type, sourceFile); + + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ArrowFunction: + if ((n).body) { + return isCompletedNode((n).body, sourceFile); + } + + if ((n).type) { + return isCompletedNode((n).type, sourceFile); + } + + // Even though type parameters can be unclosed, we can get away with + // having at least a closing paren. + return hasChildOfKind(n, SyntaxKind.CloseParenToken, sourceFile); + + case SyntaxKind.ModuleDeclaration: + return (n).body && isCompletedNode((n).body, sourceFile); + + case SyntaxKind.IfStatement: + if ((n).elseStatement) { + return isCompletedNode((n).elseStatement, sourceFile); + } + return isCompletedNode((n).thenStatement, sourceFile); + + case SyntaxKind.ExpressionStatement: + return isCompletedNode((n).expression, sourceFile); + + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ArrayBindingPattern: + case SyntaxKind.IndexSignature: + case SyntaxKind.ComputedPropertyName: + case SyntaxKind.TupleType: + return nodeEndsWith(n, SyntaxKind.CloseBracketToken, sourceFile); + + 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.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.WhileStatement: + return isCompletedNode((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 = findChildOfKind(n, SyntaxKind.WhileKeyword, sourceFile); + if (hasWhileKeyword) { + return nodeEndsWith(n, SyntaxKind.CloseParenToken, sourceFile); + } + return isCompletedNode((n).statement, sourceFile); + + default: + return true; + } + } + + /* + * 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; + } + export function findListItemInfo(node: Node): ListItemInfo { var list = findContainingList(node);