// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.Collections.Immutable; using System.Threading; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.Editing; internal static class AddParameterEditor { public static void AddParameter( ISyntaxFacts syntaxFacts, SyntaxEditor editor, SyntaxNode declaration, int insertionIndex, SyntaxNode parameterDeclaration, CancellationToken cancellationToken) { var sourceText = declaration.SyntaxTree.GetText(cancellationToken); var generator = editor.Generator; var existingParameters = generator.GetParameters(declaration); var placeOnNewLine = ShouldPlaceParametersOnNewLine(existingParameters, cancellationToken); if (!placeOnNewLine) { // Trivial case. Just let the stock editor impl handle this for us. editor.InsertParameter(declaration, insertionIndex, parameterDeclaration); return; } if (insertionIndex >= existingParameters.Count) { // The parameter is being added after the last parameter and needs to be placed on a new line. // Get the indentation of the original last parameter and give the new parameter the same indentation. // Even if we're adding multiple parameters past the original last parameter, we can give them all the identation of the original 'last' parameter. var leadingIndentation = GetDesiredLeadingIndentation( syntaxFacts, existingParameters[existingParameters.Count - 1], includeLeadingNewLine: true); parameterDeclaration = parameterDeclaration.WithPrependedLeadingTrivia(leadingIndentation) .WithAdditionalAnnotations(Formatter.Annotation); editor.AddParameter(declaration, parameterDeclaration); } else if (insertionIndex == 0) { // Inserting into the start of the list. The existing first parameter might // be on the same line as the parameter list, or it might be on the next line. var firstParameter = existingParameters[0]; var previousToken = firstParameter.GetFirstToken().GetPreviousToken(); if (sourceText.AreOnSameLine(previousToken, firstParameter.GetFirstToken())) { // First parameter is on hte same line as the method. // We want to insert the parameter at the front of the existing parameter // list. That means we need to move the current first parameter to a new // line. Give the current first parameter the indentation of the second // parameter in the list. editor.InsertParameter(declaration, insertionIndex, parameterDeclaration); var nextParameter = existingParameters[insertionIndex]; var nextLeadingIndentation = GetDesiredLeadingIndentation( syntaxFacts, existingParameters[insertionIndex + 1], includeLeadingNewLine: true); editor.ReplaceNode( nextParameter, nextParameter.WithPrependedLeadingTrivia(nextLeadingIndentation) .WithAdditionalAnnotations(Formatter.Annotation)); } else { // First parameter is on its own line. No need to adjust its indentation. // Just copy its indentation over to the parameter we're inserting, and // make sure the current first parameter gets a newline so it stays on // its own line. // We want to insert the parameter at the front of the existing parameter // list. That means we need to move the current first parameter to a new // line. Give the current first parameter the indentation of the second // parameter in the list. var firstLeadingIndentation = GetDesiredLeadingIndentation( syntaxFacts, existingParameters[0], includeLeadingNewLine: false); editor.InsertParameter(declaration, insertionIndex, parameterDeclaration.WithLeadingTrivia(firstLeadingIndentation)); var nextParameter = existingParameters[insertionIndex]; editor.ReplaceNode( nextParameter, nextParameter.WithPrependedLeadingTrivia(syntaxFacts.ElasticCarriageReturnLineFeed) .WithAdditionalAnnotations(Formatter.Annotation)); } } else { // We're inserting somewhere after the start (but not at the end). Because // we've set placeOnNewLine, we know that the current comma we'll be placed // after already have a newline following it. So all we need for this new // parameter is to get the indentation of the following parameter. // Because we're going to 'steal' the existing comma from that parameter, // ensure that the next parameter has a new-line added to it so that it will // still stay on a new line. var nextParameter = existingParameters[insertionIndex]; var leadingIndentation = GetDesiredLeadingIndentation( syntaxFacts, existingParameters[insertionIndex], includeLeadingNewLine: false); parameterDeclaration = parameterDeclaration.WithPrependedLeadingTrivia(leadingIndentation); editor.InsertParameter(declaration, insertionIndex, parameterDeclaration); editor.ReplaceNode( nextParameter, nextParameter.WithPrependedLeadingTrivia(syntaxFacts.ElasticCarriageReturnLineFeed) .WithAdditionalAnnotations(Formatter.Annotation)); } } private static ImmutableArray<SyntaxTrivia> GetDesiredLeadingIndentation( ISyntaxFacts syntaxFacts, SyntaxNode node, bool includeLeadingNewLine) { using var _ = ArrayBuilder<SyntaxTrivia>.GetInstance(out var triviaList); if (includeLeadingNewLine) { triviaList.Add(syntaxFacts.ElasticCarriageReturnLineFeed); } var lastWhitespace = default(SyntaxTrivia); foreach (var trivia in node.GetLeadingTrivia().Reverse()) { if (syntaxFacts.IsWhitespaceTrivia(trivia)) { lastWhitespace = trivia; } else if (syntaxFacts.IsEndOfLineTrivia(trivia)) { break; } } if (lastWhitespace.RawKind != 0) { triviaList.Add(lastWhitespace); } return triviaList.ToImmutableAndClear(); } private static bool ShouldPlaceParametersOnNewLine( IReadOnlyList<SyntaxNode> parameters, CancellationToken cancellationToken) { if (parameters.Count <= 1) { return false; } var text = parameters[0].SyntaxTree.GetText(cancellationToken); for (int i = 1, n = parameters.Count; i < n; i++) { var lastParameter = parameters[i - 1]; var thisParameter = parameters[i]; if (text.AreOnSameLine(lastParameter.GetLastToken(), thisParameter.GetFirstToken())) { return false; } } // All parameters are on different lines. Place the new parameter on a new line as well. return true; } } |