private static async Task <CompletionChange> GetConversionChangeAsync( Document document, CompletionItem item, CancellationToken cancellationToken) { var position = SymbolCompletionItem.GetContextPosition(item); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var(dotToken, _) = GetDotAndExpressionStart(root, position, cancellationToken); var questionToken = dotToken.GetPreviousToken().Kind() == SyntaxKind.QuestionToken ? dotToken.GetPreviousToken() : (SyntaxToken?)null; var expression = (ExpressionSyntax)dotToken.GetRequiredParent(); expression = expression.GetRootConditionalAccessExpression() ?? expression; var replacement = questionToken != null ? $"(({item.DisplayText}){text.ToString(TextSpan.FromBounds(expression.SpanStart, questionToken.Value.FullSpan.Start))}){questionToken.Value}" : $"(({item.DisplayText}){text.ToString(TextSpan.FromBounds(expression.SpanStart, dotToken.SpanStart))})"; // If we're at `x.$$.y` then we only want to replace up through the first dot. var tokenOnLeft = root.FindTokenOnLeftOfPosition(position, includeSkipped: true); var fullTextChange = new TextChange( TextSpan.FromBounds( expression.SpanStart, tokenOnLeft.Kind() == SyntaxKind.DotDotToken ? tokenOnLeft.SpanStart + 1 : tokenOnLeft.Span.End), replacement); var newPosition = expression.SpanStart + replacement.Length; return(CompletionChange.Create(fullTextChange, newPosition)); }
private static async Task <CompletionChange> GetConversionChangeAsync( Document document, CompletionItem item, CancellationToken cancellationToken) { var position = SymbolCompletionItem.GetContextPosition(item); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var(dotToken, _) = GetDotAndExpressionStart(root, position, cancellationToken); var questionToken = dotToken.GetPreviousToken().Kind() == SyntaxKind.QuestionToken ? dotToken.GetPreviousToken() : (SyntaxToken?)null; var expression = (ExpressionSyntax)dotToken.GetRequiredParent(); expression = expression.GetRootConditionalAccessExpression() ?? expression; using var _ = ArrayBuilder <TextChange> .GetInstance(out var builder); // First, add the cast prior to the expression. var castText = $"(({item.DisplayText})"; builder.Add(new TextChange(new TextSpan(expression.SpanStart, 0), castText)); // The expression went up to either a `.`, `..`, `?.` or `?..` // // In the case of `expr.` produce `((T)expr)$$` // // In the case of `expr..` produce ((T)expr)$$. // // In the case of `expr?.` produce `((T)expr)?$$` if (questionToken == null) { // Always eat the first dot in `.` or `..` and replace that with the paren. builder.Add(new TextChange(new TextSpan(dotToken.SpanStart, 1), ")")); } else { // Place a paren before the question. builder.Add(new TextChange(new TextSpan(questionToken.Value.SpanStart, 0), ")")); // then remove the first dot that comes after. builder.Add(new TextChange(new TextSpan(dotToken.SpanStart, 1), "")); } // If the user partially wrote out the conversion type, delete what they've written. var tokenOnLeft = root.FindTokenOnLeftOfPosition(position, includeSkipped: true); if (CSharpSyntaxFacts.Instance.IsWord(tokenOnLeft)) { builder.Add(new TextChange(tokenOnLeft.Span, "")); } var newText = text.WithChanges(builder); var allChanges = builder.ToImmutable(); // Collapse all text changes down to a single change (for clients that only care about that), but also keep // all the individual changes around for clients that prefer the fine-grained information. return(CompletionChange.Create( CodeAnalysis.Completion.Utilities.Collapse(newText, allChanges), allChanges)); }