protected override IndentationResult GetDesiredIndentationWorker( Indenter indenter, SyntaxToken token, TextLine previousLine, int lastNonWhitespacePosition) { // okay, now check whether the text we found is trivia or actual token. if (token.Span.Contains(lastNonWhitespacePosition)) { // okay, it is a token case, do special work based on type of last token on previous line return(GetIndentationBasedOnToken(indenter, token)); } else { // there must be trivia that contains or touch this position Debug.Assert(token.FullSpan.Contains(lastNonWhitespacePosition)); // okay, now check whether the trivia is at the beginning of the line var firstNonWhitespacePosition = previousLine.GetFirstNonWhitespacePosition(); if (!firstNonWhitespacePosition.HasValue) { return(indenter.IndentFromStartOfLine(0)); } var trivia = indenter.Root.FindTrivia(firstNonWhitespacePosition.Value, findInsideTrivia: true); if (trivia.Kind() == SyntaxKind.None || indenter.LineToBeIndented.LineNumber > previousLine.LineNumber + 1) { // If the token belongs to the next statement and is also the first token of the statement, then it means the user wants // to start type a new statement. So get indentation from the start of the line but not based on the token. // Case: // static void Main(string[] args) // { // // A // // B // // $$ // return; // } var containingStatement = token.GetAncestor <StatementSyntax>(); if (containingStatement != null && containingStatement.GetFirstToken() == token) { var position = indenter.GetCurrentPositionNotBelongToEndOfFileToken(indenter.LineToBeIndented.Start); return(indenter.IndentFromStartOfLine(indenter.Finder.GetIndentationOfCurrentPosition(indenter.Tree, token, position, indenter.CancellationToken))); } // If the token previous of the base token happens to be a Comma from a separation list then we need to handle it different // Case: // var s = new List<string> // { // """", // """",/*sdfsdfsdfsdf*/ // // dfsdfsdfsdfsdf // // $$ // }; var previousToken = token.GetPreviousToken(); if (previousToken.IsKind(SyntaxKind.CommaToken)) { return(GetIndentationFromCommaSeparatedList(indenter, previousToken)); } else if (!previousToken.IsKind(SyntaxKind.None)) { // okay, beginning of the line is not trivia, use the last token on the line as base token return(GetIndentationBasedOnToken(indenter, token)); } } // this case we will keep the indentation of this trivia line // this trivia can't be preprocessor by the way. return(indenter.GetIndentationOfLine(previousLine)); } }
private static IndentationResult GetIndentationBasedOnToken(Indenter indenter, SyntaxToken token) { Contract.ThrowIfNull(indenter.Tree); Contract.ThrowIfTrue(token.Kind() == SyntaxKind.None); // special cases // case 1: token belongs to verbatim token literal // case 2: $@"$${0}" // case 3: $@"Comment$$ in-between{0}" // case 4: $@"{0}$$" if (token.IsVerbatimStringLiteral() || token.IsKind(SyntaxKind.InterpolatedVerbatimStringStartToken) || token.IsKind(SyntaxKind.InterpolatedStringTextToken) || (token.IsKind(SyntaxKind.CloseBraceToken) && token.Parent.IsKind(SyntaxKind.Interpolation))) { return(indenter.IndentFromStartOfLine(0)); } // if previous statement belong to labeled statement, don't follow label's indentation // but its previous one. if (token.Parent is LabeledStatementSyntax || token.IsLastTokenInLabelStatement()) { token = token.GetAncestor <LabeledStatementSyntax>() !.GetFirstToken(includeZeroWidth: true).GetPreviousToken(includeZeroWidth: true); } var position = indenter.GetCurrentPositionNotBelongToEndOfFileToken(indenter.LineToBeIndented.Start); // first check operation service to see whether we can determine indentation from it var indentation = indenter.Finder.FromIndentBlockOperations(indenter.Tree, token, position, indenter.CancellationToken); if (indentation.HasValue) { return(indenter.IndentFromStartOfLine(indentation.Value)); } var alignmentTokenIndentation = indenter.Finder.FromAlignTokensOperations(indenter.Tree, token); if (alignmentTokenIndentation.HasValue) { return(indenter.IndentFromStartOfLine(alignmentTokenIndentation.Value)); } // if we couldn't determine indentation from the service, use heuristic to find indentation. var sourceText = indenter.LineToBeIndented.Text; RoslynDebug.AssertNotNull(sourceText); // If this is the last token of an embedded statement, walk up to the top-most parenting embedded // statement owner and use its indentation. // // cases: // if (true) // if (false) // Goo(); // // if (true) // { } if (token.IsSemicolonOfEmbeddedStatement() || token.IsCloseBraceOfEmbeddedBlock()) { Debug.Assert( token.Parent != null && (token.Parent.Parent is StatementSyntax || token.Parent.Parent is ElseClauseSyntax)); var embeddedStatementOwner = token.Parent.Parent; while (embeddedStatementOwner.IsEmbeddedStatement()) { RoslynDebug.AssertNotNull(embeddedStatementOwner.Parent); embeddedStatementOwner = embeddedStatementOwner.Parent; } return(indenter.GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(embeddedStatementOwner.GetFirstToken(includeZeroWidth: true).SpanStart))); } switch (token.Kind()) { case SyntaxKind.SemicolonToken: { // special cases if (token.IsSemicolonInForStatement()) { return(GetDefaultIndentationFromToken(indenter, token)); } return(indenter.IndentFromStartOfLine(indenter.Finder.GetIndentationOfCurrentPosition(indenter.Tree, token, position, indenter.CancellationToken))); } case SyntaxKind.CloseBraceToken: { if (token.Parent.IsKind(SyntaxKind.AccessorList) && token.Parent.Parent.IsKind(SyntaxKind.PropertyDeclaration)) { if (token.GetNextToken().IsEqualsTokenInAutoPropertyInitializers()) { return(GetDefaultIndentationFromToken(indenter, token)); } } return(indenter.IndentFromStartOfLine(indenter.Finder.GetIndentationOfCurrentPosition(indenter.Tree, token, position, indenter.CancellationToken))); } case SyntaxKind.OpenBraceToken: { return(indenter.IndentFromStartOfLine(indenter.Finder.GetIndentationOfCurrentPosition(indenter.Tree, token, position, indenter.CancellationToken))); } case SyntaxKind.ColonToken: { var nonTerminalNode = token.Parent; Contract.ThrowIfNull(nonTerminalNode, @"Malformed code or bug in parser???"); if (nonTerminalNode is SwitchLabelSyntax) { return(indenter.GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(nonTerminalNode.GetFirstToken(includeZeroWidth: true).SpanStart), indenter.Options.FormattingOptions.IndentationSize)); } goto default; } case SyntaxKind.CloseBracketToken: { var nonTerminalNode = token.Parent; Contract.ThrowIfNull(nonTerminalNode, @"Malformed code or bug in parser???"); // if this is closing an attribute, we shouldn't indent. if (nonTerminalNode is AttributeListSyntax) { return(indenter.GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(nonTerminalNode.GetFirstToken(includeZeroWidth: true).SpanStart))); } goto default; } case SyntaxKind.XmlTextLiteralToken: { return(indenter.GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(token.SpanStart))); } case SyntaxKind.CommaToken: { return(GetIndentationFromCommaSeparatedList(indenter, token)); } case SyntaxKind.CloseParenToken: { if (token.Parent.IsKind(SyntaxKind.ArgumentList)) { return(GetDefaultIndentationFromToken(indenter, token.Parent.GetFirstToken(includeZeroWidth: true))); } goto default; } default: { return(GetDefaultIndentationFromToken(indenter, token)); } } }
private static IndentationResult GetIndentationBasedOnToken(Indenter indenter, SyntaxToken token) { Contract.ThrowIfNull(indenter.Tree); Contract.ThrowIfTrue(token.Kind() == SyntaxKind.None); var sourceText = indenter.LineToBeIndented.Text; RoslynDebug.AssertNotNull(sourceText); // case: """$$ // """ if (token.IsKind(SyntaxKind.MultiLineRawStringLiteralToken)) { var endLine = sourceText.Lines.GetLineFromPosition(token.Span.End); var minimumOffset = endLine.GetFirstNonWhitespaceOffset(); Contract.ThrowIfNull(minimumOffset); // If possible, indent to match the indentation of the previous non-whitespace line contained in the // same raw string. Otherwise, indent to match the ending line of the raw string. var startLine = sourceText.Lines.GetLineFromPosition(token.SpanStart); for (var currentLineNumber = indenter.LineToBeIndented.LineNumber - 1; currentLineNumber >= startLine.LineNumber + 1; currentLineNumber--) { var currentLine = sourceText.Lines[currentLineNumber]; if (currentLine.GetFirstNonWhitespaceOffset() is { } priorLineOffset) { if (priorLineOffset >= minimumOffset.Value) { return(indenter.GetIndentationOfLine(currentLine)); } else { // The prior line is not sufficiently indented, so use the ending delimiter for the indent break; } } } return(indenter.GetIndentationOfLine(endLine)); } // case 1: $"""$$ // """ // case 2: $""" // text$$ // """ // case 3: $""" // {value}$$ // """ if (token.Kind() is SyntaxKind.InterpolatedMultiLineRawStringStartToken or SyntaxKind.InterpolatedStringTextToken || (token.IsKind(SyntaxKind.CloseBraceToken) && token.Parent.IsKind(SyntaxKind.Interpolation))) { var interpolatedExpression = token.GetAncestor <InterpolatedStringExpressionSyntax>(); Contract.ThrowIfNull(interpolatedExpression); if (interpolatedExpression.StringStartToken.IsKind(SyntaxKind.InterpolatedMultiLineRawStringStartToken)) { var endLine = sourceText.Lines.GetLineFromPosition(interpolatedExpression.StringEndToken.Span.End); var minimumOffset = endLine.GetFirstNonWhitespaceOffset(); Contract.ThrowIfNull(minimumOffset); // If possible, indent to match the indentation of the previous non-whitespace line contained in the // same raw string. Otherwise, indent to match the ending line of the raw string. var startLine = sourceText.Lines.GetLineFromPosition(interpolatedExpression.StringStartToken.SpanStart); for (var currentLineNumber = indenter.LineToBeIndented.LineNumber - 1; currentLineNumber >= startLine.LineNumber + 1; currentLineNumber--) { var currentLine = sourceText.Lines[currentLineNumber]; if (!indenter.Root.FindToken(currentLine.Start, findInsideTrivia: true).IsKind(SyntaxKind.InterpolatedStringTextToken)) { // Avoid trying to indent to match the content of an interpolation. Example: // // _ = $""" // { // 0} <-- the start of this line is not part of the text content // """ // continue; } if (currentLine.GetFirstNonWhitespaceOffset() is { } priorLineOffset) { if (priorLineOffset >= minimumOffset.Value) { return(indenter.GetIndentationOfLine(currentLine)); } else { // The prior line is not sufficiently indented, so use the ending delimiter for the indent break; } } } return(indenter.GetIndentationOfLine(endLine)); } } // special cases // case 1: token belongs to verbatim token literal // case 2: $@"$${0}" // case 3: $@"Comment$$ in-between{0}" // case 4: $@"{0}$$" if (token.IsVerbatimStringLiteral() || token.IsKind(SyntaxKind.InterpolatedVerbatimStringStartToken) || token.IsKind(SyntaxKind.InterpolatedStringTextToken) || (token.IsKind(SyntaxKind.CloseBraceToken) && token.Parent.IsKind(SyntaxKind.Interpolation))) { return(indenter.IndentFromStartOfLine(0)); } // if previous statement belong to labeled statement, don't follow label's indentation // but its previous one. if (token.Parent is LabeledStatementSyntax || token.IsLastTokenInLabelStatement()) { token = token.GetAncestor <LabeledStatementSyntax>() !.GetFirstToken(includeZeroWidth: true).GetPreviousToken(includeZeroWidth: true); } var position = indenter.GetCurrentPositionNotBelongToEndOfFileToken(indenter.LineToBeIndented.Start); // first check operation service to see whether we can determine indentation from it var indentation = indenter.Finder.FromIndentBlockOperations(indenter.Tree, token, position, indenter.CancellationToken); if (indentation.HasValue) { return(indenter.IndentFromStartOfLine(indentation.Value)); } var alignmentTokenIndentation = indenter.Finder.FromAlignTokensOperations(indenter.Tree, token); if (alignmentTokenIndentation.HasValue) { return(indenter.IndentFromStartOfLine(alignmentTokenIndentation.Value)); } // if we couldn't determine indentation from the service, use heuristic to find indentation. // If this is the last token of an embedded statement, walk up to the top-most parenting embedded // statement owner and use its indentation. // // cases: // if (true) // if (false) // Goo(); // // if (true) // { } if (token.IsSemicolonOfEmbeddedStatement() || token.IsCloseBraceOfEmbeddedBlock()) { RoslynDebug.Assert( token.Parent != null && (token.Parent.Parent is StatementSyntax || token.Parent.Parent is ElseClauseSyntax)); var embeddedStatementOwner = token.Parent.Parent; while (embeddedStatementOwner.IsEmbeddedStatement()) { RoslynDebug.AssertNotNull(embeddedStatementOwner.Parent); embeddedStatementOwner = embeddedStatementOwner.Parent; } return(indenter.GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(embeddedStatementOwner.GetFirstToken(includeZeroWidth: true).SpanStart))); } switch (token.Kind()) { case SyntaxKind.SemicolonToken: { // special cases if (token.IsSemicolonInForStatement()) { return(GetDefaultIndentationFromToken(indenter, token)); } return(indenter.IndentFromStartOfLine(indenter.Finder.GetIndentationOfCurrentPosition(indenter.Tree, token, position, indenter.CancellationToken))); } case SyntaxKind.CloseBraceToken: { if (token.Parent.IsKind(SyntaxKind.AccessorList) && token.Parent.Parent.IsKind(SyntaxKind.PropertyDeclaration)) { if (token.GetNextToken().IsEqualsTokenInAutoPropertyInitializers()) { return(GetDefaultIndentationFromToken(indenter, token)); } } return(indenter.IndentFromStartOfLine(indenter.Finder.GetIndentationOfCurrentPosition(indenter.Tree, token, position, indenter.CancellationToken))); } case SyntaxKind.OpenBraceToken: { return(indenter.IndentFromStartOfLine(indenter.Finder.GetIndentationOfCurrentPosition(indenter.Tree, token, position, indenter.CancellationToken))); } case SyntaxKind.ColonToken: { var nonTerminalNode = token.Parent; Contract.ThrowIfNull(nonTerminalNode, @"Malformed code or bug in parser???"); if (nonTerminalNode is SwitchLabelSyntax) { return(indenter.GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(nonTerminalNode.GetFirstToken(includeZeroWidth: true).SpanStart), indenter.Options.FormattingOptions.IndentationSize)); } goto default; } case SyntaxKind.CloseBracketToken: { var nonTerminalNode = token.Parent; Contract.ThrowIfNull(nonTerminalNode, @"Malformed code or bug in parser???"); // if this is closing an attribute, we shouldn't indent. if (nonTerminalNode is AttributeListSyntax) { return(indenter.GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(nonTerminalNode.GetFirstToken(includeZeroWidth: true).SpanStart))); } goto default; } case SyntaxKind.XmlTextLiteralToken: { return(indenter.GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(token.SpanStart))); } case SyntaxKind.CommaToken: { return(GetIndentationFromCommaSeparatedList(indenter, token)); } case SyntaxKind.CloseParenToken: { if (token.Parent.IsKind(SyntaxKind.ArgumentList)) { return(GetDefaultIndentationFromToken(indenter, token.Parent.GetFirstToken(includeZeroWidth: true))); } goto default; } default: { return(GetDefaultIndentationFromToken(indenter, token)); } } }