context: // Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.
  
  using System.Collections.Generic;
  using System.Linq;
  using System.Threading;
  using Microsoft.CodeAnalysis;
  using Microsoft.CodeAnalysis.CSharp;
  using Microsoft.CodeAnalysis.CSharp.Extensions;
  using Microsoft.CodeAnalysis.CSharp.Formatting;
  using Microsoft.CodeAnalysis.CSharp.Syntax;
  using Microsoft.CodeAnalysis.Formatting;
  using Microsoft.CodeAnalysis.Formatting.Rules;
  using Microsoft.CodeAnalysis.LanguageServices;
  using Microsoft.CodeAnalysis.Options;
  using Microsoft.CodeAnalysis.Shared.Extensions;
  using Microsoft.CodeAnalysis.Text;
  using Roslyn.Utilities;
  
  namespace dnSpy.Roslyn.Internal.SmartIndent.CSharp
  {
      internal partial class CSharpIndentationService
      {
          internal class Indenter : AbstractIndenter
          {
              public Indenter(
                  ISyntaxFactsService syntaxFacts,
                  SyntaxTree syntaxTree,
                  IEnumerable<IFormattingRule> rules,
                  OptionSet optionSet,
                  TextLine line,
                  CancellationToken cancellationToken) :
                  base(syntaxFacts, syntaxTree, rules, optionSet, line, cancellationToken)
              {
              }
  
              protected override IndentationResult? GetDesiredIndentationWorker(
                  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(token);
                  }
                  else
                  {
                      // there must be trivia that contains or touch this position
                      Contract.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 IndentFromStartOfLine(0);
                      }
  
                      var trivia = Tree.GetRoot(CancellationToken).FindTrivia(firstNonWhitespacePosition.Value, findInsideTrivia: true);
                      if (trivia.Kind() == SyntaxKind.None || this.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 = GetCurrentPositionNotBelongToEndOfFileToken(LineToBeIndented.Start);
                              return IndentFromStartOfLine(Finder.GetIndentationOfCurrentPosition(Tree, token, position, 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(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(token);
                          }
                      }
  
                      // this case we will keep the indentation of this trivia line
                      // this trivia can't be preprocessor by the way.
                      return GetIndentationOfLine(previousLine);
                  }
              }
  
              private IndentationResult? GetIndentationBasedOnToken(SyntaxToken token)
              {
                  Contract.ThrowIfNull(Tree);
                  Contract.ThrowIfTrue(token.Kind() == SyntaxKind.None);
  
                  // special cases
                  // case 1: token belongs to verbatim token literal
                  // case 2: $@"$${0}"
                  // case 3: $@"Comment$$ inbetween{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 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 = GetCurrentPositionNotBelongToEndOfFileToken(LineToBeIndented.Start);
  
                  // first check operation service to see whether we can determine indentation from it
                  var indentation = Finder.FromIndentBlockOperations(Tree, token, position, CancellationToken);
                  if (indentation.HasValue)
                  {
                      return IndentFromStartOfLine(indentation.Value);
                  }
  
                  var alignmentTokenIndentation = Finder.FromAlignTokensOperations(Tree, token);
                  if (alignmentTokenIndentation.HasValue)
                  {
                      return IndentFromStartOfLine(alignmentTokenIndentation.Value);
                  }
  
                  // if we couldn't determine indentation from the service, use heuristic to find indentation.
                  var sourceText = LineToBeIndented.Text;
  
                  // 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())
                  {
                      Contract.Requires(
                          token.Parent != null &&
                          (token.Parent.Parent is StatementSyntax || token.Parent.Parent is ElseClauseSyntax));
  
                      var embeddedStatementOwner = token.Parent.Parent;
                      while (embeddedStatementOwner.IsEmbeddedStatement())
                      {
                          embeddedStatementOwner = embeddedStatementOwner.Parent;
                      }
  
                      return GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(embeddedStatementOwner.GetFirstToken(includeZeroWidth: true).SpanStart));
                  }
  
                  switch (token.Kind())
                  {
                      case SyntaxKind.SemicolonToken:
                          {
                              // special cases
                              if (token.IsSemicolonInForStatement())
                              {
                                  return GetDefaultIndentationFromToken(token);
                              }
  
                              return IndentFromStartOfLine(Finder.GetIndentationOfCurrentPosition(Tree, token, position, CancellationToken));
                          }
  
                      case SyntaxKind.CloseBraceToken:
                          {
                              if (token.Parent.IsKind(SyntaxKind.AccessorList) &&
                                  token.Parent.Parent.IsKind(SyntaxKind.PropertyDeclaration))
                              {
                                  if (token.GetNextToken().IsEqualsTokenInAutoPropertyInitializers())
                                  {
                                      return GetDefaultIndentationFromToken(token);
                                  }
                              }
  
                              return IndentFromStartOfLine(Finder.GetIndentationOfCurrentPosition(Tree, token, position, CancellationToken));
                          }
  
                      case SyntaxKind.OpenBraceToken:
                          {
                              return IndentFromStartOfLine(Finder.GetIndentationOfCurrentPosition(Tree, token, position, CancellationToken));
                          }
  
                      case SyntaxKind.ColonToken:
                          {
                              var nonTerminalNode = token.Parent;
                              Contract.ThrowIfNull(nonTerminalNode, @"Malformed code or bug in parser???");
  
                              if (nonTerminalNode is SwitchLabelSyntax)
                              {
                                  return GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(nonTerminalNode.GetFirstToken(includeZeroWidth: true).SpanStart), OptionSet.GetOption(FormattingOptions.IndentationSize, token.Language));
                              }
  
                              // default case
                              return GetDefaultIndentationFromToken(token);
                          }
  
                      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 GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(nonTerminalNode.GetFirstToken(includeZeroWidth: true).SpanStart));
                              }
  
                              // default case
                              return GetDefaultIndentationFromToken(token);
                          }
  
                      case SyntaxKind.XmlTextLiteralToken:
                          {
                              return GetIndentationOfLine(sourceText.Lines.GetLineFromPosition(token.SpanStart));
                          }
  
                      case SyntaxKind.CommaToken:
                          {
                              return GetIndentationFromCommaSeparatedList(token);
                          }
  
                      default:
                          {
                              return GetDefaultIndentationFromToken(token);
                          }
                  }
              }
  
              private IndentationResult? GetIndentationFromCommaSeparatedList(SyntaxToken token)
              {
                  var node = token.Parent;
                  switch (node)
                  {
                      case BaseArgumentListSyntax argument:
                          return GetIndentationFromCommaSeparatedList(argument.Arguments, token);
                      case BaseParameterListSyntax parameter:
                          return GetIndentationFromCommaSeparatedList(parameter.Parameters, token);
                      case TypeArgumentListSyntax typeArgument:
                          return GetIndentationFromCommaSeparatedList(typeArgument.Arguments, token);
                      case TypeParameterListSyntax typeParameter:
                          return GetIndentationFromCommaSeparatedList(typeParameter.Parameters, token);
                      case EnumDeclarationSyntax enumDeclaration:
                          return GetIndentationFromCommaSeparatedList(enumDeclaration.Members, token);
                      case InitializerExpressionSyntax initializerSyntax:
                          return GetIndentationFromCommaSeparatedList(initializerSyntax.Expressions, token);
                  }
  
                  return GetDefaultIndentationFromToken(token);
              }
  
              private IndentationResult? GetIndentationFromCommaSeparatedList<T>(SeparatedSyntaxList<T> list, SyntaxToken token) where T : SyntaxNode
              {
                  var index = list.GetWithSeparators().IndexOf(token);
                  if (index < 0)
                  {
                      return GetDefaultIndentationFromToken(token);
                  }
  
                  // find node that starts at the beginning of a line
                  var sourceText = LineToBeIndented.Text;
                  for (int i = (index - 1) / 2; i >= 0; i--)
                  {
                      var node = list[i];
  
gt:                     var firstToken = node.GetFirstToken(includeZeroWidth: true);