From 4ee5f980f7b5842b95a4591c0e279726b917a417 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Wed, 16 Mar 2022 10:48:35 -0700 Subject: [PATCH 01/58] start work on if snippet --- .../Snippets/CSharpConsoleSnippetProvider.cs | 1 + .../Portable/Snippets/CSharpSnippetService.cs | 1 + .../Core/Portable/FeaturesResources.resx | 3 + .../Snippets/AbstractSnippetService.cs | 1 + .../ExportSnippetProviderAttribute.cs | 1 + .../Core/Portable/Snippets/ISnippetService.cs | 1 + .../AbstractConsoleSnippetProvider.cs | 0 .../AbstractIfSnippetProvider.cs | 66 +++++++++++++++++++ .../AbstractSnippetProvider.cs | 1 + .../ISnippetProvider.cs | 2 +- 10 files changed, 76 insertions(+), 1 deletion(-) rename src/Features/Core/Portable/Snippets/{ => SnippetProviders}/AbstractConsoleSnippetProvider.cs (100%) create mode 100644 src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs rename src/Features/Core/Portable/Snippets/{ => SnippetProviders}/AbstractSnippetProvider.cs (99%) rename src/Features/Core/Portable/Snippets/{ => SnippetProviders}/ISnippetProvider.cs (95%) diff --git a/src/Features/CSharp/Portable/Snippets/CSharpConsoleSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpConsoleSnippetProvider.cs index 7dcce9be665b7..1ac7d20c24337 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpConsoleSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpConsoleSnippetProvider.cs @@ -19,6 +19,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Snippets; +using Microsoft.CodeAnalysis.Snippets.SnippetProviders; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; diff --git a/src/Features/CSharp/Portable/Snippets/CSharpSnippetService.cs b/src/Features/CSharp/Portable/Snippets/CSharpSnippetService.cs index 8546174899ff5..6f4f796207d72 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpSnippetService.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpSnippetService.cs @@ -15,6 +15,7 @@ using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; using Microsoft.CodeAnalysis.Snippets; +using Microsoft.CodeAnalysis.Snippets.SnippetProviders; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 677bb6d96b21f..44c8d347a4a12 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -3214,4 +3214,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Sort Imports or usings + + Insert an if statement + \ No newline at end of file diff --git a/src/Features/Core/Portable/Snippets/AbstractSnippetService.cs b/src/Features/Core/Portable/Snippets/AbstractSnippetService.cs index 1208d21bb0e1e..3cf776e009557 100644 --- a/src/Features/Core/Portable/Snippets/AbstractSnippetService.cs +++ b/src/Features/Core/Portable/Snippets/AbstractSnippetService.cs @@ -13,6 +13,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Snippets.SnippetProviders; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Snippets diff --git a/src/Features/Core/Portable/Snippets/ExportSnippetProviderAttribute.cs b/src/Features/Core/Portable/Snippets/ExportSnippetProviderAttribute.cs index ea2ad2e53d6ae..d20fbfefef59b 100644 --- a/src/Features/Core/Portable/Snippets/ExportSnippetProviderAttribute.cs +++ b/src/Features/Core/Portable/Snippets/ExportSnippetProviderAttribute.cs @@ -4,6 +4,7 @@ using System; using System.Composition; +using Microsoft.CodeAnalysis.Snippets.SnippetProviders; namespace Microsoft.CodeAnalysis.Snippets { diff --git a/src/Features/Core/Portable/Snippets/ISnippetService.cs b/src/Features/Core/Portable/Snippets/ISnippetService.cs index 30f6c4d86968a..f005325da09da 100644 --- a/src/Features/Core/Portable/Snippets/ISnippetService.cs +++ b/src/Features/Core/Portable/Snippets/ISnippetService.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Snippets.SnippetProviders; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Snippets diff --git a/src/Features/Core/Portable/Snippets/AbstractConsoleSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs similarity index 100% rename from src/Features/Core/Portable/Snippets/AbstractConsoleSnippetProvider.cs rename to src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs new file mode 100644 index 0000000000000..eef7bbdf70d14 --- /dev/null +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs @@ -0,0 +1,66 @@ +// 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; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Snippets +{ + internal abstract class AbstractIfSnippetProvider : AbstractSnippetProvider + { + public override string SnippetIdentifier => "if"; + + public override string SnippetDisplayName => FeaturesResources.Insert_an_if_statement; + + protected override async Task IsValidSnippetLocationAsync(Document document, int position, CancellationToken cancellationToken) + { + var semanticModel = await document.ReuseExistingSpeculativeModelAsync(position, cancellationToken).ConfigureAwait(false); + + var syntaxContext = document.GetRequiredLanguageService().CreateContext(document, semanticModel, position, cancellationToken); + return syntaxContext.IsStatementContext || syntaxContext.IsGlobalStatementContext; + } + + protected override Task> GenerateSnippetTextChangesAsync(Document document, int position, CancellationToken cancellationToken) + { + var snippetTextChange = GenerateSnippetTextChange(document, position); + return Task.FromResult(ImmutableArray.Create(snippetTextChange)); + } + + private static TextChange GenerateSnippetTextChange(Document document, int position) + { + var generator = SyntaxGenerator.GetGenerator(document); + + var ifStatement = generator.IfStatement(generator.TrueLiteralExpression(), Array.Empty(), Array.Empty()); + return new TextChange(TextSpan.FromBounds(position, position), ifStatement.ToFullString()); + } + + protected override int? GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget) + { + var invocationExpression = caretTarget.DescendantNodes().Where(syntaxFacts.Blo).FirstOrDefault(); + if (invocationExpression is null) + { + return null; + } + + var argumentListNode = syntaxFacts.GetArgumentListOfInvocationExpression(invocationExpression); + if (argumentListNode is null) + { + return null; + } + + syntaxFacts.GetPartsOfArgumentList(argumentListNode, out var openParenToken, out _, out _); + return openParenToken.Span.End; + } + } +} diff --git a/src/Features/Core/Portable/Snippets/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs similarity index 99% rename from src/Features/Core/Portable/Snippets/AbstractSnippetProvider.cs rename to src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs index a82d7121bd5ed..c69ee55e92672 100644 --- a/src/Features/Core/Portable/Snippets/AbstractSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Snippets.SnippetProviders; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Snippets diff --git a/src/Features/Core/Portable/Snippets/ISnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/ISnippetProvider.cs similarity index 95% rename from src/Features/Core/Portable/Snippets/ISnippetProvider.cs rename to src/Features/Core/Portable/Snippets/SnippetProviders/ISnippetProvider.cs index 5ffeaf1d2452b..ec5dfa51c773c 100644 --- a/src/Features/Core/Portable/Snippets/ISnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/ISnippetProvider.cs @@ -10,7 +10,7 @@ using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; -namespace Microsoft.CodeAnalysis.Snippets +namespace Microsoft.CodeAnalysis.Snippets.SnippetProviders { internal interface ISnippetProvider { From f7ff1ad2324db7811d5720aaa006ab6229f55882 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Wed, 16 Mar 2022 13:09:34 -0700 Subject: [PATCH 02/58] wip --- .../AbstractIfSnippetProvider.cs | 49 +++++++++++++++---- 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs index eef7bbdf70d14..21daa1ee50083 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs @@ -10,9 +10,11 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Snippets @@ -31,36 +33,63 @@ protected override async Task IsValidSnippetLocationAsync(Document documen return syntaxContext.IsStatementContext || syntaxContext.IsGlobalStatementContext; } - protected override Task> GenerateSnippetTextChangesAsync(Document document, int position, CancellationToken cancellationToken) + protected override async Task> GenerateSnippetTextChangesAsync(Document document, int position, CancellationToken cancellationToken) { - var snippetTextChange = GenerateSnippetTextChange(document, position); - return Task.FromResult(ImmutableArray.Create(snippetTextChange)); + var snippetTextChange = await GenerateSnippetTextChangeAsync(document, position, cancellationToken).ConfigureAwait(false); + return ImmutableArray.Create(snippetTextChange); } - private static TextChange GenerateSnippetTextChange(Document document, int position) + private static async Task GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken) { var generator = SyntaxGenerator.GetGenerator(document); + var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); + var token = tree.FindTokenOnLeftOfPosition(position, cancellationToken); + var syntaxFacts = document.GetRequiredLanguageService(); + var possibleNodes = token.GetAncestor(node => syntaxFacts.IsExpressionStatement(node)); + var ifStatement = generator.IfStatement(generator.TrueLiteralExpression(), Array.Empty(), Array.Empty()); return new TextChange(TextSpan.FromBounds(position, position), ifStatement.ToFullString()); } protected override int? GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget) { - var invocationExpression = caretTarget.DescendantNodes().Where(syntaxFacts.Blo).FirstOrDefault(); - if (invocationExpression is null) + return 0; + } + + protected override async Task AnnotateNodesToReformatAsync(Document document, + SyntaxAnnotation findSnippetAnnotation, SyntaxAnnotation cursorAnnotation, int position, CancellationToken cancellationToken) + { + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var syntaxFacts = document.GetRequiredLanguageService(); + var snippetExpressionNode = GetIfExpressionStatement(syntaxFacts, root, position); + if (snippetExpressionNode is null) + { + return root; + } + + var reformatSnippetNode = snippetExpressionNode.WithAdditionalAnnotations(findSnippetAnnotation, cursorAnnotation, Simplifier.Annotation, Formatter.Annotation); + return root.ReplaceNode(snippetExpressionNode, reformatSnippetNode); + } + + private static SyntaxNode? GetIfExpressionStatement(ISyntaxFactsService syntaxFacts, SyntaxNode root, int position) + { + var closestNode = root.FindNode(TextSpan.FromBounds(position, position)); + var nearestExpressionStatement = closestNode.FirstAncestorOrSelf(syntaxFacts.IsExpressionStatement); + if (nearestExpressionStatement is null) { return null; } - var argumentListNode = syntaxFacts.GetArgumentListOfInvocationExpression(invocationExpression); - if (argumentListNode is null) + // Checking to see if that expression statement that we found is + // starting at the same position as the position we inserted + // the Console WriteLine expression statement. + if (nearestExpressionStatement.SpanStart != position) { return null; } - syntaxFacts.GetPartsOfArgumentList(argumentListNode, out var openParenToken, out _, out _); - return openParenToken.Span.End; + return nearestExpressionStatement; } } } From bf3685ca049347b6a5ab12e799ecd9dc8022f315 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Thu, 17 Mar 2022 08:45:16 -0700 Subject: [PATCH 03/58] wip --- .../CSharpIfSnippetCompletionProviderTests.cs | 49 +++++++++++++++++++ .../Snippets/CSharpIfSnippetProvider.cs | 37 ++++++++++++++ .../AbstractIfSnippetProvider.cs | 14 +++--- .../Portable/xlf/FeaturesResources.cs.xlf | 5 ++ .../Portable/xlf/FeaturesResources.de.xlf | 5 ++ .../Portable/xlf/FeaturesResources.es.xlf | 5 ++ .../Portable/xlf/FeaturesResources.fr.xlf | 5 ++ .../Portable/xlf/FeaturesResources.it.xlf | 5 ++ .../Portable/xlf/FeaturesResources.ja.xlf | 5 ++ .../Portable/xlf/FeaturesResources.ko.xlf | 5 ++ .../Portable/xlf/FeaturesResources.pl.xlf | 5 ++ .../Portable/xlf/FeaturesResources.pt-BR.xlf | 5 ++ .../Portable/xlf/FeaturesResources.ru.xlf | 5 ++ .../Portable/xlf/FeaturesResources.tr.xlf | 5 ++ .../xlf/FeaturesResources.zh-Hans.xlf | 5 ++ .../xlf/FeaturesResources.zh-Hant.xlf | 5 ++ .../Services/SyntaxFacts/CSharpSyntaxFacts.cs | 9 ++++ .../Core/Services/SyntaxFacts/ISyntaxFacts.cs | 1 + .../SyntaxFacts/VisualBasicSyntaxFacts.vb | 5 +- 19 files changed, 172 insertions(+), 8 deletions(-) create mode 100644 src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs create mode 100644 src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs new file mode 100644 index 0000000000000..5136ff10df96b --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs @@ -0,0 +1,49 @@ +// 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; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Completion.CompletionProviders.Snippets; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders.Snippets +{ + public class CSharpIfSnippetCompletionProviderTests : AbstractCSharpCompletionProviderTests + { + private static readonly string s_itemToCommit = FeaturesResources.Insert_an_if_statement; + + internal override Type GetCompletionProviderType() + => typeof(CSharpSnippetCompletionProvider); + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippetInMethodTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + Ins$$ + } +}"; + + var expectedCodeAfterCommit = +@"$$class Program +{ + public void Method() + { + if (true) + { + } + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + } + } +} diff --git a/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs new file mode 100644 index 0000000000000..5b935419784ae --- /dev/null +++ b/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs @@ -0,0 +1,37 @@ +// 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; +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.Simplification; +using Microsoft.CodeAnalysis.Snippets; +using Microsoft.CodeAnalysis.Snippets.SnippetProviders; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.Snippets +{ + [ExportSnippetProvider(nameof(ISnippetProvider), LanguageNames.CSharp), Shared] + internal class CSharpIfSnippetProvider : AbstractIfSnippetProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CSharpIfSnippetProvider() + { + } + } +} diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs index 21daa1ee50083..4be4c7271d19f 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs @@ -47,14 +47,14 @@ private static async Task GenerateSnippetTextChangeAsync(Document do var syntaxFacts = document.GetRequiredLanguageService(); var possibleNodes = token.GetAncestor(node => syntaxFacts.IsExpressionStatement(node)); - - var ifStatement = generator.IfStatement(generator.TrueLiteralExpression(), Array.Empty(), Array.Empty()); + var ifStatement = generator.IfStatement(generator.TrueLiteralExpression(), Array.Empty()); return new TextChange(TextSpan.FromBounds(position, position), ifStatement.ToFullString()); } protected override int? GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget) { - return 0; + syntaxFacts.GetPartsOfIfStatement(caretTarget, out var openParen, out _, out _, out _); + return openParen.Span.End; } protected override async Task AnnotateNodesToReformatAsync(Document document, @@ -75,8 +75,8 @@ protected override async Task AnnotateNodesToReformatAsync(Document private static SyntaxNode? GetIfExpressionStatement(ISyntaxFactsService syntaxFacts, SyntaxNode root, int position) { var closestNode = root.FindNode(TextSpan.FromBounds(position, position)); - var nearestExpressionStatement = closestNode.FirstAncestorOrSelf(syntaxFacts.IsExpressionStatement); - if (nearestExpressionStatement is null) + var nearestStatement = closestNode.FirstAncestorOrSelf(syntaxFacts.IsStatement); + if (nearestStatement is null) { return null; } @@ -84,12 +84,12 @@ protected override async Task AnnotateNodesToReformatAsync(Document // Checking to see if that expression statement that we found is // starting at the same position as the position we inserted // the Console WriteLine expression statement. - if (nearestExpressionStatement.SpanStart != position) + if (nearestStatement.SpanStart != position) { return null; } - return nearestExpressionStatement; + return nearestStatement; } } } diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 4a3857a0e767b..83dd463e26cb6 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -960,6 +960,11 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn Inline refaktoring metody {0} se zachováním deklarace + + Insert an if statement + Insert an if statement + + Insufficient hexadecimal digits Nedostatek šestnáctkových číslic diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index d41c388e96060..3c7414fea0cf1 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -960,6 +960,11 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d "{0}" inline einbinden und beibehalten + + Insert an if statement + Insert an if statement + + Insufficient hexadecimal digits Nicht genügend Hexadezimalziffern. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index a00689ae96747..30f0f1a2eafd6 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -960,6 +960,11 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa Alinear y mantener "{0}" + + Insert an if statement + Insert an if statement + + Insufficient hexadecimal digits Insuficientes dígitos hexadecimales diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index ea5d1b303a9e1..a18dbf6441c12 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -960,6 +960,11 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai Inline et conserver '{0}' + + Insert an if statement + Insert an if statement + + Insufficient hexadecimal digits Chiffres hexadécimaux insuffisants diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 23f5163ccd79c..ef8382e5883c6 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -960,6 +960,11 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa Imposta come inline e mantieni '{0}' + + Insert an if statement + Insert an if statement + + Insufficient hexadecimal digits Cifre esadecimali insufficienti diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 9fd1348accba7..d306bea6202aa 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -960,6 +960,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma インラインおよび '{0}' の保持 + + Insert an if statement + Insert an if statement + + Insufficient hexadecimal digits 16 進数の数字が正しくありません diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 56adcfa7d4ae5..840e03f55859b 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -960,6 +960,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma '{0}'을(를) 인라인으로 지정 및 유지 + + Insert an if statement + Insert an if statement + + Insufficient hexadecimal digits 16진수가 부족합니다. diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 3f2f55b2f4f24..a70b6afeabff8 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -960,6 +960,11 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k Wstaw środwierszowo i zachowaj metodę „{0}” + + Insert an if statement + Insert an if statement + + Insufficient hexadecimal digits Zbyt mało cyfr szesnastkowych diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index 5a889a245e4e0..03c20bd726e5e 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -960,6 +960,11 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess Embutir e manter '{0}' + + Insert an if statement + Insert an if statement + + Insufficient hexadecimal digits Dígitos hexadecimais insuficientes diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index c12c19f45e5e2..8b2fd85e6c8bf 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -960,6 +960,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma Сделать "{0}" встроенным и сохранить его + + Insert an if statement + Insert an if statement + + Insufficient hexadecimal digits Недостаточно шестнадцатеричных цифр diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 1a8bc2b897b8d..dca5ef24b404f 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -960,6 +960,11 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be '{0}' öğesini satır içine al ve koru + + Insert an if statement + Insert an if statement + + Insufficient hexadecimal digits Yetersiz onaltılık basamak diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index a5e48ed3b957f..52f483d6220e2 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -960,6 +960,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 内联并保留“{0}” + + Insert an if statement + Insert an if statement + + Insufficient hexadecimal digits 无效的十六进制数字 diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index c548a03df2a76..7d9d2192cc6fd 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -960,6 +960,11 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma 內嵌並保留 '{0}' + + Insert an if statement + Insert an if statement + + Insufficient hexadecimal digits 十六進位數位不足 diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index 0530f462a6622..7fa285a1a51c0 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -1680,6 +1680,15 @@ public void GetPartsOfConditionalExpression(SyntaxNode node, out SyntaxNode cond whenFalse = conditionalExpression.WhenFalse; } + public void GetPartsOfIfStatement(SyntaxNode node, out SyntaxToken openParen, out SyntaxNode condition, out SyntaxToken closeParen, out SyntaxNode statement) + { + var ifStatement = (IfStatementSyntax)node; + openParen = ifStatement.OpenParenToken; + condition = ifStatement.Condition; + closeParen = ifStatement.CloseParenToken; + statement = ifStatement.Statement; + } + public void GetPartsOfInvocationExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxNode? argumentList) { var invocation = (InvocationExpressionSyntax)node; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs index 9dbb8e9628fcd..8a40c3b37fc18 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs @@ -562,6 +562,7 @@ void GetPartsOfInterpolationExpression(SyntaxNode node, void GetPartsOfCompilationUnit(SyntaxNode node, out SyntaxList imports, out SyntaxList attributeLists, out SyntaxList members); void GetPartsOfConditionalAccessExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxToken operatorToken, out SyntaxNode whenNotNull); void GetPartsOfConditionalExpression(SyntaxNode node, out SyntaxNode condition, out SyntaxNode whenTrue, out SyntaxNode whenFalse); + void GetPartsOfIfStatement(SyntaxNode node, out SyntaxToken openParen, out SyntaxNode condition, out SyntaxToken closeParen, out SyntaxNode statement); void GetPartsOfInvocationExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxNode? argumentList); void GetPartsOfIsPatternExpression(SyntaxNode node, out SyntaxNode left, out SyntaxToken isToken, out SyntaxNode right); void GetPartsOfMemberAccessExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxToken operatorToken, out SyntaxNode name); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb index 525a58fb3fb65..2c03009b428c1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb @@ -1895,6 +1895,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices whenFalse = conditionalExpression.WhenFalse End Sub + Public Sub GetPartsOfIfStatement(node As SyntaxNode, ByRef openParen As SyntaxToken, ByRef condition As SyntaxNode, ByRef closeParen As SyntaxToken, ByRef statement As SyntaxNode) Implements ISyntaxFacts.GetPartsOfIfStatement + Throw ExceptionUtilities.Unreachable + End Sub + Public Sub GetPartsOfInvocationExpression(node As SyntaxNode, ByRef expression As SyntaxNode, ByRef argumentList As SyntaxNode) Implements ISyntaxFacts.GetPartsOfInvocationExpression Dim invocation = DirectCast(node, InvocationExpressionSyntax) expression = invocation.Expression @@ -1978,7 +1982,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices Return initializer.Initializer.Initializers End Function - #End Region End Class End Namespace From 74f5583686735472b2cbc7585dd35252bce71173 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Thu, 17 Mar 2022 13:58:04 -0700 Subject: [PATCH 04/58] wip --- .../Core/Portable/Snippets/SnippetChange.cs | 16 ++++++++++++++-- .../AbstractIfSnippetProvider.cs | 17 +++++++---------- .../SnippetProviders/AbstractSnippetProvider.cs | 5 ++++- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/Features/Core/Portable/Snippets/SnippetChange.cs b/src/Features/Core/Portable/Snippets/SnippetChange.cs index 136e10d07b761..838e44d84f986 100644 --- a/src/Features/Core/Portable/Snippets/SnippetChange.cs +++ b/src/Features/Core/Portable/Snippets/SnippetChange.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using Microsoft.CodeAnalysis.Text; @@ -13,6 +14,12 @@ namespace Microsoft.CodeAnalysis.Snippets /// internal readonly struct SnippetChange { + /// + /// The primary text change. + /// This will be the change that has associated renaming and tab stops. + /// + public readonly TextChange? MainTextChange; + /// /// The TextChange's associated with introducing a snippet into a document /// @@ -23,15 +30,20 @@ internal readonly struct SnippetChange /// public readonly int? CursorPosition; + public readonly Dictionary> RenameAndLocationsMap; + public SnippetChange( + TextChange? mainTextChange, ImmutableArray textChanges, - int? cursorPosition) + int? cursorPosition + Dictionary>) { - if (textChanges.IsEmpty) + if (textChanges.IsEmpty || mainTextChange is null) { throw new ArgumentException($"{ textChanges.Length } must not be empty"); } + MainTextChange = mainTextChange; TextChanges = textChanges; CursorPosition = cursorPosition; } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs index 4be4c7271d19f..ed34171a544d1 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs @@ -33,20 +33,15 @@ protected override async Task IsValidSnippetLocationAsync(Document documen return syntaxContext.IsStatementContext || syntaxContext.IsGlobalStatementContext; } - protected override async Task> GenerateSnippetTextChangesAsync(Document document, int position, CancellationToken cancellationToken) + protected override Task> GenerateSnippetTextChangesAsync(Document document, int position, CancellationToken cancellationToken) { - var snippetTextChange = await GenerateSnippetTextChangeAsync(document, position, cancellationToken).ConfigureAwait(false); - return ImmutableArray.Create(snippetTextChange); + var snippetTextChange = GenerateSnippetTextChange(document, position); + return Task.FromResult(ImmutableArray.Create(snippetTextChange)); } - private static async Task GenerateSnippetTextChangeAsync(Document document, int position, CancellationToken cancellationToken) + private static TextChange GenerateSnippetTextChange(Document document, int position) { var generator = SyntaxGenerator.GetGenerator(document); - var tree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); - var token = tree.FindTokenOnLeftOfPosition(position, cancellationToken); - - var syntaxFacts = document.GetRequiredLanguageService(); - var possibleNodes = token.GetAncestor(node => syntaxFacts.IsExpressionStatement(node)); var ifStatement = generator.IfStatement(generator.TrueLiteralExpression(), Array.Empty()); return new TextChange(TextSpan.FromBounds(position, position), ifStatement.ToFullString()); } @@ -72,6 +67,8 @@ protected override async Task AnnotateNodesToReformatAsync(Document return root.ReplaceNode(snippetExpressionNode, reformatSnippetNode); } + protected override async + private static SyntaxNode? GetIfExpressionStatement(ISyntaxFactsService syntaxFacts, SyntaxNode root, int position) { var closestNode = root.FindNode(TextSpan.FromBounds(position, position)); @@ -83,7 +80,7 @@ protected override async Task AnnotateNodesToReformatAsync(Document // Checking to see if that expression statement that we found is // starting at the same position as the position we inserted - // the Console WriteLine expression statement. + // the if statement. if (nearestStatement.SpanStart != position) { return null; diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs index c69ee55e92672..c79a4bccdefb0 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -81,9 +81,12 @@ public async Task GetSnippetAsync(Document document, int position var reformattedRoot = await reformattedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var caretTarget = reformattedRoot.GetAnnotatedNodes(_cursorAnnotation).SingleOrDefault(); var changes = await reformattedDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); + var mainChange = changes.Where(x => x.Span.Start == position).FirstOrDefault(); return new SnippetChange( + mainTextChange: mainChange, textChanges: changes.ToImmutableArray(), - cursorPosition: GetTargetCaretPosition(syntaxFacts, caretTarget)); + cursorPosition: GetTargetCaretPosition(syntaxFacts, caretTarget), + ); } private async Task CleanupDocumentAsync( From fa966421a0d24b8e65363e7126306bb1a3800765 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Tue, 22 Mar 2022 16:28:59 -0700 Subject: [PATCH 05/58] wip --- .../AbstractSnippetCompletionProvider.cs | 18 ++++++++++++ .../Core/Portable/Snippets/ISnippetService.cs | 2 ++ .../Core/Portable/Snippets/SnippetChange.cs | 11 +++++-- .../AbstractIfSnippetProvider.cs | 29 ++++++++++++++++++- .../AbstractSnippetProvider.cs | 10 ++++++- 5 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs index f88cc061d1a59..06b207dcb4059 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs @@ -38,6 +38,24 @@ public override async Task GetChangeAsync(Document document, C return CompletionChange.Create(change, allTextChanges.AsImmutable(), newPosition: snippet.CursorPosition, includesCommitCharacter: true); } + private string? GenerateLSPSnippet(SnippetChange snippetChange) + { + var mainChangeText = snippetChange.MainTextChange!.Value.NewText; + var renameLocationsMap = snippetChange.RenameLocationsMap; + if (renameLocationsMap is null) + { + return mainChangeText; + } + + for (var i = 1; i < renameLocationsMap.Count; i++) + { + var value = renameLocationsMap[i]; + + foreach + newText += + } + } + public override async Task ProvideCompletionsAsync(CompletionContext context) { var document = context.Document; diff --git a/src/Features/Core/Portable/Snippets/ISnippetService.cs b/src/Features/Core/Portable/Snippets/ISnippetService.cs index f005325da09da..3d6f3049f1945 100644 --- a/src/Features/Core/Portable/Snippets/ISnippetService.cs +++ b/src/Features/Core/Portable/Snippets/ISnippetService.cs @@ -26,5 +26,7 @@ internal interface ISnippetService : ILanguageService /// Called upon by the AbstractSnippetCompletionProvider /// ISnippetProvider GetSnippetProvider(string snippetIdentifier); + + } } diff --git a/src/Features/Core/Portable/Snippets/SnippetChange.cs b/src/Features/Core/Portable/Snippets/SnippetChange.cs index 838e44d84f986..83cf73446a398 100644 --- a/src/Features/Core/Portable/Snippets/SnippetChange.cs +++ b/src/Features/Core/Portable/Snippets/SnippetChange.cs @@ -30,13 +30,17 @@ internal readonly struct SnippetChange /// public readonly int? CursorPosition; - public readonly Dictionary> RenameAndLocationsMap; + /// + /// The items that we will want to rename as well as the ordering + /// in which to visit those items. + /// + public readonly Dictionary>? RenameLocationsMap; public SnippetChange( TextChange? mainTextChange, ImmutableArray textChanges, - int? cursorPosition - Dictionary>) + int? cursorPosition, + Dictionary>? renameLocationsMap) { if (textChanges.IsEmpty || mainTextChange is null) { @@ -46,6 +50,7 @@ public SnippetChange( MainTextChange = mainTextChange; TextChanges = textChanges; CursorPosition = cursorPosition; + RenameLocationsMap = renameLocationsMap; } } } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs index ed34171a544d1..28ec7d9120381 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs @@ -67,7 +67,34 @@ protected override async Task AnnotateNodesToReformatAsync(Document return root.ReplaceNode(snippetExpressionNode, reformatSnippetNode); } - protected override async + protected override async Task>?> GetRenameLocationsMapAsync(Document document, int position, CancellationToken cancellationToken) + { + var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var syntaxFacts = document.GetRequiredLanguageService(); + var snippetExpressionNode = GetIfExpressionStatement(syntaxFacts, root, position); + if (snippetExpressionNode is null) + { + return null; + } + + var renameLocationsMap = new Dictionary>(); + syntaxFacts.GetPartsOfIfStatement(snippetExpressionNode, out _, out var condition, out _, out var statement); + var list1 = new List + { + new TextSpan(condition.SpanStart - snippetExpressionNode.SpanStart, condition.Span.Length) + }; + + renameLocationsMap.Add(1, list1); + + var list2 = new List + { + new TextSpan(statement.SpanStart - snippetExpressionNode.SpanStart, statement.Span.Length) + }; + + renameLocationsMap.Add(0, list2); + + return renameLocationsMap; + } private static SyntaxNode? GetIfExpressionStatement(ISyntaxFactsService syntaxFacts, SyntaxNode root, int position) { diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs index c79a4bccdefb0..688540dfa4128 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -2,6 +2,7 @@ // 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.Linq; using System.Threading; @@ -10,6 +11,7 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.EditAndContinue.Contracts; using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.ExtractMethod; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Shared.Extensions; @@ -76,6 +78,7 @@ public async Task GetSnippetAsync(Document document, int position var textChanges = await GenerateSnippetTextChangesAsync(document, position, cancellationToken).ConfigureAwait(false); var snippetDocument = await GetDocumentWithSnippetAsync(document, textChanges, cancellationToken).ConfigureAwait(false); + var renameLocationsMap = await GetRenameLocationsMapAsync(snippetDocument, position, cancellationToken).ConfigureAwait(false); var formatAnnotatedSnippetDocument = await AddFormatAnnotationAsync(snippetDocument, position, cancellationToken).ConfigureAwait(false); var reformattedDocument = await CleanupDocumentAsync(formatAnnotatedSnippetDocument, cancellationToken).ConfigureAwait(false); var reformattedRoot = await reformattedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); @@ -86,7 +89,7 @@ public async Task GetSnippetAsync(Document document, int position mainTextChange: mainChange, textChanges: changes.ToImmutableArray(), cursorPosition: GetTargetCaretPosition(syntaxFacts, caretTarget), - ); + renameLocationsMap: renameLocationsMap); } private async Task CleanupDocumentAsync( @@ -127,5 +130,10 @@ private async Task AddFormatAnnotationAsync(Document document, int pos document = document.WithSyntaxRoot(annotatedSnippetRoot); return document; } + + protected virtual Task>?> GetRenameLocationsMapAsync(Document document, int position, CancellationToken cancellationToken) + { + return Task.FromResult>?>(null); + } } } From 3fbf4c7d3fc71e35422002a30a479321053e7a01 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Wed, 23 Mar 2022 16:26:00 -0700 Subject: [PATCH 06/58] wip --- .../CSharpIfSnippetCompletionProviderTests.cs | 19 +++++++++--- .../AbstractSnippetCompletionProvider.cs | 31 ++++++++++++++----- .../Core/Portable/Snippets/SnippetChange.cs | 4 +-- .../AbstractIfSnippetProvider.cs | 14 ++++++--- .../AbstractSnippetProvider.cs | 9 +++--- .../Services/SyntaxFacts/CSharpSyntaxFacts.cs | 3 ++ .../Services/SyntaxFacts/CSharpSyntaxKinds.cs | 1 + .../Core/Services/SyntaxFacts/ISyntaxFacts.cs | 1 + .../SyntaxFacts/ISyntaxFactsExtensions.cs | 3 ++ .../Core/Services/SyntaxFacts/ISyntaxKinds.cs | 1 + .../SyntaxFacts/VisualBasicSyntaxFacts.vb | 3 ++ .../SyntaxFacts/VisualBasicSyntaxKinds.vb | 1 + 12 files changed, 67 insertions(+), 23 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs index 5136ff10df96b..3050c6f26543d 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs @@ -34,16 +34,27 @@ public void Method() }"; var expectedCodeAfterCommit = -@"$$class Program +@"class Program { public void Method() { - if (true) - { - } + if ($$true) { } } }"; await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippetInGlobalContextTest() + { + var markupBeforeCommit = +@"Ins$$ +"; + + var expectedCodeAfterCommit = +@"if ($$true) { } +"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + } } } diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs index 06b207dcb4059..233b12c11e4ca 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs @@ -34,27 +34,42 @@ public override async Task GetChangeAsync(Document document, C var allChangesDocument = document.WithText(allChangesText); var allTextChanges = await allChangesDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); + var lspSnippet = GenerateLSPSnippet(snippet); var change = Utilities.Collapse(allChangesText, allTextChanges.AsImmutable()); return CompletionChange.Create(change, allTextChanges.AsImmutable(), newPosition: snippet.CursorPosition, includesCommitCharacter: true); } - private string? GenerateLSPSnippet(SnippetChange snippetChange) + private static string? GenerateLSPSnippet(SnippetChange snippetChange) { - var mainChangeText = snippetChange.MainTextChange!.Value.NewText; + var mainChangeText = snippetChange.MainTextChange!.Value.NewText!; var renameLocationsMap = snippetChange.RenameLocationsMap; if (renameLocationsMap is null) { return mainChangeText; } - for (var i = 1; i < renameLocationsMap.Count; i++) + var count = 1; + var modifier = 0; + foreach (var (priority, identifier) in renameLocationsMap.Keys) { - var value = renameLocationsMap[i]; - - foreach - newText += + if (identifier.Length != 0) + { + var locationCount = renameLocationsMap[(priority, identifier)].Count; + var newStr = $"${{{{{count}:{identifier}}}}}"; + mainChangeText = mainChangeText.Replace(identifier, newStr); + modifier += ((newStr.Length - identifier.Length) * locationCount + 1); + } + else + { + var location = renameLocationsMap[(priority, identifier)][0]; + mainChangeText = mainChangeText.Insert(location.Start + modifier, $"$0"); + } + + count++; } - } + + return mainChangeText; + } //foreach (${{1:var}} ${{2:item}} in ${{3:collection}}) { $0} public override async Task ProvideCompletionsAsync(CompletionContext context) { diff --git a/src/Features/Core/Portable/Snippets/SnippetChange.cs b/src/Features/Core/Portable/Snippets/SnippetChange.cs index 83cf73446a398..b5126fe5c9002 100644 --- a/src/Features/Core/Portable/Snippets/SnippetChange.cs +++ b/src/Features/Core/Portable/Snippets/SnippetChange.cs @@ -34,13 +34,13 @@ internal readonly struct SnippetChange /// The items that we will want to rename as well as the ordering /// in which to visit those items. /// - public readonly Dictionary>? RenameLocationsMap; + public readonly Dictionary<(int, string), List>? RenameLocationsMap; public SnippetChange( TextChange? mainTextChange, ImmutableArray textChanges, int? cursorPosition, - Dictionary>? renameLocationsMap) + Dictionary<(int, string), List>? renameLocationsMap) { if (textChanges.IsEmpty || mainTextChange is null) { diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs index 28ec7d9120381..bc75da789a96e 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs @@ -67,7 +67,7 @@ protected override async Task AnnotateNodesToReformatAsync(Document return root.ReplaceNode(snippetExpressionNode, reformatSnippetNode); } - protected override async Task>?> GetRenameLocationsMapAsync(Document document, int position, CancellationToken cancellationToken) + protected override async Task>?> GetRenameLocationsMapAsync(Document document, int position, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var syntaxFacts = document.GetRequiredLanguageService(); @@ -77,21 +77,21 @@ protected override async Task AnnotateNodesToReformatAsync(Document return null; } - var renameLocationsMap = new Dictionary>(); + var renameLocationsMap = new Dictionary<(int, string), List>(); syntaxFacts.GetPartsOfIfStatement(snippetExpressionNode, out _, out var condition, out _, out var statement); var list1 = new List { new TextSpan(condition.SpanStart - snippetExpressionNode.SpanStart, condition.Span.Length) }; - renameLocationsMap.Add(1, list1); + renameLocationsMap.Add((1, condition.ToFullString()), list1); var list2 = new List { new TextSpan(statement.SpanStart - snippetExpressionNode.SpanStart, statement.Span.Length) }; - renameLocationsMap.Add(0, list2); + renameLocationsMap.Add((0, ""), list2); return renameLocationsMap; } @@ -99,7 +99,11 @@ protected override async Task AnnotateNodesToReformatAsync(Document private static SyntaxNode? GetIfExpressionStatement(ISyntaxFactsService syntaxFacts, SyntaxNode root, int position) { var closestNode = root.FindNode(TextSpan.FromBounds(position, position)); - var nearestStatement = closestNode.FirstAncestorOrSelf(syntaxFacts.IsStatement); + + var nearestStatement = syntaxFacts.IsGlobalStatement(closestNode) + ? syntaxFacts.GetStatementOfGlobalStatement(closestNode) + : closestNode.DescendantNodesAndSelf(syntaxFacts.IsIfStatement).FirstOrDefault(); + if (nearestStatement is null) { return null; diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs index 688540dfa4128..714cceede7985 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -77,14 +77,15 @@ public async Task GetSnippetAsync(Document document, int position var syntaxFacts = document.GetRequiredLanguageService(); var textChanges = await GenerateSnippetTextChangesAsync(document, position, cancellationToken).ConfigureAwait(false); var snippetDocument = await GetDocumentWithSnippetAsync(document, textChanges, cancellationToken).ConfigureAwait(false); - + var mainChanges = await snippetDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); + var mainChange = mainChanges.Where(change => change.Span.Start == position).FirstOrDefault(); var renameLocationsMap = await GetRenameLocationsMapAsync(snippetDocument, position, cancellationToken).ConfigureAwait(false); var formatAnnotatedSnippetDocument = await AddFormatAnnotationAsync(snippetDocument, position, cancellationToken).ConfigureAwait(false); var reformattedDocument = await CleanupDocumentAsync(formatAnnotatedSnippetDocument, cancellationToken).ConfigureAwait(false); var reformattedRoot = await reformattedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var caretTarget = reformattedRoot.GetAnnotatedNodes(_cursorAnnotation).SingleOrDefault(); var changes = await reformattedDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); - var mainChange = changes.Where(x => x.Span.Start == position).FirstOrDefault(); + return new SnippetChange( mainTextChange: mainChange, textChanges: changes.ToImmutableArray(), @@ -131,9 +132,9 @@ private async Task AddFormatAnnotationAsync(Document document, int pos return document; } - protected virtual Task>?> GetRenameLocationsMapAsync(Document document, int position, CancellationToken cancellationToken) + protected virtual Task>?> GetRenameLocationsMapAsync(Document document, int position, CancellationToken cancellationToken) { - return Task.FromResult>?>(null); + return Task.FromResult>?>(null); } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index 7fa285a1a51c0..0baccee85da8e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -1761,6 +1761,9 @@ public SyntaxNode GetExpressionOfAwaitExpression(SyntaxNode node) public SyntaxNode GetExpressionOfThrowExpression(SyntaxNode node) => ((ThrowExpressionSyntax)node).Expression; + public SyntaxNode GetStatementOfGlobalStatement(SyntaxNode node) + => ((GlobalStatementSyntax)node).Statement; + public SeparatedSyntaxList GetInitializersOfObjectMemberInitializer(SyntaxNode node) => node is InitializerExpressionSyntax(SyntaxKind.ObjectInitializerExpression) initExpr ? initExpr.Expressions : default; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs index 23804dbdba773..86af4a28bede1 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxKinds.cs @@ -93,6 +93,7 @@ public TSyntaxKind Convert(int kind) where TSyntaxKind : struct public int ExpressionStatement => (int)SyntaxKind.ExpressionStatement; public int ForEachStatement => (int)SyntaxKind.ForEachStatement; + public int IfStatement => (int)SyntaxKind.IfStatement; public int LocalDeclarationStatement => (int)SyntaxKind.LocalDeclarationStatement; public int? LocalFunctionStatement => (int)SyntaxKind.LocalFunctionStatement; public int LockStatement => (int)SyntaxKind.LockStatement; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs index 8a40c3b37fc18..fac6b070c0427 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs @@ -585,6 +585,7 @@ void GetPartsOfInterpolationExpression(SyntaxNode node, SyntaxNode GetExpressionOfExpressionStatement(SyntaxNode node); SyntaxNode? GetExpressionOfReturnStatement(SyntaxNode node); SyntaxNode GetExpressionOfThrowExpression(SyntaxNode node); + SyntaxNode GetStatementOfGlobalStatement(SyntaxNode node); SyntaxNode? GetValueOfEqualsValueClause(SyntaxNode? node); SeparatedSyntaxList GetInitializersOfObjectMemberInitializer(SyntaxNode node); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs index 11c5e5b63dbd0..75e3811f8a0cb 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFactsExtensions.cs @@ -826,6 +826,9 @@ public static bool IsExpressionStatement(this ISyntaxFacts syntaxFacts, [NotNull public static bool IsForEachStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node) => node?.RawKind == syntaxFacts.SyntaxKinds.ForEachStatement; + public static bool IsIfStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node) + => node?.RawKind == syntaxFacts.SyntaxKinds.IfStatement; + public static bool IsLocalDeclarationStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node) => node?.RawKind == syntaxFacts.SyntaxKinds.LocalDeclarationStatement; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs index 1df75b2a6b05f..906686e6c0562 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxKinds.cs @@ -131,6 +131,7 @@ internal interface ISyntaxKinds int ExpressionStatement { get; } int ForEachStatement { get; } + int IfStatement { get; } int LocalDeclarationStatement { get; } int? LocalFunctionStatement { get; } int LockStatement { get; } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb index 2c03009b428c1..91d84a2d3393e 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb @@ -1965,6 +1965,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices Throw New NotImplementedException() End Function + Public Function GetStatementOfGlobalStatement(node As SyntaxNode) As SyntaxNode Implements ISyntaxFacts.GetStatementOfGlobalStatement + Throw New NotImplementedException() + End Function Public Function GetInitializersOfObjectMemberInitializer(node As SyntaxNode) As SeparatedSyntaxList(Of SyntaxNode) Implements ISyntaxFacts.GetInitializersOfObjectMemberInitializer Dim initializer = TryCast(node, ObjectMemberInitializerSyntax) If initializer Is Nothing Then diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb index 1be621e8a6d19..1a4184cd323ee 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxKinds.vb @@ -95,6 +95,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices Public ReadOnly Property ExpressionStatement As Integer = SyntaxKind.ExpressionStatement Implements ISyntaxKinds.ExpressionStatement Public ReadOnly Property ForEachStatement As Integer = SyntaxKind.ForEachStatement Implements ISyntaxKinds.ForEachStatement + Public ReadOnly Property IfStatement As Integer = SyntaxKind.IfStatement Implements ISyntaxKinds.IfStatement Public ReadOnly Property LocalDeclarationStatement As Integer = SyntaxKind.LocalDeclarationStatement Implements ISyntaxKinds.LocalDeclarationStatement Public ReadOnly Property LocalFunctionStatement As Integer? = Nothing Implements ISyntaxKinds.LocalFunctionStatement Public ReadOnly Property LockStatement As Integer = SyntaxKind.SyncLockStatement Implements ISyntaxKinds.LockStatement From 977529d1e80163d7a5bbdfc076cee884b67a7347 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 28 Mar 2022 15:45:17 -0700 Subject: [PATCH 07/58] tests --- .../CSharpIfSnippetCompletionProviderTests.cs | 303 ++++++++++++++++++ 1 file changed, 303 insertions(+) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs index 3050c6f26543d..573eac6d08afd 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs @@ -56,5 +56,308 @@ public async Task InsertIfSnippetInGlobalContextTest() "; await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInBlockNamespaceTest() + { + var markupBeforeCommit = +@" +namespace Namespace +{ + $$ + class Program + { + public async Task MethodAsync() + { + } + } +}"; + await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInFileScopedNamespaceTest() + { + var markupBeforeCommit = +@" +namespace Namespace; +$$ +class Program +{ + public async Task MethodAsync() + { + } +} +"; + await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippetInConstructorTest() + { + var markupBeforeCommit = +@"class Program +{ + public Program() + { + var x = 5; + $$ + } +}"; + + var expectedCodeAfterCommit = +@"class Program +{ + public Program() + { + var x = 5; + if ($$true) { } + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippettInLocalFunctionTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + var x = 5; + void LocalMethod() + { + $$ + } + } +}"; + + var expectedCodeAfterCommit = +@"class Program +{ + public void Method() + { + var x = 5; + void LocalMethod() + { + if ($$true) { } + } + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippetInAnonymousFunctionTest() + { + var markupBeforeCommit = +@"public delegate void Print(int value); + +static void Main(string[] args) +{ + Print print = delegate(int val) { + $$ + }; + +}"; + + var expectedCodeAfterCommit = +@"public delegate void Print(int value); + +static void Main(string[] args) +{ + Print print = delegate(int val) { + if ($$true) { } + }; + +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippetInParenthesizedLambdaExpressionTest() + { + var markupBeforeCommit = +@"Func testForEquality = (x, y) => +{ + $$ + return x == y; +};"; + + var expectedCodeAfterCommit = +@"Func testForEquality = (x, y) => +{ + if ($$true) { } + return x == y; +};"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInSwitchExpression() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + var operation = 2; + + var result = operation switch + { + $$ + 1 => ""Case 1"", + 2 => ""Case 2"", + 3 => ""Case 3"", + 4 => ""Case 4"", + }; + } +}"; + await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInSingleLambdaExpression() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + Func f = x => $$; + } +}"; + await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInStringTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + var str = ""$$""; + } +}"; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInObjectInitializerTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + var str = new Test($$); + } +} + +class Test +{ + private string val; + + public Test(string val) + { + this.val = val; + } +}"; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInParameterListTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method(int x, $$) + { + } +}"; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInRecordDeclarationTest() + { + var markupBeforeCommit = +@"public record Person +{ + $$ + public string FirstName { get; init; } = default!; + public string LastName { get; init; } = default!; +};"; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task NoIfSnippetInVariableDeclarationTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + var x = $$ + } +}"; + + await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippetWithInvocationBeforeAndAfterCursorTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + Wr$$Blah + } +}"; + + var expectedCodeAfterCommit = +@"class Program +{ + public void Method() + { + if ($$true) { } + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippetWithInvocationUnderscoreBeforeAndAfterCursorTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + _Wr$$Blah_ + } +}"; + + var expectedCodeAfterCommit = +@"class Program +{ + public void Method() + { + if ($$true) { } + } +}"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + } } } From 2c1ed3813ce24ac39b2b9c71d4c4b97dea21f4ab Mon Sep 17 00:00:00 2001 From: akhera99 Date: Wed, 6 Apr 2022 08:50:22 -0700 Subject: [PATCH 08/58] wip --- eng/Versions.props | 25 ++++++++++--------- .../CodeFixes/CSharpCodeFixes.projitems | 2 +- .../AsyncCompletion/CommitManager.cs | 6 ++++- .../AsyncCompletion/CommitManagerProvider.cs | 7 ++++-- ...crosoft.CodeAnalysis.EditorFeatures.csproj | 1 + .../AbstractSnippetCompletionProvider.cs | 1 - .../Core/WorkspaceExtensions.projitems | 2 +- 7 files changed, 26 insertions(+), 18 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 3342c7e8206f8..638f9bd701c1f 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -26,11 +26,11 @@ 4.1.0 16.10.230 - 17.0.487 + 17.2.163-preview 5.0.0-alpha1.19409.1 5.0.0-preview.1.20112.8 - 17.2.8 - 17.0.31723.112 + 17.3.1 + 17.2.0-preview-1-32131-009 16.5.0 13.0.1 - 2.11.14-alpha + 2.11.19-alpha + diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs index 233b12c11e4ca..6053983b184e1 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs @@ -34,7 +34,6 @@ public override async Task GetChangeAsync(Document document, C var allChangesDocument = document.WithText(allChangesText); var allTextChanges = await allChangesDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); - var lspSnippet = GenerateLSPSnippet(snippet); var change = Utilities.Collapse(allChangesText, allTextChanges.AsImmutable()); return CompletionChange.Create(change, allTextChanges.AsImmutable(), newPosition: snippet.CursorPosition, includesCommitCharacter: true); } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems index 37665675673e7..3bfc333d315be 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Workspace/Core/WorkspaceExtensions.projitems @@ -97,4 +97,4 @@ - + \ No newline at end of file From c705fd810b7d873f1b5f2461ee8f2f03749da524 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Wed, 6 Apr 2022 12:17:12 -0700 Subject: [PATCH 09/58] wip --- eng/Versions.props | 28 +++++++++---------- .../AsyncCompletion/CommitManager.cs | 5 ++-- .../AsyncCompletion/CommitManagerProvider.cs | 5 ++-- ...crosoft.CodeAnalysis.EditorFeatures.csproj | 1 - 4 files changed, 17 insertions(+), 22 deletions(-) diff --git a/eng/Versions.props b/eng/Versions.props index 638f9bd701c1f..fd58e37c00054 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -26,11 +26,11 @@ 4.1.0 16.10.230 - 17.2.163-preview + 17.0.487 5.0.0-alpha1.19409.1 5.0.0-preview.1.20112.8 - 17.3.1 - 17.2.0-preview-1-32131-009 + 17.2.8 + 17.0.31723.112 16.5.0 13.0.1 - 2.11.19-alpha + 2.11.14-alpha - From 85c12ccd7189c80a2ba84c154302152bf72d1735 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Tue, 12 Apr 2022 13:31:49 -0700 Subject: [PATCH 10/58] lsp service now works --- .../AsyncCompletion/CommitManager.cs | 142 +++++++++++------- .../AsyncCompletion/CommitManagerProvider.cs | 3 +- .../Snippets/CSharpIfSnippetProvider.cs | 8 + .../Portable/Completion/CompletionChange.cs | 24 +++ .../AbstractSnippetCompletionProvider.cs | 35 ++--- .../Snippets/SnippetCompletionItem.cs | 5 + .../Core/Portable/PublicAPI.Unshipped.txt | 2 +- .../Core/Portable/Snippets/SnippetChange.cs | 6 +- .../AbstractIfSnippetProvider.cs | 22 +-- .../AbstractSnippetProvider.cs | 8 +- .../Services/SyntaxFacts/CSharpSyntaxFacts.cs | 9 -- .../Core/Services/SyntaxFacts/ISyntaxFacts.cs | 1 - .../SyntaxFacts/VisualBasicSyntaxFacts.vb | 4 - 13 files changed, 159 insertions(+), 110 deletions(-) diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs index dbbed431637a3..b1b8eef1fa9b5 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs @@ -25,7 +25,9 @@ using AsyncCompletionData = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data; using RoslynCompletionItem = Microsoft.CodeAnalysis.Completion.CompletionItem; using VSCompletionItem = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionItem; -using System.ComponentModel.Composition; +using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; +using Microsoft.CodeAnalysis.Completion.Providers.Snippets; +using Microsoft.CodeAnalysis.LanguageServer; namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion { @@ -234,84 +236,108 @@ private AsyncCompletionData.CommitResult Commit( return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None); } - var textChange = change.TextChange; - var triggerSnapshotSpan = new SnapshotSpan(triggerSnapshot, textChange.Span.ToSpan()); - var mappedSpan = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); - - using (var edit = subjectBuffer.CreateEdit(EditOptions.DefaultMinimalChange, reiteratedVersionNumber: null, editTag: null)) + var snippetServiceSucceeded = false; + if (SnippetCompletionItem.IsSnippet(roslynItem)) { - edit.Replace(mappedSpan.Span, change.TextChange.NewText); - - // edit.Apply() may trigger changes made by extensions. - // updatedCurrentSnapshot will contain changes made by Roslyn but not by other extensions. - var updatedCurrentSnapshot = edit.Apply(); + var type = _languageServerSnippetExpander.GetType(); + var expandMethod = type.GetMethod("TryExpand"); + var lspSnippetText = change.LSPSnippet; + var sourceText = document.GetTextAsync(cancellationToken).WaitAndGetResult(cancellationToken); + var textEdit = new LSP.TextEdit() + { + Range = ProtocolConversions.TextSpanToRange(change.TextChange.Span, sourceText), + NewText = lspSnippetText! + }; - if (change.NewPosition.HasValue) + if (expandMethod is not null) { - // Roslyn knows how to position the caret in the snapshot we just created. - // If there were more edits made by extensions, TryMoveCaretToAndEnsureVisible maps the snapshot point to the most recent one. - view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(updatedCurrentSnapshot, change.NewPosition.Value)); + var expandMethodResult = expandMethod.Invoke(_languageServerSnippetExpander, new object[] { textEdit, view, triggerSnapshot }); + snippetServiceSucceeded = expandMethodResult is not null && (bool)expandMethodResult; } - else + } + + if (!snippetServiceSucceeded) + { + + var textChange = change.TextChange; + var triggerSnapshotSpan = new SnapshotSpan(triggerSnapshot, textChange.Span.ToSpan()); + var mappedSpan = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); + + using (var edit = subjectBuffer.CreateEdit(EditOptions.DefaultMinimalChange, reiteratedVersionNumber: null, editTag: null)) { - // Or, If we're doing a minimal change, then the edit that we make to the - // buffer may not make the total text change that places the caret where we - // would expect it to go based on the requested change. In this case, - // determine where the item should go and set the care manually. - - // Note: we only want to move the caret if the caret would have been moved - // by the edit. i.e. if the caret was actually in the mapped span that - // we're replacing. - var caretPositionInBuffer = view.GetCaretPoint(subjectBuffer); - if (caretPositionInBuffer.HasValue && mappedSpan.IntersectsWith(caretPositionInBuffer.Value)) + edit.Replace(mappedSpan.Span, change.TextChange.NewText); + + // edit.Apply() may trigger changes made by extensions. + // updatedCurrentSnapshot will contain changes made by Roslyn but not by other extensions. + var updatedCurrentSnapshot = edit.Apply(); + + if (change.NewPosition.HasValue) { - view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(subjectBuffer.CurrentSnapshot, mappedSpan.Start.Position + textChange.NewText?.Length ?? 0)); + // Roslyn knows how to position the caret in the snapshot we just created. + // If there were more edits made by extensions, TryMoveCaretToAndEnsureVisible maps the snapshot point to the most recent one. + view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(updatedCurrentSnapshot, change.NewPosition.Value)); } else { - view.Caret.EnsureVisible(); + // Or, If we're doing a minimal change, then the edit that we make to the + // buffer may not make the total text change that places the caret where we + // would expect it to go based on the requested change. In this case, + // determine where the item should go and set the care manually. + + // Note: we only want to move the caret if the caret would have been moved + // by the edit. i.e. if the caret was actually in the mapped span that + // we're replacing. + var caretPositionInBuffer = view.GetCaretPoint(subjectBuffer); + if (caretPositionInBuffer.HasValue && mappedSpan.IntersectsWith(caretPositionInBuffer.Value)) + { + view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(subjectBuffer.CurrentSnapshot, mappedSpan.Start.Position + textChange.NewText?.Length ?? 0)); + } + else + { + view.Caret.EnsureVisible(); + } } - } - includesCommitCharacter = change.IncludesCommitCharacter; + includesCommitCharacter = change.IncludesCommitCharacter; - if (roslynItem.Rules.FormatOnCommit) - { - // The edit updates the snapshot however other extensions may make changes there. - // Therefore, it is required to use subjectBuffer.CurrentSnapshot for further calculations rather than the updated current snapshot defined above. - var currentDocument = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); - var formattingService = currentDocument?.GetRequiredLanguageService(); - - if (currentDocument != null && formattingService != null) + if (roslynItem.Rules.FormatOnCommit) { - var spanToFormat = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); - var changes = formattingService.GetFormattingChangesAsync( - currentDocument, spanToFormat.Span.ToTextSpan(), documentOptions: null, CancellationToken.None).WaitAndGetResult(CancellationToken.None); - currentDocument.Project.Solution.Workspace.ApplyTextChanges(currentDocument.Id, changes, CancellationToken.None); + // The edit updates the snapshot however other extensions may make changes there. + // Therefore, it is required to use subjectBuffer.CurrentSnapshot for further calculations rather than the updated current snapshot defined above. + var currentDocument = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + var formattingService = currentDocument?.GetRequiredLanguageService(); + + if (currentDocument != null && formattingService != null) + { + var spanToFormat = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); + var changes = formattingService.GetFormattingChangesAsync( + currentDocument, spanToFormat.Span.ToTextSpan(), documentOptions: null, CancellationToken.None).WaitAndGetResult(CancellationToken.None); + currentDocument.Project.Solution.Workspace.ApplyTextChanges(currentDocument.Id, changes, CancellationToken.None); + } } } - } - _recentItemsManager.MakeMostRecentItem(roslynItem.FilterText); + _recentItemsManager.MakeMostRecentItem(roslynItem.FilterText); - if (provider is INotifyCommittingItemCompletionProvider notifyProvider) - { - _ = ThreadingContext.JoinableTaskFactory.RunAsync(async () => + if (provider is INotifyCommittingItemCompletionProvider notifyProvider) { + _ = ThreadingContext.JoinableTaskFactory.RunAsync(async () => + { // Make sure the notification isn't sent on UI thread. - await TaskScheduler.Default; - _ = notifyProvider.NotifyCommittingItemAsync(document, roslynItem, commitCharacter, cancellationToken).ReportNonFatalErrorAsync(); - }); - } + await TaskScheduler.Default; + _ = notifyProvider.NotifyCommittingItemAsync(document, roslynItem, commitCharacter, cancellationToken).ReportNonFatalErrorAsync(); + }); + } - if (includesCommitCharacter) - { - return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.SuppressFurtherTypeCharCommandHandlers); - } + if (includesCommitCharacter) + { + return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.SuppressFurtherTypeCharCommandHandlers); + } - if (commitCharacter == '\n' && SendEnterThroughToEditor(rules, roslynItem, filterText)) - { - return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.RaiseFurtherReturnKeyAndTabKeyCommandHandlers); + if (commitCharacter == '\n' && SendEnterThroughToEditor(rules, roslynItem, filterText)) + { + return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.RaiseFurtherReturnKeyAndTabKeyCommandHandlers); + } } return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None); diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManagerProvider.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManagerProvider.cs index 76f73c5e270d5..595bd12f9553b 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManagerProvider.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManagerProvider.cs @@ -26,7 +26,8 @@ internal class CommitManagerProvider : IAsyncCompletionCommitManagerProvider [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CommitManagerProvider(IThreadingContext threadingContext, RecentItemsManager recentItemsManager, IGlobalOptionService globalOptions, [Import("LanguageServerSnippetExpander")] object languageServerSnippetExpander) + public CommitManagerProvider(IThreadingContext threadingContext, RecentItemsManager recentItemsManager, IGlobalOptionService globalOptions, + [Import("Microsoft.VisualStudio.LanguageServer.Client.Snippets.LanguageServerSnippetExpander")] object languageServerSnippetExpander) { _threadingContext = threadingContext; _recentItemsManager = recentItemsManager; diff --git a/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs index 5b935419784ae..4b1837517a9fc 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs @@ -33,5 +33,13 @@ internal class CSharpIfSnippetProvider : AbstractIfSnippetProvider public CSharpIfSnippetProvider() { } + + protected override void GetPartsOfIfStatement(SyntaxNode node, out SyntaxToken openParen, out SyntaxNode condition, out SyntaxNode statement) + { + var ifStatement = (IfStatementSyntax)node; + openParen = ifStatement.OpenParenToken; + condition = ifStatement.Condition; + statement = ifStatement.Statement; + } } } diff --git a/src/Features/Core/Portable/Completion/CompletionChange.cs b/src/Features/Core/Portable/Completion/CompletionChange.cs index 4c7f456992d04..0652cb9be8c1a 100644 --- a/src/Features/Core/Portable/Completion/CompletionChange.cs +++ b/src/Features/Core/Portable/Completion/CompletionChange.cs @@ -40,6 +40,8 @@ public sealed class CompletionChange /// public bool IncludesCommitCharacter { get; } + internal string? LSPSnippet { get; } + private CompletionChange( TextChange textChange, ImmutableArray textChanges, int? newPosition, bool includesCommitCharacter) { @@ -51,6 +53,18 @@ private CompletionChange( TextChanges = ImmutableArray.Create(textChange); } + private CompletionChange( + TextChange textChange, ImmutableArray textChanges, int? newPosition, bool includesCommitCharacter, string? lspSnippet) + { + TextChange = textChange; + NewPosition = newPosition; + IncludesCommitCharacter = includesCommitCharacter; + TextChanges = textChanges.NullToEmpty(); + if (TextChanges.IsEmpty) + TextChanges = ImmutableArray.Create(textChange); + LSPSnippet = lspSnippet; + } + /// /// Creates a new instance. /// @@ -97,6 +111,16 @@ public static CompletionChange Create( return new CompletionChange(textChange, textChanges, newPosition, includesCommitCharacter); } + internal static CompletionChange CreateSpecialLSPSnippetChange( + TextChange textChange, + ImmutableArray textChanges, + int? newPosition = null, + bool includesCommitCharacter = false, + string? lspSnippet = null) + { + return new CompletionChange(textChange, textChanges, newPosition, includesCommitCharacter, lspSnippet); + } + /// /// Creates a copy of this with the property changed. /// diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs index 6053983b184e1..717610064af5a 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs @@ -3,8 +3,10 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.ConvertToInterpolatedString; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Snippets; using Microsoft.CodeAnalysis.Text; @@ -35,40 +37,35 @@ public override async Task GetChangeAsync(Document document, C var allTextChanges = await allChangesDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); var change = Utilities.Collapse(allChangesText, allTextChanges.AsImmutable()); - return CompletionChange.Create(change, allTextChanges.AsImmutable(), newPosition: snippet.CursorPosition, includesCommitCharacter: true); + var lspSnippet = GenerateLSPSnippet(snippet.MainTextChange, snippet.Placeholders); + return CompletionChange.CreateSpecialLSPSnippetChange(change, allTextChanges.AsImmutable(), newPosition: snippet.CursorPosition, includesCommitCharacter: true, lspSnippet); } - private static string? GenerateLSPSnippet(SnippetChange snippetChange) + private static string? GenerateLSPSnippet(TextChange? textChange, List<(string, List)>? placeholders) { - var mainChangeText = snippetChange.MainTextChange!.Value.NewText!; - var renameLocationsMap = snippetChange.RenameLocationsMap; - if (renameLocationsMap is null) + var textChangeText = textChange!.Value.NewText!; + if (placeholders is null) { - return mainChangeText; + return textChangeText; } - var count = 1; - var modifier = 0; - foreach (var (priority, identifier) in renameLocationsMap.Keys) + for (var i = 0; i < placeholders.Count; i++) { + var (identifier, placeholderList) = placeholders[i]; if (identifier.Length != 0) { - var locationCount = renameLocationsMap[(priority, identifier)].Count; - var newStr = $"${{{{{count}:{identifier}}}}}"; - mainChangeText = mainChangeText.Replace(identifier, newStr); - modifier += ((newStr.Length - identifier.Length) * locationCount + 1); + var newStr = $"${{{i}:{identifier}}}"; + textChangeText = textChangeText.Replace(identifier, newStr); } else { - var location = renameLocationsMap[(priority, identifier)][0]; - mainChangeText = mainChangeText.Insert(location.Start + modifier, $"$0"); + var location = placeholderList[0]; + textChangeText = textChangeText.Insert(location.Start, $"$0"); } - - count++; } - return mainChangeText; - } //foreach (${{1:var}} ${{2:item}} in ${{3:collection}}) { $0} + return textChangeText; + } public override async Task ProvideCompletionsAsync(CompletionContext context) { diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs index edb4a3d156c7f..63d5190fba3f9 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs @@ -42,5 +42,10 @@ public static int GetInvocationPosition(CompletionItem item) Contract.ThrowIfFalse(int.TryParse(text, out var num)); return num; } + + public static bool IsSnippet(CompletionItem item) + { + return item.Properties.TryGetValue("SnippetIdentifier", out var _); + } } } diff --git a/src/Features/Core/Portable/PublicAPI.Unshipped.txt b/src/Features/Core/Portable/PublicAPI.Unshipped.txt index a3ca61c8d6d98..d603bac57d00f 100644 --- a/src/Features/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Features/Core/Portable/PublicAPI.Unshipped.txt @@ -1,3 +1,3 @@ *REMOVED*Microsoft.CodeAnalysis.QuickInfo.QuickInfoService.QuickInfoService() -> void *REMOVED*virtual Microsoft.CodeAnalysis.QuickInfo.QuickInfoService.GetQuickInfoAsync(Microsoft.CodeAnalysis.Document document, int position, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task -Microsoft.CodeAnalysis.QuickInfo.QuickInfoService.GetQuickInfoAsync(Microsoft.CodeAnalysis.Document document, int position, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task +Microsoft.CodeAnalysis.QuickInfo.QuickInfoService.GetQuickInfoAsync(Microsoft.CodeAnalysis.Document document, int position, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task \ No newline at end of file diff --git a/src/Features/Core/Portable/Snippets/SnippetChange.cs b/src/Features/Core/Portable/Snippets/SnippetChange.cs index b5126fe5c9002..965de7f8b8e74 100644 --- a/src/Features/Core/Portable/Snippets/SnippetChange.cs +++ b/src/Features/Core/Portable/Snippets/SnippetChange.cs @@ -34,13 +34,13 @@ internal readonly struct SnippetChange /// The items that we will want to rename as well as the ordering /// in which to visit those items. /// - public readonly Dictionary<(int, string), List>? RenameLocationsMap; + public readonly List<(string, List)>? Placeholders; public SnippetChange( TextChange? mainTextChange, ImmutableArray textChanges, int? cursorPosition, - Dictionary<(int, string), List>? renameLocationsMap) + List<(string, List)>? placeholders) { if (textChanges.IsEmpty || mainTextChange is null) { @@ -50,7 +50,7 @@ public SnippetChange( MainTextChange = mainTextChange; TextChanges = textChanges; CursorPosition = cursorPosition; - RenameLocationsMap = renameLocationsMap; + Placeholders = placeholders; } } } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs index bc75da789a96e..7dd1e63cc4220 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs @@ -21,6 +21,7 @@ namespace Microsoft.CodeAnalysis.Snippets { internal abstract class AbstractIfSnippetProvider : AbstractSnippetProvider { + protected abstract void GetPartsOfIfStatement(SyntaxNode node, out SyntaxToken openParen, out SyntaxNode condition, out SyntaxNode statement); public override string SnippetIdentifier => "if"; public override string SnippetDisplayName => FeaturesResources.Insert_an_if_statement; @@ -48,7 +49,7 @@ private static TextChange GenerateSnippetTextChange(Document document, int posit protected override int? GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget) { - syntaxFacts.GetPartsOfIfStatement(caretTarget, out var openParen, out _, out _, out _); + GetPartsOfIfStatement(caretTarget, out var openParen, out _, out _); return openParen.Span.End; } @@ -67,7 +68,7 @@ protected override async Task AnnotateNodesToReformatAsync(Document return root.ReplaceNode(snippetExpressionNode, reformatSnippetNode); } - protected override async Task>?> GetRenameLocationsMapAsync(Document document, int position, CancellationToken cancellationToken) + protected override async Task)>?> GetRenameLocationsMapAsync(Document document, int position, CancellationToken cancellationToken) { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var syntaxFacts = document.GetRequiredLanguageService(); @@ -77,21 +78,22 @@ protected override async Task AnnotateNodesToReformatAsync(Document return null; } - var renameLocationsMap = new Dictionary<(int, string), List>(); - syntaxFacts.GetPartsOfIfStatement(snippetExpressionNode, out _, out var condition, out _, out var statement); - var list1 = new List + var renameLocationsMap = new List<(string, List)>(); + GetPartsOfIfStatement(snippetExpressionNode, out _, out var condition, out var statement); + + var list2 = new List { - new TextSpan(condition.SpanStart - snippetExpressionNode.SpanStart, condition.Span.Length) + new TextSpan(statement.SpanStart - snippetExpressionNode.SpanStart + 1, statement.Span.Length) }; - renameLocationsMap.Add((1, condition.ToFullString()), list1); + renameLocationsMap.Add(("", list2)); - var list2 = new List + var list1 = new List { - new TextSpan(statement.SpanStart - snippetExpressionNode.SpanStart, statement.Span.Length) + new TextSpan(condition.SpanStart - snippetExpressionNode.SpanStart, condition.Span.Length) }; - renameLocationsMap.Add((0, ""), list2); + renameLocationsMap.Add((condition.ToFullString(), list1)); return renameLocationsMap; } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs index 714cceede7985..5448abc793607 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -79,7 +79,7 @@ public async Task GetSnippetAsync(Document document, int position var snippetDocument = await GetDocumentWithSnippetAsync(document, textChanges, cancellationToken).ConfigureAwait(false); var mainChanges = await snippetDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); var mainChange = mainChanges.Where(change => change.Span.Start == position).FirstOrDefault(); - var renameLocationsMap = await GetRenameLocationsMapAsync(snippetDocument, position, cancellationToken).ConfigureAwait(false); + var placeholders = await GetRenameLocationsMapAsync(snippetDocument, position, cancellationToken).ConfigureAwait(false); var formatAnnotatedSnippetDocument = await AddFormatAnnotationAsync(snippetDocument, position, cancellationToken).ConfigureAwait(false); var reformattedDocument = await CleanupDocumentAsync(formatAnnotatedSnippetDocument, cancellationToken).ConfigureAwait(false); var reformattedRoot = await reformattedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); @@ -90,7 +90,7 @@ public async Task GetSnippetAsync(Document document, int position mainTextChange: mainChange, textChanges: changes.ToImmutableArray(), cursorPosition: GetTargetCaretPosition(syntaxFacts, caretTarget), - renameLocationsMap: renameLocationsMap); + placeholders: placeholders); } private async Task CleanupDocumentAsync( @@ -132,9 +132,9 @@ private async Task AddFormatAnnotationAsync(Document document, int pos return document; } - protected virtual Task>?> GetRenameLocationsMapAsync(Document document, int position, CancellationToken cancellationToken) + protected virtual Task)>?> GetRenameLocationsMapAsync(Document document, int position, CancellationToken cancellationToken) { - return Task.FromResult>?>(null); + return Task.FromResult)>?>(null); } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index bcd1e72bb5bbb..9baee6fbf81cf 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -1690,15 +1690,6 @@ public void GetPartsOfConditionalExpression(SyntaxNode node, out SyntaxNode cond whenFalse = conditionalExpression.WhenFalse; } - public void GetPartsOfIfStatement(SyntaxNode node, out SyntaxToken openParen, out SyntaxNode condition, out SyntaxToken closeParen, out SyntaxNode statement) - { - var ifStatement = (IfStatementSyntax)node; - openParen = ifStatement.OpenParenToken; - condition = ifStatement.Condition; - closeParen = ifStatement.CloseParenToken; - statement = ifStatement.Statement; - } - public void GetPartsOfInvocationExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxNode? argumentList) { var invocation = (InvocationExpressionSyntax)node; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs index ca6028cd7d3bc..11530933036fa 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs @@ -564,7 +564,6 @@ void GetPartsOfInterpolationExpression(SyntaxNode node, void GetPartsOfCompilationUnit(SyntaxNode node, out SyntaxList imports, out SyntaxList attributeLists, out SyntaxList members); void GetPartsOfConditionalAccessExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxToken operatorToken, out SyntaxNode whenNotNull); void GetPartsOfConditionalExpression(SyntaxNode node, out SyntaxNode condition, out SyntaxNode whenTrue, out SyntaxNode whenFalse); - void GetPartsOfIfStatement(SyntaxNode node, out SyntaxToken openParen, out SyntaxNode condition, out SyntaxToken closeParen, out SyntaxNode statement); void GetPartsOfInvocationExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxNode? argumentList); void GetPartsOfIsPatternExpression(SyntaxNode node, out SyntaxNode left, out SyntaxToken isToken, out SyntaxNode right); void GetPartsOfMemberAccessExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxToken operatorToken, out SyntaxNode name); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb index d81d2bf5280d4..3ddc33515a34f 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb @@ -1903,10 +1903,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices whenFalse = conditionalExpression.WhenFalse End Sub - Public Sub GetPartsOfIfStatement(node As SyntaxNode, ByRef openParen As SyntaxToken, ByRef condition As SyntaxNode, ByRef closeParen As SyntaxToken, ByRef statement As SyntaxNode) Implements ISyntaxFacts.GetPartsOfIfStatement - Throw ExceptionUtilities.Unreachable - End Sub - Public Sub GetPartsOfInvocationExpression(node As SyntaxNode, ByRef expression As SyntaxNode, ByRef argumentList As SyntaxNode) Implements ISyntaxFacts.GetPartsOfInvocationExpression Dim invocation = DirectCast(node, InvocationExpressionSyntax) expression = invocation.Expression From 293e2b51a19cddae8a1cf3bcfe262f2d5f518e48 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Thu, 14 Apr 2022 15:08:19 -0700 Subject: [PATCH 11/58] lots of changes --- .../CSharpIfSnippetCompletionProviderTests.cs | 33 ++++++++--- .../AsyncCompletion/CommitManager.cs | 4 +- .../AbstractSnippetCompletionProvider.cs | 12 ++-- .../Core/Portable/Snippets/SnippetChange.cs | 14 +---- .../AbstractConsoleSnippetProvider.cs | 57 ++++++++++++++---- .../AbstractIfSnippetProvider.cs | 46 ++++++++------- .../AbstractSnippetProvider.cs | 59 ++++++++++++++++--- .../Rules/ElasticTriviaFormattingRule.cs | 9 +++ 8 files changed, 163 insertions(+), 71 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs index 573eac6d08afd..c5fe54fd9e17f 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs @@ -38,7 +38,9 @@ public void Method() { public void Method() { - if ($$true) { } + if (true) + {$$ + } } }"; await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); @@ -52,7 +54,9 @@ public async Task InsertIfSnippetInGlobalContextTest() "; var expectedCodeAfterCommit = -@"if ($$true) { } +@"if (true) +{$$ +} "; await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); } @@ -111,7 +115,9 @@ public Program() public Program() { var x = 5; - if ($$true) { } + if (true) + {$$ + } } }"; await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); @@ -141,7 +147,9 @@ public void Method() var x = 5; void LocalMethod() { - if ($$true) { } + if (true) + {$$ + } } } }"; @@ -168,7 +176,9 @@ static void Main(string[] args) static void Main(string[] args) { Print print = delegate(int val) { - if ($$true) { } + if (true) + {$$ + } }; }"; @@ -188,7 +198,10 @@ public async Task InsertIfSnippetInParenthesizedLambdaExpressionTest() var expectedCodeAfterCommit = @"Func testForEquality = (x, y) => { - if ($$true) { } + if (true) + {$$ + } + return x == y; };"; await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); @@ -331,7 +344,9 @@ public void Method() { public void Method() { - if ($$true) { } + if (true) + {$$ + } } }"; await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); @@ -354,7 +369,9 @@ public void Method() { public void Method() { - if ($$true) { } + if (true) + {$$ + } } }"; await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs index b1b8eef1fa9b5..e944d13881ee6 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs @@ -256,6 +256,8 @@ private AsyncCompletionData.CommitResult Commit( } } + // For all other completions and if the snippet LSP service fails + // we still want to try and insert the snippet sans the placeholders + tab stop behavior. if (!snippetServiceSucceeded) { @@ -323,7 +325,7 @@ private AsyncCompletionData.CommitResult Commit( { _ = ThreadingContext.JoinableTaskFactory.RunAsync(async () => { - // Make sure the notification isn't sent on UI thread. + // Make sure the notification isn't sent on UI thread. await TaskScheduler.Default; _ = notifyProvider.NotifyCommittingItemAsync(document, roslynItem, commitCharacter, cancellationToken).ReportNonFatalErrorAsync(); }); diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs index 717610064af5a..2f0afac3657ef 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs @@ -37,17 +37,13 @@ public override async Task GetChangeAsync(Document document, C var allTextChanges = await allChangesDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); var change = Utilities.Collapse(allChangesText, allTextChanges.AsImmutable()); - var lspSnippet = GenerateLSPSnippet(snippet.MainTextChange, snippet.Placeholders); + var lspSnippet = GenerateLSPSnippet(change, snippet.Placeholders); return CompletionChange.CreateSpecialLSPSnippetChange(change, allTextChanges.AsImmutable(), newPosition: snippet.CursorPosition, includesCommitCharacter: true, lspSnippet); } - private static string? GenerateLSPSnippet(TextChange? textChange, List<(string, List)>? placeholders) + private static string? GenerateLSPSnippet(TextChange textChange, List<(string, List)> placeholders) { - var textChangeText = textChange!.Value.NewText!; - if (placeholders is null) - { - return textChangeText; - } + var textChangeText = textChange.NewText!; for (var i = 0; i < placeholders.Count; i++) { @@ -60,7 +56,7 @@ public override async Task GetChangeAsync(Document document, C else { var location = placeholderList[0]; - textChangeText = textChangeText.Insert(location.Start, $"$0"); + textChangeText = textChangeText.Insert(location.Start - textChange.Span.Start, $"$0"); } } diff --git a/src/Features/Core/Portable/Snippets/SnippetChange.cs b/src/Features/Core/Portable/Snippets/SnippetChange.cs index 965de7f8b8e74..f08e0182a9f15 100644 --- a/src/Features/Core/Portable/Snippets/SnippetChange.cs +++ b/src/Features/Core/Portable/Snippets/SnippetChange.cs @@ -14,12 +14,6 @@ namespace Microsoft.CodeAnalysis.Snippets /// internal readonly struct SnippetChange { - /// - /// The primary text change. - /// This will be the change that has associated renaming and tab stops. - /// - public readonly TextChange? MainTextChange; - /// /// The TextChange's associated with introducing a snippet into a document /// @@ -34,20 +28,18 @@ internal readonly struct SnippetChange /// The items that we will want to rename as well as the ordering /// in which to visit those items. /// - public readonly List<(string, List)>? Placeholders; + public readonly List<(string, List)> Placeholders; public SnippetChange( - TextChange? mainTextChange, ImmutableArray textChanges, int? cursorPosition, - List<(string, List)>? placeholders) + List<(string, List)> placeholders) { - if (textChanges.IsEmpty || mainTextChange is null) + if (textChanges.IsEmpty) { throw new ArgumentException($"{ textChanges.Length } must not be empty"); } - MainTextChange = mainTextChange; TextChanges = textChanges; CursorPosition = cursorPosition; Placeholders = placeholders; diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs index 440dd4defdb22..dcd515b21f9d2 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.Linq; @@ -75,20 +76,13 @@ private async Task GenerateSnippetTextChangeAsync(Document document, protected override int? GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget) { - var invocationExpression = caretTarget.DescendantNodes().Where(syntaxFacts.IsInvocationExpression).FirstOrDefault(); - if (invocationExpression is null) + var openParenToken = GetOpenParenToken(caretTarget, syntaxFacts); + if (openParenToken is null) { return null; } - var argumentListNode = syntaxFacts.GetArgumentListOfInvocationExpression(invocationExpression); - if (argumentListNode is null) - { - return null; - } - - syntaxFacts.GetPartsOfArgumentList(argumentListNode, out var openParenToken, out _, out _); - return openParenToken.Span.End; + return openParenToken.Value.Span.End; } protected override async Task AnnotateNodesToReformatAsync(Document document, @@ -96,7 +90,7 @@ protected override async Task AnnotateNodesToReformatAsync(Document { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var syntaxFacts = document.GetRequiredLanguageService(); - var snippetExpressionNode = GetConsoleExpressionStatement(syntaxFacts, root, position); + var snippetExpressionNode = FindAddedSnippetSyntaxNode(root, position, syntaxFacts); if (snippetExpressionNode is null) { return root; @@ -108,7 +102,46 @@ protected override async Task AnnotateNodesToReformatAsync(Document return root.ReplaceNode(snippetExpressionNode, reformatSnippetNode); } - private static SyntaxNode? GetConsoleExpressionStatement(ISyntaxFactsService syntaxFacts, SyntaxNode root, int position) + protected override List<(string, List)> GetRenameLocationsMap(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + { + var renameLocationsMap = new List<(string, List)>(); + var openParenToken = GetOpenParenToken(node, syntaxFacts); + + if (openParenToken is null) + { + return renameLocationsMap; + } + + var list1 = new List + { + new TextSpan(openParenToken.Value.Span.End, openParenToken.Value.Span.Length) + }; + + renameLocationsMap.Add(("", list1)); + + return renameLocationsMap; + } + + private static SyntaxToken? GetOpenParenToken(SyntaxNode node, ISyntaxFacts syntaxFacts) + { + var invocationExpression = node.DescendantNodes().Where(syntaxFacts.IsInvocationExpression).FirstOrDefault(); + if (invocationExpression is null) + { + return null; + } + + var argumentListNode = syntaxFacts.GetArgumentListOfInvocationExpression(invocationExpression); + if (argumentListNode is null) + { + return null; + } + + syntaxFacts.GetPartsOfArgumentList(argumentListNode, out var openParenToken, out _, out _); + + return openParenToken; + } + + protected override SyntaxNode? FindAddedSnippetSyntaxNode(SyntaxNode root, int position, ISyntaxFacts syntaxFacts) { var closestNode = root.FindNode(TextSpan.FromBounds(position, position)); var nearestExpressionStatement = closestNode.FirstAncestorOrSelf(syntaxFacts.IsExpressionStatement); diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs index 7dd1e63cc4220..c823b19dbd6b3 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs @@ -44,13 +44,14 @@ private static TextChange GenerateSnippetTextChange(Document document, int posit { var generator = SyntaxGenerator.GetGenerator(document); var ifStatement = generator.IfStatement(generator.TrueLiteralExpression(), Array.Empty()); + return new TextChange(TextSpan.FromBounds(position, position), ifStatement.ToFullString()); } protected override int? GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget) { - GetPartsOfIfStatement(caretTarget, out var openParen, out _, out _); - return openParen.Span.End; + GetPartsOfIfStatement(caretTarget, out _, out _, out var statement); + return statement.SpanStart + 1; } protected override async Task AnnotateNodesToReformatAsync(Document document, @@ -58,7 +59,7 @@ protected override async Task AnnotateNodesToReformatAsync(Document { var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var syntaxFacts = document.GetRequiredLanguageService(); - var snippetExpressionNode = GetIfExpressionStatement(syntaxFacts, root, position); + var snippetExpressionNode = FindAddedSnippetSyntaxNode(root, position, syntaxFacts); if (snippetExpressionNode is null) { return root; @@ -68,42 +69,43 @@ protected override async Task AnnotateNodesToReformatAsync(Document return root.ReplaceNode(snippetExpressionNode, reformatSnippetNode); } - protected override async Task)>?> GetRenameLocationsMapAsync(Document document, int position, CancellationToken cancellationToken) + protected override List<(string, List)> GetRenameLocationsMap(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var syntaxFacts = document.GetRequiredLanguageService(); - var snippetExpressionNode = GetIfExpressionStatement(syntaxFacts, root, position); - if (snippetExpressionNode is null) - { - return null; - } - var renameLocationsMap = new List<(string, List)>(); - GetPartsOfIfStatement(snippetExpressionNode, out _, out var condition, out var statement); + GetPartsOfIfStatement(node, out _, out var condition, out var statement); - var list2 = new List + var list1 = new List { - new TextSpan(statement.SpanStart - snippetExpressionNode.SpanStart + 1, statement.Span.Length) + // Need to add 1 to the span start because we are retrieving a block and want the cursor to appear inside the block. + new TextSpan(statement.SpanStart + 1, statement.Span.Length) }; - renameLocationsMap.Add(("", list2)); + renameLocationsMap.Add(("", list1)); - var list1 = new List + var list2 = new List { - new TextSpan(condition.SpanStart - snippetExpressionNode.SpanStart, condition.Span.Length) + new TextSpan(condition.SpanStart, condition.Span.Length) }; - renameLocationsMap.Add((condition.ToFullString(), list1)); + renameLocationsMap.Add((condition.ToString(), list2)); return renameLocationsMap; } - private static SyntaxNode? GetIfExpressionStatement(ISyntaxFactsService syntaxFacts, SyntaxNode root, int position) + protected override SyntaxNode? FindAddedSnippetSyntaxNode(SyntaxNode root, int position, ISyntaxFacts syntaxFacts) { var closestNode = root.FindNode(TextSpan.FromBounds(position, position)); - var nearestStatement = syntaxFacts.IsGlobalStatement(closestNode) - ? syntaxFacts.GetStatementOfGlobalStatement(closestNode) + SyntaxNode? statementOfGlobalStatement = null; + if (syntaxFacts.IsGlobalStatement(closestNode)) + { + statementOfGlobalStatement = syntaxFacts.GetStatementOfGlobalStatement(closestNode); + } + + var nearestStatement = statementOfGlobalStatement is not null + ? syntaxFacts.IsIfStatement(statementOfGlobalStatement) + ? statementOfGlobalStatement + : null : closestNode.DescendantNodesAndSelf(syntaxFacts.IsIfStatement).FirstOrDefault(); if (nearestStatement is null) diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs index 5448abc793607..2f62ede1a7fd8 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -13,7 +13,9 @@ using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.ExtractMethod; using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.NavigateTo; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Simplification; using Microsoft.CodeAnalysis.Snippets.SnippetProviders; @@ -46,6 +48,8 @@ internal abstract class AbstractSnippetProvider : ISnippetProvider protected abstract Task AnnotateNodesToReformatAsync(Document document, SyntaxAnnotation reformatAnnotation, SyntaxAnnotation cursorAnnotation, int position, CancellationToken cancellationToken); protected abstract int? GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget); + protected abstract SyntaxNode? FindAddedSnippetSyntaxNode(SyntaxNode root, int position, ISyntaxFacts syntaxFacts); + /// /// Determines if the location is valid for a snippet, /// if so, then it creates a SnippetData. @@ -77,22 +81,38 @@ public async Task GetSnippetAsync(Document document, int position var syntaxFacts = document.GetRequiredLanguageService(); var textChanges = await GenerateSnippetTextChangesAsync(document, position, cancellationToken).ConfigureAwait(false); var snippetDocument = await GetDocumentWithSnippetAsync(document, textChanges, cancellationToken).ConfigureAwait(false); - var mainChanges = await snippetDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); - var mainChange = mainChanges.Where(change => change.Span.Start == position).FirstOrDefault(); - var placeholders = await GetRenameLocationsMapAsync(snippetDocument, position, cancellationToken).ConfigureAwait(false); - var formatAnnotatedSnippetDocument = await AddFormatAnnotationAsync(snippetDocument, position, cancellationToken).ConfigureAwait(false); + var snippetWithTriviaDocument = await GetDocumentWithSnippetAndTriviaAsync(snippetDocument, position, syntaxFacts, cancellationToken).ConfigureAwait(false); + var formatAnnotatedSnippetDocument = await AddFormatAnnotationAsync(snippetWithTriviaDocument, position, cancellationToken).ConfigureAwait(false); var reformattedDocument = await CleanupDocumentAsync(formatAnnotatedSnippetDocument, cancellationToken).ConfigureAwait(false); var reformattedRoot = await reformattedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var caretTarget = reformattedRoot.GetAnnotatedNodes(_cursorAnnotation).SingleOrDefault(); + var mainChangeNode = reformattedRoot.GetAnnotatedNodes(_findSnippetAnnotation).SingleOrDefault(); var changes = await reformattedDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); - + var placeholders = GetRenameLocationsMap(mainChangeNode, syntaxFacts, cancellationToken); + var changesArray = changes.ToImmutableArray(); return new SnippetChange( - mainTextChange: mainChange, - textChanges: changes.ToImmutableArray(), + textChanges: changesArray, cursorPosition: GetTargetCaretPosition(syntaxFacts, caretTarget), placeholders: placeholders); } + protected static SyntaxNode? GenerateElasticTriviaForSyntax(ISyntaxFacts syntaxFacts, SyntaxNode? node) + { + if (node is null) + { + return null; + } + + var nodeWithTrivia = node.ReplaceTokens(node.DescendantTokens(descendIntoTrivia: true), AddAnnotations); + + return nodeWithTrivia; + + SyntaxToken AddAnnotations(SyntaxToken oldToken, SyntaxToken newToken) + { + return oldToken.WithAdditionalAnnotations(SyntaxAnnotation.ElasticAnnotation).WithAppendedTrailingTrivia(syntaxFacts.ElasticMarker).WithPrependedLeadingTrivia(syntaxFacts.ElasticMarker); + } + } + private async Task CleanupDocumentAsync( Document document, CancellationToken cancellationToken) { @@ -115,6 +135,27 @@ private async Task CleanupDocumentAsync( return document; } + private async Task GetDocumentWithSnippetAndTriviaAsync(Document snippetDocument, int position, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + { + var root = await snippetDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var nearestStatement = FindAddedSnippetSyntaxNode(root, position, syntaxFacts); + + if (nearestStatement is null) + { + return snippetDocument; + } + + var nearestStatementWithTrivia = GenerateElasticTriviaForSyntax(syntaxFacts, nearestStatement); + + if (nearestStatementWithTrivia is null) + { + return snippetDocument; + } + + root = root.ReplaceNode(nearestStatement, nearestStatementWithTrivia); + return snippetDocument.WithSyntaxRoot(root); + } + private static async Task GetDocumentWithSnippetAsync(Document document, ImmutableArray snippets, CancellationToken cancellationToken) { var originalText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); @@ -132,9 +173,9 @@ private async Task AddFormatAnnotationAsync(Document document, int pos return document; } - protected virtual Task)>?> GetRenameLocationsMapAsync(Document document, int position, CancellationToken cancellationToken) + protected virtual List<(string, List)> GetRenameLocationsMap(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { - return Task.FromResult)>?>(null); + return new List<(string, List)>(); } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs index 55fd4486126bf..fbcf91e55b856 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs @@ -125,6 +125,15 @@ private static void AddInitializerSuppressOperations(List lis return null; } + if (CommonFormattingHelpers.HasAnyWhitespaceElasticTrivia(previousToken, currentToken) && + currentToken.IsKind(SyntaxKind.OpenBraceToken) && + currentToken.Parent.Parent.IsKind(SyntaxKind.IfStatement)) + { + var num = LineBreaksAfter(previousToken, currentToken); + + return CreateAdjustNewLinesOperation(num, AdjustNewLinesOption.ForceLinesIfOnSingleLine); + } + // if operation is already forced, return as it is. if (operation.Option == AdjustNewLinesOption.ForceLines) return operation; From b8593ebf9d5aa68f033afa1335ba5da360709058 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Tue, 19 Apr 2022 14:07:34 -0700 Subject: [PATCH 12/58] wip --- Roslyn.sln | 172 ++++++++++-------- .../Snippets/AbstractCSharpLSPSnippetTests.cs | 67 +++++++ .../Snippets/CSharpLSPSnippetTests.cs | 59 ++++++ .../Snippets/AbstractSnippetTests.cs | 73 ++++++++ .../CSharpConvertToLSPSnippetService.cs | 23 +++ .../AbstractSnippetCompletionProvider.cs | 26 +-- .../Snippets/ConvertToLSPSnippetService.cs | 37 ++++ .../AbstractSnippetProvider.cs | 4 +- 8 files changed, 358 insertions(+), 103 deletions(-) create mode 100644 src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs create mode 100644 src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs create mode 100644 src/EditorFeatures/TestUtilities/Snippets/AbstractSnippetTests.cs create mode 100644 src/Features/CSharp/Portable/Snippets/CSharpConvertToLSPSnippetService.cs create mode 100644 src/Features/Core/Portable/Snippets/ConvertToLSPSnippetService.cs diff --git a/Roslyn.sln b/Roslyn.sln index 475b330043b1c..eddbb49a4663e 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -496,84 +496,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.Lang EndProjectSection EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - src\Analyzers\VisualBasic\CodeFixes\VisualBasicCodeFixes.projitems*{0141285d-8f6c-42c7-baf3-3c0ccd61c716}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\VisualBasic\VisualBasicWorkspaceExtensions.projitems*{0141285d-8f6c-42c7-baf3-3c0ccd61c716}*SharedItemsImports = 5 - src\Analyzers\VisualBasic\Tests\VisualBasicAnalyzers.UnitTests.projitems*{0be66736-cdaa-4989-88b1-b3f46ebdca4a}*SharedItemsImports = 5 - src\Analyzers\Core\CodeFixes\CodeFixes.projitems*{1b6c4a1a-413b-41fb-9f85-5c09118e541b}*SharedItemsImports = 13 - src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 - src\Dependencies\CodeAnalysis.Debugging\Microsoft.CodeAnalysis.Debugging.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 - src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 - src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\CSharpCompilerExtensions.projitems*{21b239d0-d144-430f-a394-c066d58ee267}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\CSharp\CSharpWorkspaceExtensions.projitems*{21b239d0-d144-430f-a394-c066d58ee267}*SharedItemsImports = 5 - src\Compilers\VisualBasic\BasicAnalyzerDriver\BasicAnalyzerDriver.projitems*{2523d0e6-df32-4a3e-8ae0-a19bffae2ef6}*SharedItemsImports = 5 - src\Analyzers\VisualBasic\Analyzers\VisualBasicAnalyzers.projitems*{2531a8c4-97dd-47bc-a79c-b7846051e137}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\VisualBasic\VisualBasicCompilerExtensions.projitems*{2531a8c4-97dd-47bc-a79c-b7846051e137}*SharedItemsImports = 5 - src\Analyzers\Core\Analyzers\Analyzers.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 - src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 - src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\CompilerExtensions.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 - src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{3140fe61-0856-4367-9aa3-8081b9a80e35}*SharedItemsImports = 13 - src\ExpressionEvaluator\CSharp\Source\ResultProvider\CSharpResultProvider.projitems*{3140fe61-0856-4367-9aa3-8081b9a80e36}*SharedItemsImports = 13 - src\Analyzers\CSharp\Analyzers\CSharpAnalyzers.projitems*{3973b09a-4fbf-44a5-8359-3d22ceb71f71}*SharedItemsImports = 5 - src\Analyzers\CSharp\CodeFixes\CSharpCodeFixes.projitems*{3973b09a-4fbf-44a5-8359-3d22ceb71f71}*SharedItemsImports = 5 - src\Compilers\CSharp\CSharpAnalyzerDriver\CSharpAnalyzerDriver.projitems*{3973b09a-4fbf-44a5-8359-3d22ceb71f71}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\CSharp\CSharpWorkspaceExtensions.projitems*{438db8af-f3f0-4ed9-80b5-13fddd5b8787}*SharedItemsImports = 13 - src\Compilers\Core\CommandLine\CommandLine.projitems*{4b45ca0c-03a0-400f-b454-3d4bcb16af38}*SharedItemsImports = 5 - src\Analyzers\CSharp\Tests\CSharpAnalyzers.UnitTests.projitems*{5018d049-5870-465a-889b-c742ce1e31cb}*SharedItemsImports = 5 - src\Compilers\CSharp\CSharpAnalyzerDriver\CSharpAnalyzerDriver.projitems*{54e08bf5-f819-404f-a18d-0ab9ea81ea04}*SharedItemsImports = 13 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\VisualBasic\VisualBasicCompilerExtensions.projitems*{57ca988d-f010-4bf2-9a2e-07d6dcd2ff2c}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\VisualBasic\VisualBasicWorkspaceExtensions.projitems*{57ca988d-f010-4bf2-9a2e-07d6dcd2ff2c}*SharedItemsImports = 5 - src\Analyzers\CSharp\Tests\CSharpAnalyzers.UnitTests.projitems*{58969243-7f59-4236-93d0-c93b81f569b3}*SharedItemsImports = 13 - src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 - src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\CompilerExtensions.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\WorkspaceExtensions.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 - src\Analyzers\Core\CodeFixes\CodeFixes.projitems*{5ff1e493-69cc-4d0b-83f2-039f469a04e1}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\WorkspaceExtensions.projitems*{5ff1e493-69cc-4d0b-83f2-039f469a04e1}*SharedItemsImports = 5 - src\ExpressionEvaluator\CSharp\Source\ResultProvider\CSharpResultProvider.projitems*{60db272a-21c9-4e8d-9803-ff4e132392c8}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\CSharpCompilerExtensions.projitems*{699fea05-aea7-403d-827e-53cf4e826955}*SharedItemsImports = 13 - src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{76242a2d-2600-49dd-8c15-fea07ecb1843}*SharedItemsImports = 5 - src\Analyzers\Core\Analyzers\Analyzers.projitems*{76e96966-4780-4040-8197-bde2879516f4}*SharedItemsImports = 13 - src\Compilers\Core\CommandLine\CommandLine.projitems*{7ad4fe65-9a30-41a6-8004-aa8f89bcb7f3}*SharedItemsImports = 5 - src\Analyzers\VisualBasic\Tests\VisualBasicAnalyzers.UnitTests.projitems*{7b7f4153-ae93-4908-b8f0-430871589f83}*SharedItemsImports = 13 - src\Analyzers\VisualBasic\Analyzers\VisualBasicAnalyzers.projitems*{94faf461-2e74-4dbb-9813-6b2cde6f1880}*SharedItemsImports = 13 - src\Compilers\Core\CommandLine\CommandLine.projitems*{9508f118-f62e-4c16-a6f4-7c3b56e166ad}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\WorkspaceExtensions.projitems*{99f594b1-3916-471d-a761-a6731fc50e9a}*SharedItemsImports = 13 - src\Analyzers\VisualBasic\CodeFixes\VisualBasicCodeFixes.projitems*{9f9ccc78-7487-4127-9d46-db23e501f001}*SharedItemsImports = 13 - src\Analyzers\CSharp\CodeFixes\CSharpCodeFixes.projitems*{a07abcf5-bc43-4ee9-8fd8-b2d77fd54d73}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\CSharp\CSharpWorkspaceExtensions.projitems*{a07abcf5-bc43-4ee9-8fd8-b2d77fd54d73}*SharedItemsImports = 5 - src\Analyzers\VisualBasic\Analyzers\VisualBasicAnalyzers.projitems*{a1bcd0ce-6c2f-4f8c-9a48-d9d93928e26d}*SharedItemsImports = 5 - src\Analyzers\VisualBasic\CodeFixes\VisualBasicCodeFixes.projitems*{a1bcd0ce-6c2f-4f8c-9a48-d9d93928e26d}*SharedItemsImports = 5 - src\Compilers\VisualBasic\BasicAnalyzerDriver\BasicAnalyzerDriver.projitems*{a1bcd0ce-6c2f-4f8c-9a48-d9d93928e26d}*SharedItemsImports = 5 - src\Analyzers\CSharp\Analyzers\CSharpAnalyzers.projitems*{aa87bfed-089a-4096-b8d5-690bdc7d5b24}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\CSharpCompilerExtensions.projitems*{aa87bfed-089a-4096-b8d5-690bdc7d5b24}*SharedItemsImports = 5 - src\ExpressionEvaluator\Core\Source\ResultProvider\ResultProvider.projitems*{abdbac1e-350e-4dc3-bb45-3504404545ee}*SharedItemsImports = 5 - src\Analyzers\CSharp\Tests\CSharpAnalyzers.UnitTests.projitems*{ac2bcefb-9298-4621-ac48-1ff5e639e48d}*SharedItemsImports = 5 - src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{ace53515-482c-4c6a-e2d2-4242a687dfee}*SharedItemsImports = 5 - src\Compilers\Core\CommandLine\CommandLine.projitems*{ad6f474e-e6d4-4217-91f3-b7af1be31ccc}*SharedItemsImports = 13 - src\Compilers\CSharp\CSharpAnalyzerDriver\CSharpAnalyzerDriver.projitems*{b501a547-c911-4a05-ac6e-274a50dff30e}*SharedItemsImports = 5 - src\ExpressionEvaluator\Core\Source\ResultProvider\ResultProvider.projitems*{bb3ca047-5d00-48d4-b7d3-233c1265c065}*SharedItemsImports = 13 - src\ExpressionEvaluator\CSharp\Source\ResultProvider\CSharpResultProvider.projitems*{bf9dac1e-3a5e-4dc3-bb44-9a64e0d4e9d4}*SharedItemsImports = 5 - src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{c1930979-c824-496b-a630-70f5369a636f}*SharedItemsImports = 13 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\VisualBasic\VisualBasicCompilerExtensions.projitems*{cec0dce7-8d52-45c3-9295-fc7b16bd2451}*SharedItemsImports = 13 - src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{d0bc9be7-24f6-40ca-8dc6-fcb93bd44b34}*SharedItemsImports = 13 - src\Dependencies\CodeAnalysis.Debugging\Microsoft.CodeAnalysis.Debugging.projitems*{d73adf7d-2c1c-42ae-b2ab-edc9497e4b71}*SharedItemsImports = 13 - src\Analyzers\CSharp\CodeFixes\CSharpCodeFixes.projitems*{da973826-c985-4128-9948-0b445e638bdb}*SharedItemsImports = 13 - src\Analyzers\VisualBasic\Tests\VisualBasicAnalyzers.UnitTests.projitems*{e512c6c1-f085-4ad7-b0d9-e8f1a0a2a510}*SharedItemsImports = 5 - src\Compilers\Core\CommandLine\CommandLine.projitems*{e58ee9d7-1239-4961-a0c1-f9ec3952c4c1}*SharedItemsImports = 5 - src\Compilers\VisualBasic\BasicAnalyzerDriver\BasicAnalyzerDriver.projitems*{e8f0baa5-7327-43d1-9a51-644e81ae55f1}*SharedItemsImports = 13 - src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{e919dd77-34f8-4f57-8058-4d3ff4c2b241}*SharedItemsImports = 13 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\VisualBasic\VisualBasicWorkspaceExtensions.projitems*{e9dbfa41-7a9c-49be-bd36-fd71b31aa9fe}*SharedItemsImports = 13 - src\Analyzers\CSharp\Analyzers\CSharpAnalyzers.projitems*{eaffca55-335b-4860-bb99-efcead123199}*SharedItemsImports = 13 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\CompilerExtensions.projitems*{ec946164-1e17-410b-b7d9-7de7e6268d63}*SharedItemsImports = 13 - src\Analyzers\Core\Analyzers\Analyzers.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 5 - src\Analyzers\Core\CodeFixes\CodeFixes.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 5 - src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 5 - src\Dependencies\CodeAnalysis.Debugging\Microsoft.CodeAnalysis.Debugging.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 5 - src\ExpressionEvaluator\Core\Source\ResultProvider\ResultProvider.projitems*{fa0e905d-ec46-466d-b7b2-3b5557f9428c}*SharedItemsImports = 5 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU @@ -935,6 +857,12 @@ Global {ABC7262E-1053-49F3-B846-E3091BB92E8C}.Debug|Any CPU.Build.0 = Debug|Any CPU {ABC7262E-1053-49F3-B846-E3091BB92E8C}.Release|Any CPU.ActiveCfg = Release|Any CPU {ABC7262E-1053-49F3-B846-E3091BB92E8C}.Release|Any CPU.Build.0 = Release|Any CPU + {E8F0BAA5-7327-43D1-9A51-644E81AE55F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E8F0BAA5-7327-43D1-9A51-644E81AE55F1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {54E08BF5-F819-404F-A18D-0AB9EA81EA04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {54E08BF5-F819-404F-A18D-0AB9EA81EA04}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD6F474E-E6D4-4217-91F3-B7AF1BE31CCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD6F474E-E6D4-4217-91F3-B7AF1BE31CCC}.Release|Any CPU.ActiveCfg = Release|Any CPU {43026D51-3083-4850-928D-07E1883D5B1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {43026D51-3083-4850-928D-07E1883D5B1A}.Debug|Any CPU.Build.0 = Debug|Any CPU {43026D51-3083-4850-928D-07E1883D5B1A}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -963,6 +891,10 @@ Global {59AD474E-2A35-4E8A-A74D-E33479977FBF}.Debug|Any CPU.Build.0 = Debug|Any CPU {59AD474E-2A35-4E8A-A74D-E33479977FBF}.Release|Any CPU.ActiveCfg = Release|Any CPU {59AD474E-2A35-4E8A-A74D-E33479977FBF}.Release|Any CPU.Build.0 = Release|Any CPU + {D73ADF7D-2C1C-42AE-B2AB-EDC9497E4B71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D73ADF7D-2C1C-42AE-B2AB-EDC9497E4B71}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1930979-C824-496B-A630-70F5369A636F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1930979-C824-496B-A630-70F5369A636F}.Release|Any CPU.ActiveCfg = Release|Any CPU {F822F72A-CC87-4E31-B57D-853F65CBEBF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F822F72A-CC87-4E31-B57D-853F65CBEBF3}.Debug|Any CPU.Build.0 = Debug|Any CPU {F822F72A-CC87-4E31-B57D-853F65CBEBF3}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1191,6 +1123,12 @@ Global {D15BF03E-04ED-4BEE-A72B-7620F541F4E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {D15BF03E-04ED-4BEE-A72B-7620F541F4E2}.Release|Any CPU.ActiveCfg = Release|Any CPU {D15BF03E-04ED-4BEE-A72B-7620F541F4E2}.Release|Any CPU.Build.0 = Release|Any CPU + {438DB8AF-F3F0-4ED9-80B5-13FDDD5B8787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {438DB8AF-F3F0-4ED9-80B5-13FDDD5B8787}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58969243-7F59-4236-93D0-C93B81F569B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58969243-7F59-4236-93D0-C93B81F569B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B6C4A1A-413B-41FB-9F85-5C09118E541B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B6C4A1A-413B-41FB-9F85-5C09118E541B}.Release|Any CPU.ActiveCfg = Release|Any CPU {B64766CD-1A1F-4C1B-B11F-C30F82B8E41E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B64766CD-1A1F-4C1B-B11F-C30F82B8E41E}.Debug|Any CPU.Build.0 = Debug|Any CPU {B64766CD-1A1F-4C1B-B11F-C30F82B8E41E}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1507,4 +1445,82 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {604E6B91-7BC0-4126-AE07-D4D2FEFC3D29} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + src\Analyzers\VisualBasic\CodeFixes\VisualBasicCodeFixes.projitems*{0141285d-8f6c-42c7-baf3-3c0ccd61c716}*SharedItemsImports = 5 + src\Workspaces\SharedUtilitiesAndExtensions\Workspace\VisualBasic\VisualBasicWorkspaceExtensions.projitems*{0141285d-8f6c-42c7-baf3-3c0ccd61c716}*SharedItemsImports = 5 + src\Analyzers\VisualBasic\Tests\VisualBasicAnalyzers.UnitTests.projitems*{0be66736-cdaa-4989-88b1-b3f46ebdca4a}*SharedItemsImports = 5 + src\Analyzers\Core\CodeFixes\CodeFixes.projitems*{1b6c4a1a-413b-41fb-9f85-5c09118e541b}*SharedItemsImports = 13 + src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 + src\Dependencies\CodeAnalysis.Debugging\Microsoft.CodeAnalysis.Debugging.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 + src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 + src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 + src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\CSharpCompilerExtensions.projitems*{21b239d0-d144-430f-a394-c066d58ee267}*SharedItemsImports = 5 + src\Workspaces\SharedUtilitiesAndExtensions\Workspace\CSharp\CSharpWorkspaceExtensions.projitems*{21b239d0-d144-430f-a394-c066d58ee267}*SharedItemsImports = 5 + src\Compilers\VisualBasic\BasicAnalyzerDriver\BasicAnalyzerDriver.projitems*{2523d0e6-df32-4a3e-8ae0-a19bffae2ef6}*SharedItemsImports = 5 + src\Analyzers\VisualBasic\Analyzers\VisualBasicAnalyzers.projitems*{2531a8c4-97dd-47bc-a79c-b7846051e137}*SharedItemsImports = 5 + src\Workspaces\SharedUtilitiesAndExtensions\Compiler\VisualBasic\VisualBasicCompilerExtensions.projitems*{2531a8c4-97dd-47bc-a79c-b7846051e137}*SharedItemsImports = 5 + src\Analyzers\Core\Analyzers\Analyzers.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 + src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 + src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 + src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\CompilerExtensions.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 + src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{3140fe61-0856-4367-9aa3-8081b9a80e35}*SharedItemsImports = 13 + src\ExpressionEvaluator\CSharp\Source\ResultProvider\CSharpResultProvider.projitems*{3140fe61-0856-4367-9aa3-8081b9a80e36}*SharedItemsImports = 13 + src\Analyzers\CSharp\Analyzers\CSharpAnalyzers.projitems*{3973b09a-4fbf-44a5-8359-3d22ceb71f71}*SharedItemsImports = 5 + src\Analyzers\CSharp\CodeFixes\CSharpCodeFixes.projitems*{3973b09a-4fbf-44a5-8359-3d22ceb71f71}*SharedItemsImports = 5 + src\Compilers\CSharp\CSharpAnalyzerDriver\CSharpAnalyzerDriver.projitems*{3973b09a-4fbf-44a5-8359-3d22ceb71f71}*SharedItemsImports = 5 + src\Workspaces\SharedUtilitiesAndExtensions\Workspace\CSharp\CSharpWorkspaceExtensions.projitems*{438db8af-f3f0-4ed9-80b5-13fddd5b8787}*SharedItemsImports = 13 + src\Compilers\Core\CommandLine\CommandLine.projitems*{4b45ca0c-03a0-400f-b454-3d4bcb16af38}*SharedItemsImports = 5 + src\Analyzers\CSharp\Tests\CSharpAnalyzers.UnitTests.projitems*{5018d049-5870-465a-889b-c742ce1e31cb}*SharedItemsImports = 5 + src\Compilers\CSharp\CSharpAnalyzerDriver\CSharpAnalyzerDriver.projitems*{54e08bf5-f819-404f-a18d-0ab9ea81ea04}*SharedItemsImports = 13 + src\Workspaces\SharedUtilitiesAndExtensions\Compiler\VisualBasic\VisualBasicCompilerExtensions.projitems*{57ca988d-f010-4bf2-9a2e-07d6dcd2ff2c}*SharedItemsImports = 5 + src\Workspaces\SharedUtilitiesAndExtensions\Workspace\VisualBasic\VisualBasicWorkspaceExtensions.projitems*{57ca988d-f010-4bf2-9a2e-07d6dcd2ff2c}*SharedItemsImports = 5 + src\Analyzers\CSharp\Tests\CSharpAnalyzers.UnitTests.projitems*{58969243-7f59-4236-93d0-c93b81f569b3}*SharedItemsImports = 13 + src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 + src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 + src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\CompilerExtensions.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 + src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\WorkspaceExtensions.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 + src\Analyzers\Core\CodeFixes\CodeFixes.projitems*{5ff1e493-69cc-4d0b-83f2-039f469a04e1}*SharedItemsImports = 5 + src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\WorkspaceExtensions.projitems*{5ff1e493-69cc-4d0b-83f2-039f469a04e1}*SharedItemsImports = 5 + src\ExpressionEvaluator\CSharp\Source\ResultProvider\CSharpResultProvider.projitems*{60db272a-21c9-4e8d-9803-ff4e132392c8}*SharedItemsImports = 5 + src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\CSharpCompilerExtensions.projitems*{699fea05-aea7-403d-827e-53cf4e826955}*SharedItemsImports = 13 + src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{76242a2d-2600-49dd-8c15-fea07ecb1843}*SharedItemsImports = 5 + src\Analyzers\Core\Analyzers\Analyzers.projitems*{76e96966-4780-4040-8197-bde2879516f4}*SharedItemsImports = 13 + src\Compilers\Core\CommandLine\CommandLine.projitems*{7ad4fe65-9a30-41a6-8004-aa8f89bcb7f3}*SharedItemsImports = 5 + src\Analyzers\VisualBasic\Tests\VisualBasicAnalyzers.UnitTests.projitems*{7b7f4153-ae93-4908-b8f0-430871589f83}*SharedItemsImports = 13 + src\Analyzers\VisualBasic\Analyzers\VisualBasicAnalyzers.projitems*{94faf461-2e74-4dbb-9813-6b2cde6f1880}*SharedItemsImports = 13 + src\Compilers\Core\CommandLine\CommandLine.projitems*{9508f118-f62e-4c16-a6f4-7c3b56e166ad}*SharedItemsImports = 5 + src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\WorkspaceExtensions.projitems*{99f594b1-3916-471d-a761-a6731fc50e9a}*SharedItemsImports = 13 + src\Analyzers\VisualBasic\CodeFixes\VisualBasicCodeFixes.projitems*{9f9ccc78-7487-4127-9d46-db23e501f001}*SharedItemsImports = 13 + src\Analyzers\CSharp\CodeFixes\CSharpCodeFixes.projitems*{a07abcf5-bc43-4ee9-8fd8-b2d77fd54d73}*SharedItemsImports = 5 + src\Workspaces\SharedUtilitiesAndExtensions\Workspace\CSharp\CSharpWorkspaceExtensions.projitems*{a07abcf5-bc43-4ee9-8fd8-b2d77fd54d73}*SharedItemsImports = 5 + src\Analyzers\VisualBasic\Analyzers\VisualBasicAnalyzers.projitems*{a1bcd0ce-6c2f-4f8c-9a48-d9d93928e26d}*SharedItemsImports = 5 + src\Analyzers\VisualBasic\CodeFixes\VisualBasicCodeFixes.projitems*{a1bcd0ce-6c2f-4f8c-9a48-d9d93928e26d}*SharedItemsImports = 5 + src\Compilers\VisualBasic\BasicAnalyzerDriver\BasicAnalyzerDriver.projitems*{a1bcd0ce-6c2f-4f8c-9a48-d9d93928e26d}*SharedItemsImports = 5 + src\Analyzers\CSharp\Analyzers\CSharpAnalyzers.projitems*{aa87bfed-089a-4096-b8d5-690bdc7d5b24}*SharedItemsImports = 5 + src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\CSharpCompilerExtensions.projitems*{aa87bfed-089a-4096-b8d5-690bdc7d5b24}*SharedItemsImports = 5 + src\ExpressionEvaluator\Core\Source\ResultProvider\ResultProvider.projitems*{abdbac1e-350e-4dc3-bb45-3504404545ee}*SharedItemsImports = 5 + src\Analyzers\CSharp\Tests\CSharpAnalyzers.UnitTests.projitems*{ac2bcefb-9298-4621-ac48-1ff5e639e48d}*SharedItemsImports = 5 + src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{ace53515-482c-4c6a-e2d2-4242a687dfee}*SharedItemsImports = 5 + src\Compilers\Core\CommandLine\CommandLine.projitems*{ad6f474e-e6d4-4217-91f3-b7af1be31ccc}*SharedItemsImports = 13 + src\Compilers\CSharp\CSharpAnalyzerDriver\CSharpAnalyzerDriver.projitems*{b501a547-c911-4a05-ac6e-274a50dff30e}*SharedItemsImports = 5 + src\ExpressionEvaluator\Core\Source\ResultProvider\ResultProvider.projitems*{bb3ca047-5d00-48d4-b7d3-233c1265c065}*SharedItemsImports = 13 + src\ExpressionEvaluator\CSharp\Source\ResultProvider\CSharpResultProvider.projitems*{bf9dac1e-3a5e-4dc3-bb44-9a64e0d4e9d4}*SharedItemsImports = 5 + src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{c1930979-c824-496b-a630-70f5369a636f}*SharedItemsImports = 13 + src\Workspaces\SharedUtilitiesAndExtensions\Compiler\VisualBasic\VisualBasicCompilerExtensions.projitems*{cec0dce7-8d52-45c3-9295-fc7b16bd2451}*SharedItemsImports = 13 + src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{d0bc9be7-24f6-40ca-8dc6-fcb93bd44b34}*SharedItemsImports = 13 + src\Dependencies\CodeAnalysis.Debugging\Microsoft.CodeAnalysis.Debugging.projitems*{d73adf7d-2c1c-42ae-b2ab-edc9497e4b71}*SharedItemsImports = 13 + src\Analyzers\CSharp\CodeFixes\CSharpCodeFixes.projitems*{da973826-c985-4128-9948-0b445e638bdb}*SharedItemsImports = 13 + src\Analyzers\VisualBasic\Tests\VisualBasicAnalyzers.UnitTests.projitems*{e512c6c1-f085-4ad7-b0d9-e8f1a0a2a510}*SharedItemsImports = 5 + src\Compilers\Core\CommandLine\CommandLine.projitems*{e58ee9d7-1239-4961-a0c1-f9ec3952c4c1}*SharedItemsImports = 5 + src\Compilers\VisualBasic\BasicAnalyzerDriver\BasicAnalyzerDriver.projitems*{e8f0baa5-7327-43d1-9a51-644e81ae55f1}*SharedItemsImports = 13 + src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{e919dd77-34f8-4f57-8058-4d3ff4c2b241}*SharedItemsImports = 13 + src\Workspaces\SharedUtilitiesAndExtensions\Workspace\VisualBasic\VisualBasicWorkspaceExtensions.projitems*{e9dbfa41-7a9c-49be-bd36-fd71b31aa9fe}*SharedItemsImports = 13 + src\Analyzers\CSharp\Analyzers\CSharpAnalyzers.projitems*{eaffca55-335b-4860-bb99-efcead123199}*SharedItemsImports = 13 + src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\CompilerExtensions.projitems*{ec946164-1e17-410b-b7d9-7de7e6268d63}*SharedItemsImports = 13 + src\Analyzers\Core\Analyzers\Analyzers.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 5 + src\Analyzers\Core\CodeFixes\CodeFixes.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 5 + src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 5 + src\Dependencies\CodeAnalysis.Debugging\Microsoft.CodeAnalysis.Debugging.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 5 + src\ExpressionEvaluator\Core\Source\ResultProvider\ResultProvider.projitems*{fa0e905d-ec46-466d-b7b2-3b5557f9428c}*SharedItemsImports = 5 + EndGlobalSection EndGlobal diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs new file mode 100644 index 0000000000000..5d3c94c2c32db --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs @@ -0,0 +1,67 @@ +// 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. + +#nullable disable + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Test.Utilities.Snippets +{ + [UseExportProvider] + public abstract class AbstractCSharpLSPSnippetTests : AbstractCSharpCompletionProviderTests + { + protected virtual async Task VerifyCustomCommitProviderWorkerAsync(string codeBeforeCommit, string expectedLSPSnippet, int position, string itemToCommit, SourceCodeKind sourceCodeKind, char? commitChar = null) + { + using var workspaceFixture = GetOrCreateWorkspaceFixture(); + var workspace = workspaceFixture.Target.GetWorkspace(); + + // Set options that are not CompletionOptions + workspace.SetOptions(WithChangedNonCompletionOptions(workspace.Options)); + + var document1 = workspaceFixture.Target.UpdateDocument(codeBeforeCommit, sourceCodeKind); + await VerifyCustomCommitProviderLSPSnippetAsync(document1, expectedLSPSnippet, position, itemToCommit, commitChar); + + if (await CanUseSpeculativeSemanticModelAsync(document1, position)) + { + var document2 = workspaceFixture.Target.UpdateDocument(codeBeforeCommit, sourceCodeKind, cleanBeforeUpdate: false); + await VerifyCustomCommitProviderLSPSnippetAsync(document2, expectedLSPSnippet, position, itemToCommit, commitChar); + } + } + + private async Task VerifyCustomCommitProviderLSPSnippetAsync(Document document, string expectedLSPSnippet, int position, string itemToCommit, char? commitChar = null) + { + var service = GetCompletionService(document.Project); + var completionList = await GetCompletionListAsync(service, document, position, CompletionTrigger.Invoke); + var items = completionList.Items; + + Assert.Contains(itemToCommit, items.Select(x => x.DisplayText), GetStringComparer()); + var firstItem = items.First(i => CompareItems(i.DisplayText, itemToCommit)); + await VerifyCustomCommitWorkerAsync(service, document, firstItem, expectedLSPSnippet, commitChar); + } + + private async Task VerifyCustomCommitWorkerAsync( + CompletionServiceWithProviders service, + Document document, + CompletionItem completionItem, + string expectedLSPSnippet, + char? commitChar = null) + { + using var workspaceFixture = GetOrCreateWorkspaceFixture(); + + // textview is created lazily, so need to access it before making + // changes to document, so the cursor position is tracked correctly. + var textView = workspaceFixture.Target.CurrentDocument.GetTextView(); + + var commit = await service.GetChangeAsync(document, completionItem, commitChar, CancellationToken.None); + var generatedLSPSnippet = commit.LSPSnippet; + AssertEx.EqualOrDiff(expectedLSPSnippet, generatedLSPSnippet); + } + } +} diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs new file mode 100644 index 0000000000000..949ae8e0c16bb --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs @@ -0,0 +1,59 @@ +// 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; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Completion.CompletionProviders.Snippets; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities.Snippets; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders.Snippets +{ + public class CSharpLSPSnippetTests : AbstractCSharpLSPSnippetTests + { + internal override Type GetCompletionProviderType() => typeof(CSharpSnippetCompletionProvider); + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertConsoleSnippetInMethodTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + $$ + } +}"; + + var expectedLSPSnippet = +@"Console.WriteLine($0);"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, FeaturesResources.Write_to_the_console, expectedLSPSnippet); + } + + [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task InsertIfSnippetInMethodTest() + { + var markupBeforeCommit = +@"class Program +{ + public void Method() + { + $$ + } +}"; + + var expectedLSPSnippet = +@"if ({1:true}) +{$0 +};"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, FeaturesResources.Insert_an_if_statement, expectedLSPSnippet); + } + } +} diff --git a/src/EditorFeatures/TestUtilities/Snippets/AbstractSnippetTests.cs b/src/EditorFeatures/TestUtilities/Snippets/AbstractSnippetTests.cs new file mode 100644 index 0000000000000..f6136da895860 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/Snippets/AbstractSnippetTests.cs @@ -0,0 +1,73 @@ +/*// 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. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Editor.UnitTests.Completion; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Test.Utilities.Snippets +{ + [UseExportProvider] + public abstract class AbstractSnippetTests : AbstractCSharpCompletionProviderTests + { + protected virtual async Task VerifyCustomCommitProviderWorkerAsync(string codeBeforeCommit, string expectedLSPSnippet, int position, string itemToCommit, SourceCodeKind sourceCodeKind, char? commitChar = null) + { + using var workspaceFixture = GetOrCreateWorkspaceFixture(); + var workspace = workspaceFixture.Target.GetWorkspace(); + + // Set options that are not CompletionOptions + workspace.SetOptions(WithChangedNonCompletionOptions(workspace.Options)); + + var document1 = workspaceFixture.Target.UpdateDocument(codeBeforeCommit, sourceCodeKind); + await VerifyCustomCommitProviderLSPSnippetAsync(document1, expectedLSPSnippet, position, itemToCommit, commitChar); + + if (await CanUseSpeculativeSemanticModelAsync(document1, position)) + { + var document2 = workspaceFixture.Target.UpdateDocument(codeBeforeCommit, sourceCodeKind, cleanBeforeUpdate: false); + await VerifyCustomCommitProviderLSPSnippetAsync(document2, expectedLSPSnippet, position, itemToCommit, commitChar); + } + } + + private async Task VerifyCustomCommitProviderLSPSnippetAsync(Document document, string expectedLSPSnippet, int position, string itemToCommit, char? commitChar = null) + { + var service = GetCompletionService(document.Project); + var completionList = await GetCompletionListAsync(service, document, position, CompletionTrigger.Invoke); + var items = completionList.Items; + + Assert.Contains(itemToCommit, items.Select(x => x.DisplayText), GetStringComparer()); + var firstItem = items.First(i => CompareItems(i.DisplayText, itemToCommit)); + await VerifyCustomCommitWorkerAsync(service, document, firstItem, expectedLSPSnippet, commitChar); + } + + private async Task VerifyCustomCommitWorkerAsync( + CompletionServiceWithProviders service, + Document document, + CompletionItem completionItem, + string expectedLSPSnippet, + char? commitChar = null) + { + using var workspaceFixture = GetOrCreateWorkspaceFixture(); + + // textview is created lazily, so need to access it before making + // changes to document, so the cursor position is tracked correctly. + var textView = workspaceFixture.Target.CurrentDocument.GetTextView(); + + var commit = await service.GetChangeAsync(document, completionItem, commitChar, CancellationToken.None); + var generatedLSPSnippet = commit.LSPSnippet; + AssertEx.EqualOrDiff(expectedLSPSnippet, generatedLSPSnippet); + } + } +} +*/ diff --git a/src/Features/CSharp/Portable/Snippets/CSharpConvertToLSPSnippetService.cs b/src/Features/CSharp/Portable/Snippets/CSharpConvertToLSPSnippetService.cs new file mode 100644 index 0000000000000..5700d25e6379e --- /dev/null +++ b/src/Features/CSharp/Portable/Snippets/CSharpConvertToLSPSnippetService.cs @@ -0,0 +1,23 @@ +// 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; +using System.Collections.Generic; +using System.Composition; +using System.Text; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Snippets; + +namespace Microsoft.CodeAnalysis.CSharp.Snippets +{ + [ExportLanguageService(typeof(ConvertToLSPSnippetService), LanguageNames.CSharp), Shared] + internal class CSharpConvertToLSPSnippetService : ConvertToLSPSnippetService + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CSharpConvertToLSPSnippetService() + { + } + } +} diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs index 2f0afac3657ef..ac361659d58cf 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs @@ -37,32 +37,10 @@ public override async Task GetChangeAsync(Document document, C var allTextChanges = await allChangesDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); var change = Utilities.Collapse(allChangesText, allTextChanges.AsImmutable()); - var lspSnippet = GenerateLSPSnippet(change, snippet.Placeholders); + var lspSnippetService = allChangesDocument.GetRequiredLanguageService(); + var lspSnippet = lspSnippetService.GenerateLSPSnippet(change, snippet.Placeholders); return CompletionChange.CreateSpecialLSPSnippetChange(change, allTextChanges.AsImmutable(), newPosition: snippet.CursorPosition, includesCommitCharacter: true, lspSnippet); } - - private static string? GenerateLSPSnippet(TextChange textChange, List<(string, List)> placeholders) - { - var textChangeText = textChange.NewText!; - - for (var i = 0; i < placeholders.Count; i++) - { - var (identifier, placeholderList) = placeholders[i]; - if (identifier.Length != 0) - { - var newStr = $"${{{i}:{identifier}}}"; - textChangeText = textChangeText.Replace(identifier, newStr); - } - else - { - var location = placeholderList[0]; - textChangeText = textChangeText.Insert(location.Start - textChange.Span.Start, $"$0"); - } - } - - return textChangeText; - } - public override async Task ProvideCompletionsAsync(CompletionContext context) { var document = context.Document; diff --git a/src/Features/Core/Portable/Snippets/ConvertToLSPSnippetService.cs b/src/Features/Core/Portable/Snippets/ConvertToLSPSnippetService.cs new file mode 100644 index 0000000000000..6a5da4a275f1d --- /dev/null +++ b/src/Features/Core/Portable/Snippets/ConvertToLSPSnippetService.cs @@ -0,0 +1,37 @@ +// 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; +using System.Collections.Generic; +using System.Text; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Snippets +{ + internal class ConvertToLSPSnippetService : ILanguageService + { + public string? GenerateLSPSnippet(TextChange textChange, List<(string, List)> placeholders) + { + var textChangeText = textChange.NewText!; + + for (var i = 0; i < placeholders.Count; i++) + { + var (identifier, placeholderList) = placeholders[i]; + if (identifier.Length != 0) + { + var newStr = $"${{{i}:{identifier}}}"; + textChangeText = textChangeText.Replace(identifier, newStr); + } + else + { + var location = placeholderList[0]; + textChangeText = textChangeText.Insert(location.Start - textChange.Span.Start, $"$0"); + } + } + + return textChangeText; + } + } +} diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs index 2f62ede1a7fd8..581ebfb398ce3 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -109,7 +109,9 @@ public async Task GetSnippetAsync(Document document, int position SyntaxToken AddAnnotations(SyntaxToken oldToken, SyntaxToken newToken) { - return oldToken.WithAdditionalAnnotations(SyntaxAnnotation.ElasticAnnotation).WithAppendedTrailingTrivia(syntaxFacts.ElasticMarker).WithPrependedLeadingTrivia(syntaxFacts.ElasticMarker); + return oldToken.WithAdditionalAnnotations(SyntaxAnnotation.ElasticAnnotation) + .WithAppendedTrailingTrivia(syntaxFacts.ElasticMarker) + .WithPrependedLeadingTrivia(syntaxFacts.ElasticMarker); } } From e1373636f322eeed87305f5dcf9153ac07c1077f Mon Sep 17 00:00:00 2001 From: akhera99 Date: Tue, 19 Apr 2022 14:49:15 -0700 Subject: [PATCH 13/58] remove added changes --- Roslyn.sln | 96 +----------------------------------------------------- 1 file changed, 1 insertion(+), 95 deletions(-) diff --git a/Roslyn.sln b/Roslyn.sln index b63746b0bfdd8..22097a3370260 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -946,12 +946,6 @@ Global {ABC7262E-1053-49F3-B846-E3091BB92E8C}.Debug|Any CPU.Build.0 = Debug|Any CPU {ABC7262E-1053-49F3-B846-E3091BB92E8C}.Release|Any CPU.ActiveCfg = Release|Any CPU {ABC7262E-1053-49F3-B846-E3091BB92E8C}.Release|Any CPU.Build.0 = Release|Any CPU - {E8F0BAA5-7327-43D1-9A51-644E81AE55F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E8F0BAA5-7327-43D1-9A51-644E81AE55F1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {54E08BF5-F819-404F-A18D-0AB9EA81EA04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {54E08BF5-F819-404F-A18D-0AB9EA81EA04}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AD6F474E-E6D4-4217-91F3-B7AF1BE31CCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AD6F474E-E6D4-4217-91F3-B7AF1BE31CCC}.Release|Any CPU.ActiveCfg = Release|Any CPU {43026D51-3083-4850-928D-07E1883D5B1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {43026D51-3083-4850-928D-07E1883D5B1A}.Debug|Any CPU.Build.0 = Debug|Any CPU {43026D51-3083-4850-928D-07E1883D5B1A}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -980,10 +974,6 @@ Global {59AD474E-2A35-4E8A-A74D-E33479977FBF}.Debug|Any CPU.Build.0 = Debug|Any CPU {59AD474E-2A35-4E8A-A74D-E33479977FBF}.Release|Any CPU.ActiveCfg = Release|Any CPU {59AD474E-2A35-4E8A-A74D-E33479977FBF}.Release|Any CPU.Build.0 = Release|Any CPU - {D73ADF7D-2C1C-42AE-B2AB-EDC9497E4B71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D73ADF7D-2C1C-42AE-B2AB-EDC9497E4B71}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C1930979-C824-496B-A630-70F5369A636F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C1930979-C824-496B-A630-70F5369A636F}.Release|Any CPU.ActiveCfg = Release|Any CPU {F822F72A-CC87-4E31-B57D-853F65CBEBF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F822F72A-CC87-4E31-B57D-853F65CBEBF3}.Debug|Any CPU.Build.0 = Debug|Any CPU {F822F72A-CC87-4E31-B57D-853F65CBEBF3}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1212,12 +1202,6 @@ Global {D15BF03E-04ED-4BEE-A72B-7620F541F4E2}.Debug|Any CPU.Build.0 = Debug|Any CPU {D15BF03E-04ED-4BEE-A72B-7620F541F4E2}.Release|Any CPU.ActiveCfg = Release|Any CPU {D15BF03E-04ED-4BEE-A72B-7620F541F4E2}.Release|Any CPU.Build.0 = Release|Any CPU - {438DB8AF-F3F0-4ED9-80B5-13FDDD5B8787}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {438DB8AF-F3F0-4ED9-80B5-13FDDD5B8787}.Release|Any CPU.ActiveCfg = Release|Any CPU - {58969243-7F59-4236-93D0-C93B81F569B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {58969243-7F59-4236-93D0-C93B81F569B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1B6C4A1A-413B-41FB-9F85-5C09118E541B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1B6C4A1A-413B-41FB-9F85-5C09118E541B}.Release|Any CPU.ActiveCfg = Release|Any CPU {B64766CD-1A1F-4C1B-B11F-C30F82B8E41E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B64766CD-1A1F-4C1B-B11F-C30F82B8E41E}.Debug|Any CPU.Build.0 = Debug|Any CPU {B64766CD-1A1F-4C1B-B11F-C30F82B8E41E}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1551,82 +1535,4 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {604E6B91-7BC0-4126-AE07-D4D2FEFC3D29} EndGlobalSection - GlobalSection(SharedMSBuildProjectFiles) = preSolution - src\Analyzers\VisualBasic\CodeFixes\VisualBasicCodeFixes.projitems*{0141285d-8f6c-42c7-baf3-3c0ccd61c716}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\VisualBasic\VisualBasicWorkspaceExtensions.projitems*{0141285d-8f6c-42c7-baf3-3c0ccd61c716}*SharedItemsImports = 5 - src\Analyzers\VisualBasic\Tests\VisualBasicAnalyzers.UnitTests.projitems*{0be66736-cdaa-4989-88b1-b3f46ebdca4a}*SharedItemsImports = 5 - src\Analyzers\Core\CodeFixes\CodeFixes.projitems*{1b6c4a1a-413b-41fb-9f85-5c09118e541b}*SharedItemsImports = 13 - src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 - src\Dependencies\CodeAnalysis.Debugging\Microsoft.CodeAnalysis.Debugging.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 - src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 - src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{1ee8cad3-55f9-4d91-96b2-084641da9a6c}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\CSharpCompilerExtensions.projitems*{21b239d0-d144-430f-a394-c066d58ee267}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\CSharp\CSharpWorkspaceExtensions.projitems*{21b239d0-d144-430f-a394-c066d58ee267}*SharedItemsImports = 5 - src\Compilers\VisualBasic\BasicAnalyzerDriver\BasicAnalyzerDriver.projitems*{2523d0e6-df32-4a3e-8ae0-a19bffae2ef6}*SharedItemsImports = 5 - src\Analyzers\VisualBasic\Analyzers\VisualBasicAnalyzers.projitems*{2531a8c4-97dd-47bc-a79c-b7846051e137}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\VisualBasic\VisualBasicCompilerExtensions.projitems*{2531a8c4-97dd-47bc-a79c-b7846051e137}*SharedItemsImports = 5 - src\Analyzers\Core\Analyzers\Analyzers.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 - src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 - src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\CompilerExtensions.projitems*{275812ee-dedb-4232-9439-91c9757d2ae4}*SharedItemsImports = 5 - src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{3140fe61-0856-4367-9aa3-8081b9a80e35}*SharedItemsImports = 13 - src\ExpressionEvaluator\CSharp\Source\ResultProvider\CSharpResultProvider.projitems*{3140fe61-0856-4367-9aa3-8081b9a80e36}*SharedItemsImports = 13 - src\Analyzers\CSharp\Analyzers\CSharpAnalyzers.projitems*{3973b09a-4fbf-44a5-8359-3d22ceb71f71}*SharedItemsImports = 5 - src\Analyzers\CSharp\CodeFixes\CSharpCodeFixes.projitems*{3973b09a-4fbf-44a5-8359-3d22ceb71f71}*SharedItemsImports = 5 - src\Compilers\CSharp\CSharpAnalyzerDriver\CSharpAnalyzerDriver.projitems*{3973b09a-4fbf-44a5-8359-3d22ceb71f71}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\CSharp\CSharpWorkspaceExtensions.projitems*{438db8af-f3f0-4ed9-80b5-13fddd5b8787}*SharedItemsImports = 13 - src\Compilers\Core\CommandLine\CommandLine.projitems*{4b45ca0c-03a0-400f-b454-3d4bcb16af38}*SharedItemsImports = 5 - src\Analyzers\CSharp\Tests\CSharpAnalyzers.UnitTests.projitems*{5018d049-5870-465a-889b-c742ce1e31cb}*SharedItemsImports = 5 - src\Compilers\CSharp\CSharpAnalyzerDriver\CSharpAnalyzerDriver.projitems*{54e08bf5-f819-404f-a18d-0ab9ea81ea04}*SharedItemsImports = 13 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\VisualBasic\VisualBasicCompilerExtensions.projitems*{57ca988d-f010-4bf2-9a2e-07d6dcd2ff2c}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\VisualBasic\VisualBasicWorkspaceExtensions.projitems*{57ca988d-f010-4bf2-9a2e-07d6dcd2ff2c}*SharedItemsImports = 5 - src\Analyzers\CSharp\Tests\CSharpAnalyzers.UnitTests.projitems*{58969243-7f59-4236-93d0-c93b81f569b3}*SharedItemsImports = 13 - src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 - src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\CompilerExtensions.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\WorkspaceExtensions.projitems*{5f8d2414-064a-4b3a-9b42-8e2a04246be5}*SharedItemsImports = 5 - src\Analyzers\Core\CodeFixes\CodeFixes.projitems*{5ff1e493-69cc-4d0b-83f2-039f469a04e1}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\WorkspaceExtensions.projitems*{5ff1e493-69cc-4d0b-83f2-039f469a04e1}*SharedItemsImports = 5 - src\ExpressionEvaluator\CSharp\Source\ResultProvider\CSharpResultProvider.projitems*{60db272a-21c9-4e8d-9803-ff4e132392c8}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\CSharpCompilerExtensions.projitems*{699fea05-aea7-403d-827e-53cf4e826955}*SharedItemsImports = 13 - src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{76242a2d-2600-49dd-8c15-fea07ecb1843}*SharedItemsImports = 5 - src\Analyzers\Core\Analyzers\Analyzers.projitems*{76e96966-4780-4040-8197-bde2879516f4}*SharedItemsImports = 13 - src\Compilers\Core\CommandLine\CommandLine.projitems*{7ad4fe65-9a30-41a6-8004-aa8f89bcb7f3}*SharedItemsImports = 5 - src\Analyzers\VisualBasic\Tests\VisualBasicAnalyzers.UnitTests.projitems*{7b7f4153-ae93-4908-b8f0-430871589f83}*SharedItemsImports = 13 - src\Analyzers\VisualBasic\Analyzers\VisualBasicAnalyzers.projitems*{94faf461-2e74-4dbb-9813-6b2cde6f1880}*SharedItemsImports = 13 - src\Compilers\Core\CommandLine\CommandLine.projitems*{9508f118-f62e-4c16-a6f4-7c3b56e166ad}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\Core\WorkspaceExtensions.projitems*{99f594b1-3916-471d-a761-a6731fc50e9a}*SharedItemsImports = 13 - src\Analyzers\VisualBasic\CodeFixes\VisualBasicCodeFixes.projitems*{9f9ccc78-7487-4127-9d46-db23e501f001}*SharedItemsImports = 13 - src\Analyzers\CSharp\CodeFixes\CSharpCodeFixes.projitems*{a07abcf5-bc43-4ee9-8fd8-b2d77fd54d73}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\CSharp\CSharpWorkspaceExtensions.projitems*{a07abcf5-bc43-4ee9-8fd8-b2d77fd54d73}*SharedItemsImports = 5 - src\Analyzers\VisualBasic\Analyzers\VisualBasicAnalyzers.projitems*{a1bcd0ce-6c2f-4f8c-9a48-d9d93928e26d}*SharedItemsImports = 5 - src\Analyzers\VisualBasic\CodeFixes\VisualBasicCodeFixes.projitems*{a1bcd0ce-6c2f-4f8c-9a48-d9d93928e26d}*SharedItemsImports = 5 - src\Compilers\VisualBasic\BasicAnalyzerDriver\BasicAnalyzerDriver.projitems*{a1bcd0ce-6c2f-4f8c-9a48-d9d93928e26d}*SharedItemsImports = 5 - src\Analyzers\CSharp\Analyzers\CSharpAnalyzers.projitems*{aa87bfed-089a-4096-b8d5-690bdc7d5b24}*SharedItemsImports = 5 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\CSharpCompilerExtensions.projitems*{aa87bfed-089a-4096-b8d5-690bdc7d5b24}*SharedItemsImports = 5 - src\ExpressionEvaluator\Core\Source\ResultProvider\ResultProvider.projitems*{abdbac1e-350e-4dc3-bb45-3504404545ee}*SharedItemsImports = 5 - src\Analyzers\CSharp\Tests\CSharpAnalyzers.UnitTests.projitems*{ac2bcefb-9298-4621-ac48-1ff5e639e48d}*SharedItemsImports = 5 - src\ExpressionEvaluator\VisualBasic\Source\ResultProvider\BasicResultProvider.projitems*{ace53515-482c-4c6a-e2d2-4242a687dfee}*SharedItemsImports = 5 - src\Compilers\Core\CommandLine\CommandLine.projitems*{ad6f474e-e6d4-4217-91f3-b7af1be31ccc}*SharedItemsImports = 13 - src\Compilers\CSharp\CSharpAnalyzerDriver\CSharpAnalyzerDriver.projitems*{b501a547-c911-4a05-ac6e-274a50dff30e}*SharedItemsImports = 5 - src\ExpressionEvaluator\Core\Source\ResultProvider\ResultProvider.projitems*{bb3ca047-5d00-48d4-b7d3-233c1265c065}*SharedItemsImports = 13 - src\ExpressionEvaluator\CSharp\Source\ResultProvider\CSharpResultProvider.projitems*{bf9dac1e-3a5e-4dc3-bb44-9a64e0d4e9d4}*SharedItemsImports = 5 - src\Dependencies\PooledObjects\Microsoft.CodeAnalysis.PooledObjects.projitems*{c1930979-c824-496b-a630-70f5369a636f}*SharedItemsImports = 13 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\VisualBasic\VisualBasicCompilerExtensions.projitems*{cec0dce7-8d52-45c3-9295-fc7b16bd2451}*SharedItemsImports = 13 - src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{d0bc9be7-24f6-40ca-8dc6-fcb93bd44b34}*SharedItemsImports = 13 - src\Dependencies\CodeAnalysis.Debugging\Microsoft.CodeAnalysis.Debugging.projitems*{d73adf7d-2c1c-42ae-b2ab-edc9497e4b71}*SharedItemsImports = 13 - src\Analyzers\CSharp\CodeFixes\CSharpCodeFixes.projitems*{da973826-c985-4128-9948-0b445e638bdb}*SharedItemsImports = 13 - src\Analyzers\VisualBasic\Tests\VisualBasicAnalyzers.UnitTests.projitems*{e512c6c1-f085-4ad7-b0d9-e8f1a0a2a510}*SharedItemsImports = 5 - src\Compilers\Core\CommandLine\CommandLine.projitems*{e58ee9d7-1239-4961-a0c1-f9ec3952c4c1}*SharedItemsImports = 5 - src\Compilers\VisualBasic\BasicAnalyzerDriver\BasicAnalyzerDriver.projitems*{e8f0baa5-7327-43d1-9a51-644e81ae55f1}*SharedItemsImports = 13 - src\Dependencies\Collections\Microsoft.CodeAnalysis.Collections.projitems*{e919dd77-34f8-4f57-8058-4d3ff4c2b241}*SharedItemsImports = 13 - src\Workspaces\SharedUtilitiesAndExtensions\Workspace\VisualBasic\VisualBasicWorkspaceExtensions.projitems*{e9dbfa41-7a9c-49be-bd36-fd71b31aa9fe}*SharedItemsImports = 13 - src\Analyzers\CSharp\Analyzers\CSharpAnalyzers.projitems*{eaffca55-335b-4860-bb99-efcead123199}*SharedItemsImports = 13 - src\Workspaces\SharedUtilitiesAndExtensions\Compiler\Core\CompilerExtensions.projitems*{ec946164-1e17-410b-b7d9-7de7e6268d63}*SharedItemsImports = 13 - src\Analyzers\Core\Analyzers\Analyzers.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 5 - src\Analyzers\Core\CodeFixes\CodeFixes.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 5 - src\Compilers\Core\AnalyzerDriver\AnalyzerDriver.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 5 - src\Dependencies\CodeAnalysis.Debugging\Microsoft.CodeAnalysis.Debugging.projitems*{edc68a0e-c68d-4a74-91b7-bf38ec909888}*SharedItemsImports = 5 - src\ExpressionEvaluator\Core\Source\ResultProvider\ResultProvider.projitems*{fa0e905d-ec46-466d-b7b2-3b5557f9428c}*SharedItemsImports = 5 - EndGlobalSection -EndGlobal +EndGlobal \ No newline at end of file From e78d2d4e698a428258997560aae1fa81910ab670 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Tue, 19 Apr 2022 15:18:15 -0700 Subject: [PATCH 14/58] fixes --- .../CompletionProviders/Snippets/CSharpLSPSnippetTests.cs | 1 + .../Core/IntelliSense/AsyncCompletion/CommitManager.cs | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs index 949ae8e0c16bb..7ab5e165d85a5 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs @@ -34,6 +34,7 @@ public void Method() var expectedLSPSnippet = @"Console.WriteLine($0);"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, FeaturesResources.Write_to_the_console, expectedLSPSnippet); } diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs index d35d557680c0a..438ccd6ffedae 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs @@ -64,7 +64,7 @@ internal CommitManager( RecentItemsManager recentItemsManager, IGlobalOptionService globalOptions, IThreadingContext threadingContext, - bool languageServerSnippetExpander) + object languageServerSnippetExpander) { _globalOptions = globalOptions; _threadingContext = threadingContext; @@ -320,8 +320,8 @@ private AsyncCompletionData.CommitResult Commit( { var spanToFormat = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); var changes = formattingService.GetFormattingChangesAsync( - currentDocument, spanToFormat.Span.ToTextSpan(), documentOptions: null, CancellationToken.None).WaitAndGetResult(CancellationToken.None); - currentDocument.Project.Solution.Workspace.ApplyTextChanges(currentDocument.Id, changes, CancellationToken.None); + currentDocument, spanToFormat.Span.ToTextSpan(), cancellationToken).WaitAndGetResult(cancellationToken); + currentDocument.Project.Solution.Workspace.ApplyTextChanges(currentDocument.Id, changes, cancellationToken); } } } @@ -330,7 +330,7 @@ private AsyncCompletionData.CommitResult Commit( if (provider is INotifyCommittingItemCompletionProvider notifyProvider) { - _ = ThreadingContext.JoinableTaskFactory.RunAsync(async () => + _ = _threadingContext.JoinableTaskFactory.RunAsync(async () => { // Make sure the notification isn't sent on UI thread. await TaskScheduler.Default; From 9220332eac7b0f1c98607fadf7a55300bbb50dc9 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Tue, 19 Apr 2022 15:59:07 -0700 Subject: [PATCH 15/58] cleanup --- .../Snippets/AbstractCSharpLSPSnippetTests.cs | 8 ++++---- .../Snippets/CSharpLSPSnippetTests.cs | 15 +++++++++++---- .../Core/Portable/Completion/CompletionChange.cs | 6 +++--- .../Snippets/AbstractSnippetCompletionProvider.cs | 3 ++- 4 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs index 5d3c94c2c32db..c699b4bd1d9bb 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.Test.Utilities.Snippets [UseExportProvider] public abstract class AbstractCSharpLSPSnippetTests : AbstractCSharpCompletionProviderTests { - protected virtual async Task VerifyCustomCommitProviderWorkerAsync(string codeBeforeCommit, string expectedLSPSnippet, int position, string itemToCommit, SourceCodeKind sourceCodeKind, char? commitChar = null) + protected override async Task VerifyCustomCommitProviderWorkerAsync(string codeBeforeCommit, int position, string itemToCommit, string expectedLSPSnippet, SourceCodeKind sourceCodeKind, char? commitChar = null) { using var workspaceFixture = GetOrCreateWorkspaceFixture(); var workspace = workspaceFixture.Target.GetWorkspace(); @@ -26,16 +26,16 @@ protected virtual async Task VerifyCustomCommitProviderWorkerAsync(string codeBe workspace.SetOptions(WithChangedNonCompletionOptions(workspace.Options)); var document1 = workspaceFixture.Target.UpdateDocument(codeBeforeCommit, sourceCodeKind); - await VerifyCustomCommitProviderLSPSnippetAsync(document1, expectedLSPSnippet, position, itemToCommit, commitChar); + await VerifyCustomCommitProviderLSPSnippetAsync(document1, position, itemToCommit, expectedLSPSnippet, commitChar); if (await CanUseSpeculativeSemanticModelAsync(document1, position)) { var document2 = workspaceFixture.Target.UpdateDocument(codeBeforeCommit, sourceCodeKind, cleanBeforeUpdate: false); - await VerifyCustomCommitProviderLSPSnippetAsync(document2, expectedLSPSnippet, position, itemToCommit, commitChar); + await VerifyCustomCommitProviderLSPSnippetAsync(document2, position, itemToCommit, expectedLSPSnippet, commitChar); } } - private async Task VerifyCustomCommitProviderLSPSnippetAsync(Document document, string expectedLSPSnippet, int position, string itemToCommit, char? commitChar = null) + private async Task VerifyCustomCommitProviderLSPSnippetAsync(Document document, int position, string itemToCommit, string expectedLSPSnippet, char? commitChar = null) { var service = GetCompletionService(document.Project); var completionList = await GetCompletionListAsync(service, document, position, CompletionTrigger.Invoke); diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs index 7ab5e165d85a5..7d8eb89509b8f 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs @@ -33,7 +33,13 @@ public void Method() }"; var expectedLSPSnippet = -@"Console.WriteLine($0);"; +@"using System; + +class Program +{ + public void Method() + { + Console.WriteLine($0);"; await VerifyCustomCommitProviderAsync(markupBeforeCommit, FeaturesResources.Write_to_the_console, expectedLSPSnippet); } @@ -51,9 +57,10 @@ public void Method() }"; var expectedLSPSnippet = -@"if ({1:true}) -{$0 -};"; +@"if (${1:true}) + {$0 + }"; + await VerifyCustomCommitProviderAsync(markupBeforeCommit, FeaturesResources.Insert_an_if_statement, expectedLSPSnippet); } } diff --git a/src/Features/Core/Portable/Completion/CompletionChange.cs b/src/Features/Core/Portable/Completion/CompletionChange.cs index 0652cb9be8c1a..7d76fe599109f 100644 --- a/src/Features/Core/Portable/Completion/CompletionChange.cs +++ b/src/Features/Core/Portable/Completion/CompletionChange.cs @@ -111,12 +111,12 @@ public static CompletionChange Create( return new CompletionChange(textChange, textChanges, newPosition, includesCommitCharacter); } - internal static CompletionChange CreateSpecialLSPSnippetChange( + internal static CompletionChange Create( TextChange textChange, ImmutableArray textChanges, + string? lspSnippet, int? newPosition = null, - bool includesCommitCharacter = false, - string? lspSnippet = null) + bool includesCommitCharacter = false) { return new CompletionChange(textChange, textChanges, newPosition, includesCommitCharacter, lspSnippet); } diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs index ac361659d58cf..3bbbd48058b9b 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs @@ -39,8 +39,9 @@ public override async Task GetChangeAsync(Document document, C var change = Utilities.Collapse(allChangesText, allTextChanges.AsImmutable()); var lspSnippetService = allChangesDocument.GetRequiredLanguageService(); var lspSnippet = lspSnippetService.GenerateLSPSnippet(change, snippet.Placeholders); - return CompletionChange.CreateSpecialLSPSnippetChange(change, allTextChanges.AsImmutable(), newPosition: snippet.CursorPosition, includesCommitCharacter: true, lspSnippet); + return CompletionChange.Create(change, allTextChanges.AsImmutable(), lspSnippet, newPosition: snippet.CursorPosition, includesCommitCharacter: true); } + public override async Task ProvideCompletionsAsync(CompletionContext context) { var document = context.Document; From 5e4aefe9587e73ddbfddc5c84f5a0e98b741e3eb Mon Sep 17 00:00:00 2001 From: akhera99 Date: Tue, 19 Apr 2022 16:20:55 -0700 Subject: [PATCH 16/58] comments --- .../AbstractSnippetProvider.cs | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs index 581ebfb398ce3..bd01962897ff6 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.AddImport; using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeCleanup; using Microsoft.CodeAnalysis.EditAndContinue.Contracts; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.ExtractMethod; @@ -48,6 +49,9 @@ internal abstract class AbstractSnippetProvider : ISnippetProvider protected abstract Task AnnotateNodesToReformatAsync(Document document, SyntaxAnnotation reformatAnnotation, SyntaxAnnotation cursorAnnotation, int position, CancellationToken cancellationToken); protected abstract int? GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget); + /// + /// Every SnippetProvider will need a method to retrieve the "main" snippet syntax once it has been inserted as a TextChange. + /// protected abstract SyntaxNode? FindAddedSnippetSyntaxNode(SyntaxNode root, int position, ISyntaxFacts syntaxFacts); /// @@ -79,15 +83,33 @@ internal abstract class AbstractSnippetProvider : ISnippetProvider public async Task GetSnippetAsync(Document document, int position, CancellationToken cancellationToken) { var syntaxFacts = document.GetRequiredLanguageService(); + + // Generates the snippet as a list of textchanges var textChanges = await GenerateSnippetTextChangesAsync(document, position, cancellationToken).ConfigureAwait(false); + + // Applies the snippet textchanges to the document var snippetDocument = await GetDocumentWithSnippetAsync(document, textChanges, cancellationToken).ConfigureAwait(false); + + // Finds the inserted snippet and replaces the node in the document with a node that has added trivia + // since all trivia is removed when converted to a TextChange. var snippetWithTriviaDocument = await GetDocumentWithSnippetAndTriviaAsync(snippetDocument, position, syntaxFacts, cancellationToken).ConfigureAwait(false); + + // Adds annotations to inserted snippet to be formatted, simplified, add imports if needed, etc. var formatAnnotatedSnippetDocument = await AddFormatAnnotationAsync(snippetWithTriviaDocument, position, cancellationToken).ConfigureAwait(false); + + // Goes through and calls upon the formatting engines that the previous step annotated. var reformattedDocument = await CleanupDocumentAsync(formatAnnotatedSnippetDocument, cancellationToken).ConfigureAwait(false); + var reformattedRoot = await reformattedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var caretTarget = reformattedRoot.GetAnnotatedNodes(_cursorAnnotation).SingleOrDefault(); var mainChangeNode = reformattedRoot.GetAnnotatedNodes(_findSnippetAnnotation).SingleOrDefault(); + + // All the TextChanges from the original document. Will include any imports (if necessary) and all snippet associated + // changes after having been formatted. var changes = await reformattedDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); + + // Gets a listing of the identifiers that need to be found in the snippet TextChange + // and their associated TextSpan so they can later be converted into an LSP snippet format. var placeholders = GetRenameLocationsMap(mainChangeNode, syntaxFacts, cancellationToken); var changesArray = changes.ToImmutableArray(); return new SnippetChange( @@ -96,6 +118,9 @@ public async Task GetSnippetAsync(Document document, int position placeholders: placeholders); } + /// + /// Descends into the inserted snippet to add back trivia on every token. + /// protected static SyntaxNode? GenerateElasticTriviaForSyntax(ISyntaxFacts syntaxFacts, SyntaxNode? node) { if (node is null) @@ -120,23 +145,27 @@ private async Task CleanupDocumentAsync( { if (document.SupportsSyntaxTree) { - var addImportOptions = await AddImportPlacementOptions.FromDocumentAsync(document, cancellationToken).ConfigureAwait(false); + var options = await CodeCleanupOptions.FromDocumentAsync(document, fallbackOptions: null, cancellationToken).ConfigureAwait(false); document = await ImportAdder.AddImportsFromSymbolAnnotationAsync( - document, _findSnippetAnnotation, addImportOptions, cancellationToken: cancellationToken).ConfigureAwait(false); + document, _findSnippetAnnotation, options.AddImportOptions, cancellationToken: cancellationToken).ConfigureAwait(false); - document = await Simplifier.ReduceAsync(document, _findSnippetAnnotation, cancellationToken: cancellationToken).ConfigureAwait(false); + document = await Simplifier.ReduceAsync(document, _findSnippetAnnotation, options.SimplifierOptions, cancellationToken: cancellationToken).ConfigureAwait(false); // format any node with explicit formatter annotation - document = await Formatter.FormatAsync(document, _findSnippetAnnotation, cancellationToken: cancellationToken).ConfigureAwait(false); + document = await Formatter.FormatAsync(document, _findSnippetAnnotation, options.FormattingOptions, cancellationToken: cancellationToken).ConfigureAwait(false); // format any elastic whitespace - document = await Formatter.FormatAsync(document, SyntaxAnnotation.ElasticAnnotation, cancellationToken: cancellationToken).ConfigureAwait(false); + document = await Formatter.FormatAsync(document, SyntaxAnnotation.ElasticAnnotation, options.FormattingOptions, cancellationToken: cancellationToken).ConfigureAwait(false); } return document; } + /// + /// Locates the snippet that was inserted. Generates trivia for every token in that syntaxnode. + /// Replaces the SyntaxNodes and gets back the new document. + /// private async Task GetDocumentWithSnippetAndTriviaAsync(Document snippetDocument, int position, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { var root = await snippetDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); From 334331b08eab628ce5728a6fd9d1da3e40a16391 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Tue, 19 Apr 2022 16:25:39 -0700 Subject: [PATCH 17/58] remove unused file --- .../Snippets/AbstractSnippetTests.cs | 73 ------------------- 1 file changed, 73 deletions(-) delete mode 100644 src/EditorFeatures/TestUtilities/Snippets/AbstractSnippetTests.cs diff --git a/src/EditorFeatures/TestUtilities/Snippets/AbstractSnippetTests.cs b/src/EditorFeatures/TestUtilities/Snippets/AbstractSnippetTests.cs deleted file mode 100644 index f6136da895860..0000000000000 --- a/src/EditorFeatures/TestUtilities/Snippets/AbstractSnippetTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -/*// 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. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.Editor.UnitTests.Completion; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Microsoft.CodeAnalysis.Text; -using Roslyn.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Test.Utilities.Snippets -{ - [UseExportProvider] - public abstract class AbstractSnippetTests : AbstractCSharpCompletionProviderTests - { - protected virtual async Task VerifyCustomCommitProviderWorkerAsync(string codeBeforeCommit, string expectedLSPSnippet, int position, string itemToCommit, SourceCodeKind sourceCodeKind, char? commitChar = null) - { - using var workspaceFixture = GetOrCreateWorkspaceFixture(); - var workspace = workspaceFixture.Target.GetWorkspace(); - - // Set options that are not CompletionOptions - workspace.SetOptions(WithChangedNonCompletionOptions(workspace.Options)); - - var document1 = workspaceFixture.Target.UpdateDocument(codeBeforeCommit, sourceCodeKind); - await VerifyCustomCommitProviderLSPSnippetAsync(document1, expectedLSPSnippet, position, itemToCommit, commitChar); - - if (await CanUseSpeculativeSemanticModelAsync(document1, position)) - { - var document2 = workspaceFixture.Target.UpdateDocument(codeBeforeCommit, sourceCodeKind, cleanBeforeUpdate: false); - await VerifyCustomCommitProviderLSPSnippetAsync(document2, expectedLSPSnippet, position, itemToCommit, commitChar); - } - } - - private async Task VerifyCustomCommitProviderLSPSnippetAsync(Document document, string expectedLSPSnippet, int position, string itemToCommit, char? commitChar = null) - { - var service = GetCompletionService(document.Project); - var completionList = await GetCompletionListAsync(service, document, position, CompletionTrigger.Invoke); - var items = completionList.Items; - - Assert.Contains(itemToCommit, items.Select(x => x.DisplayText), GetStringComparer()); - var firstItem = items.First(i => CompareItems(i.DisplayText, itemToCommit)); - await VerifyCustomCommitWorkerAsync(service, document, firstItem, expectedLSPSnippet, commitChar); - } - - private async Task VerifyCustomCommitWorkerAsync( - CompletionServiceWithProviders service, - Document document, - CompletionItem completionItem, - string expectedLSPSnippet, - char? commitChar = null) - { - using var workspaceFixture = GetOrCreateWorkspaceFixture(); - - // textview is created lazily, so need to access it before making - // changes to document, so the cursor position is tracked correctly. - var textView = workspaceFixture.Target.CurrentDocument.GetTextView(); - - var commit = await service.GetChangeAsync(document, completionItem, commitChar, CancellationToken.None); - var generatedLSPSnippet = commit.LSPSnippet; - AssertEx.EqualOrDiff(expectedLSPSnippet, generatedLSPSnippet); - } - } -} -*/ From ddd0aabd7399fe54734f9f9c5159a76cb5b716a2 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Wed, 20 Apr 2022 08:53:27 -0700 Subject: [PATCH 18/58] revert change made --- Roslyn.sln | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Roslyn.sln b/Roslyn.sln index 22097a3370260..5eeb5a13f95f0 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -1535,4 +1535,4 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {604E6B91-7BC0-4126-AE07-D4D2FEFC3D29} EndGlobalSection -EndGlobal \ No newline at end of file +EndGlobal From f4f0aea6e543ff5c12ff4ef828e4b00eb53477a8 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Wed, 20 Apr 2022 08:54:21 -0700 Subject: [PATCH 19/58] update --- eng/Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/Versions.props b/eng/Versions.props index e83ca84842361..6766131fdc52a 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -296,4 +296,4 @@ true true - \ No newline at end of file + From 3230e0a84e40ab739340b4cfe51c204ea5f43828 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Wed, 20 Apr 2022 09:25:38 -0700 Subject: [PATCH 20/58] fix random file change --- src/Features/Core/Portable/PublicAPI.Unshipped.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/PublicAPI.Unshipped.txt b/src/Features/Core/Portable/PublicAPI.Unshipped.txt index d603bac57d00f..a3ca61c8d6d98 100644 --- a/src/Features/Core/Portable/PublicAPI.Unshipped.txt +++ b/src/Features/Core/Portable/PublicAPI.Unshipped.txt @@ -1,3 +1,3 @@ *REMOVED*Microsoft.CodeAnalysis.QuickInfo.QuickInfoService.QuickInfoService() -> void *REMOVED*virtual Microsoft.CodeAnalysis.QuickInfo.QuickInfoService.GetQuickInfoAsync(Microsoft.CodeAnalysis.Document document, int position, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task -Microsoft.CodeAnalysis.QuickInfo.QuickInfoService.GetQuickInfoAsync(Microsoft.CodeAnalysis.Document document, int position, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task \ No newline at end of file +Microsoft.CodeAnalysis.QuickInfo.QuickInfoService.GetQuickInfoAsync(Microsoft.CodeAnalysis.Document document, int position, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task From ddfedf3c4f1d85b57c23cfcf3401373d898b01bc Mon Sep 17 00:00:00 2001 From: akhera99 Date: Wed, 20 Apr 2022 09:46:22 -0700 Subject: [PATCH 21/58] simplify --- .../Snippets/AbstractCSharpLSPSnippetTests.cs | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs index c699b4bd1d9bb..b4731d8032648 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs @@ -42,23 +42,7 @@ private async Task VerifyCustomCommitProviderLSPSnippetAsync(Document document, var items = completionList.Items; Assert.Contains(itemToCommit, items.Select(x => x.DisplayText), GetStringComparer()); - var firstItem = items.First(i => CompareItems(i.DisplayText, itemToCommit)); - await VerifyCustomCommitWorkerAsync(service, document, firstItem, expectedLSPSnippet, commitChar); - } - - private async Task VerifyCustomCommitWorkerAsync( - CompletionServiceWithProviders service, - Document document, - CompletionItem completionItem, - string expectedLSPSnippet, - char? commitChar = null) - { - using var workspaceFixture = GetOrCreateWorkspaceFixture(); - - // textview is created lazily, so need to access it before making - // changes to document, so the cursor position is tracked correctly. - var textView = workspaceFixture.Target.CurrentDocument.GetTextView(); - + var completionItem = items.First(i => CompareItems(i.DisplayText, itemToCommit)); var commit = await service.GetChangeAsync(document, completionItem, commitChar, CancellationToken.None); var generatedLSPSnippet = commit.LSPSnippet; AssertEx.EqualOrDiff(expectedLSPSnippet, generatedLSPSnippet); From 29c41a67227ab902f91f1511096fc263098c2895 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Wed, 20 Apr 2022 09:57:10 -0700 Subject: [PATCH 22/58] cleanup/comments --- .../Core/IntelliSense/AsyncCompletion/CommitManager.cs | 2 ++ .../Snippets/SnippetProviders/AbstractIfSnippetProvider.cs | 3 +++ .../Snippets/SnippetProviders/AbstractSnippetProvider.cs | 3 +++ 3 files changed, 8 insertions(+) diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs index 438ccd6ffedae..4fa6b2bf5a59f 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs @@ -243,6 +243,8 @@ private AsyncCompletionData.CommitResult Commit( return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None); } + // Specifically for snippets, we use reflection to try and invoke the LanguageServerSnippetExpander's + // TryExpand method and determine if it succeeded or not. var snippetServiceSucceeded = false; if (SnippetCompletionItem.IsSnippet(roslynItem)) { diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs index c823b19dbd6b3..783cec4e4dc95 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs @@ -21,6 +21,9 @@ namespace Microsoft.CodeAnalysis.Snippets { internal abstract class AbstractIfSnippetProvider : AbstractSnippetProvider { + /// + /// Gets the corresponding pieces of the if statement that will be implemented differently per language. + /// protected abstract void GetPartsOfIfStatement(SyntaxNode node, out SyntaxToken openParen, out SyntaxNode condition, out SyntaxNode statement); public override string SnippetIdentifier => "if"; diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs index bd01962897ff6..92f7298c5035c 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -111,6 +111,9 @@ public async Task GetSnippetAsync(Document document, int position // Gets a listing of the identifiers that need to be found in the snippet TextChange // and their associated TextSpan so they can later be converted into an LSP snippet format. var placeholders = GetRenameLocationsMap(mainChangeNode, syntaxFacts, cancellationToken); + + // All the changes from the original document to the most updated. Will later be + // collpased into one collapsed TextChange. var changesArray = changes.ToImmutableArray(); return new SnippetChange( textChanges: changesArray, From 61ffd0e6fbb4685ebf638a89ac630e53caad107e Mon Sep 17 00:00:00 2001 From: akhera99 Date: Wed, 20 Apr 2022 10:03:29 -0700 Subject: [PATCH 23/58] remove unused attribute --- .../Snippets/AbstractCSharpLSPSnippetTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs index b4731d8032648..04d8d3e6cb476 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs @@ -14,7 +14,6 @@ namespace Microsoft.CodeAnalysis.Test.Utilities.Snippets { - [UseExportProvider] public abstract class AbstractCSharpLSPSnippetTests : AbstractCSharpCompletionProviderTests { protected override async Task VerifyCustomCommitProviderWorkerAsync(string codeBeforeCommit, int position, string itemToCommit, string expectedLSPSnippet, SourceCodeKind sourceCodeKind, char? commitChar = null) From 35ba79b145a6c9166770fea956250c3b201ffc23 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Thu, 21 Apr 2022 11:23:11 -0700 Subject: [PATCH 24/58] pr feedback --- .../AsyncCompletion/CommitManager.cs | 151 +++++++++--------- .../AsyncCompletion/CommitManagerProvider.cs | 9 +- .../Core/Snippets/RoslynLSPSnippetExpander.cs | 58 +++++++ .../CSharpSnippetCompletionProvider.cs | 4 +- .../CSharpConvertToLSPSnippetService.cs | 4 +- .../AbstractSnippetCompletionProvider.cs | 50 +++--- .../Snippets/ConvertToLSPSnippetService.cs | 2 +- .../Snippets/IRoslynLSPSnippetExpander.cs | 16 ++ 8 files changed, 187 insertions(+), 107 deletions(-) create mode 100644 src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs create mode 100644 src/Features/Core/Portable/Snippets/IRoslynLSPSnippetExpander.cs diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs index 4fa6b2bf5a59f..ae2c482c8232d 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs @@ -29,6 +29,7 @@ using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; using Microsoft.CodeAnalysis.Completion.Providers.Snippets; using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.Snippets; namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion { @@ -41,7 +42,7 @@ internal sealed class CommitManager : IAsyncCompletionCommitManager private readonly ITextView _textView; private readonly IGlobalOptionService _globalOptions; private readonly IThreadingContext _threadingContext; - private readonly object _languageServerSnippetExpander; + private readonly RoslynLSPSnippetExpander _roslynLSPSnippetExpander; public IEnumerable PotentialCommitCharacters { @@ -64,13 +65,13 @@ internal CommitManager( RecentItemsManager recentItemsManager, IGlobalOptionService globalOptions, IThreadingContext threadingContext, - object languageServerSnippetExpander) + RoslynLSPSnippetExpander roslynLSPSnippetExpander) { _globalOptions = globalOptions; _threadingContext = threadingContext; _recentItemsManager = recentItemsManager; _textView = textView; - _languageServerSnippetExpander = languageServerSnippetExpander; + _roslynLSPSnippetExpander = roslynLSPSnippetExpander; } /// @@ -245,110 +246,102 @@ private AsyncCompletionData.CommitResult Commit( // Specifically for snippets, we use reflection to try and invoke the LanguageServerSnippetExpander's // TryExpand method and determine if it succeeded or not. - var snippetServiceSucceeded = false; if (SnippetCompletionItem.IsSnippet(roslynItem)) { - var type = _languageServerSnippetExpander.GetType(); - var expandMethod = type.GetMethod("TryExpand"); var lspSnippetText = change.LSPSnippet; - var sourceText = document.GetTextAsync(cancellationToken).WaitAndGetResult(cancellationToken); - var textEdit = new LSP.TextEdit() + if (!document.TryGetText(out var sourceText)) { - Range = ProtocolConversions.TextSpanToRange(change.TextChange.Span, sourceText), - NewText = lspSnippetText! - }; + FatalError.ReportAndCatch(new InvalidOperationException("Document is not loaded and available."), ErrorSeverity.Critical); + return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None); + } - if (expandMethod is not null) + if (!_roslynLSPSnippetExpander.TryExpand(change.TextChange, sourceText, lspSnippetText, _textView, triggerSnapshot)) { - var expandMethodResult = expandMethod.Invoke(_languageServerSnippetExpander, new object[] { textEdit, view, triggerSnapshot }); - snippetServiceSucceeded = expandMethodResult is not null && (bool)expandMethodResult; + FatalError.ReportAndCatch(new InvalidOperationException(""), ErrorSeverity.Critical); } + + return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None); + } - // For all other completions and if the snippet LSP service fails - // we still want to try and insert the snippet sans the placeholders + tab stop behavior. - if (!snippetServiceSucceeded) + var textChange = change.TextChange; + var triggerSnapshotSpan = new SnapshotSpan(triggerSnapshot, textChange.Span.ToSpan()); + var mappedSpan = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); + + using (var edit = subjectBuffer.CreateEdit(EditOptions.DefaultMinimalChange, reiteratedVersionNumber: null, editTag: null)) { + edit.Replace(mappedSpan.Span, change.TextChange.NewText); - var textChange = change.TextChange; - var triggerSnapshotSpan = new SnapshotSpan(triggerSnapshot, textChange.Span.ToSpan()); - var mappedSpan = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); + // edit.Apply() may trigger changes made by extensions. + // updatedCurrentSnapshot will contain changes made by Roslyn but not by other extensions. + var updatedCurrentSnapshot = edit.Apply(); - using (var edit = subjectBuffer.CreateEdit(EditOptions.DefaultMinimalChange, reiteratedVersionNumber: null, editTag: null)) + if (change.NewPosition.HasValue) { - edit.Replace(mappedSpan.Span, change.TextChange.NewText); - - // edit.Apply() may trigger changes made by extensions. - // updatedCurrentSnapshot will contain changes made by Roslyn but not by other extensions. - var updatedCurrentSnapshot = edit.Apply(); - - if (change.NewPosition.HasValue) + // Roslyn knows how to position the caret in the snapshot we just created. + // If there were more edits made by extensions, TryMoveCaretToAndEnsureVisible maps the snapshot point to the most recent one. + view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(updatedCurrentSnapshot, change.NewPosition.Value)); + } + else + { + // Or, If we're doing a minimal change, then the edit that we make to the + // buffer may not make the total text change that places the caret where we + // would expect it to go based on the requested change. In this case, + // determine where the item should go and set the care manually. + + // Note: we only want to move the caret if the caret would have been moved + // by the edit. i.e. if the caret was actually in the mapped span that + // we're replacing. + var caretPositionInBuffer = view.GetCaretPoint(subjectBuffer); + if (caretPositionInBuffer.HasValue && mappedSpan.IntersectsWith(caretPositionInBuffer.Value)) { - // Roslyn knows how to position the caret in the snapshot we just created. - // If there were more edits made by extensions, TryMoveCaretToAndEnsureVisible maps the snapshot point to the most recent one. - view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(updatedCurrentSnapshot, change.NewPosition.Value)); + view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(subjectBuffer.CurrentSnapshot, mappedSpan.Start.Position + textChange.NewText?.Length ?? 0)); } else { - // Or, If we're doing a minimal change, then the edit that we make to the - // buffer may not make the total text change that places the caret where we - // would expect it to go based on the requested change. In this case, - // determine where the item should go and set the care manually. - - // Note: we only want to move the caret if the caret would have been moved - // by the edit. i.e. if the caret was actually in the mapped span that - // we're replacing. - var caretPositionInBuffer = view.GetCaretPoint(subjectBuffer); - if (caretPositionInBuffer.HasValue && mappedSpan.IntersectsWith(caretPositionInBuffer.Value)) - { - view.TryMoveCaretToAndEnsureVisible(new SnapshotPoint(subjectBuffer.CurrentSnapshot, mappedSpan.Start.Position + textChange.NewText?.Length ?? 0)); - } - else - { - view.Caret.EnsureVisible(); - } + view.Caret.EnsureVisible(); } + } - includesCommitCharacter = change.IncludesCommitCharacter; + includesCommitCharacter = change.IncludesCommitCharacter; - if (roslynItem.Rules.FormatOnCommit) - { - // The edit updates the snapshot however other extensions may make changes there. - // Therefore, it is required to use subjectBuffer.CurrentSnapshot for further calculations rather than the updated current snapshot defined above. - var currentDocument = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); - var formattingService = currentDocument?.GetRequiredLanguageService(); + if (roslynItem.Rules.FormatOnCommit) + { + // The edit updates the snapshot however other extensions may make changes there. + // Therefore, it is required to use subjectBuffer.CurrentSnapshot for further calculations rather than the updated current snapshot defined above. + var currentDocument = subjectBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + var formattingService = currentDocument?.GetRequiredLanguageService(); - if (currentDocument != null && formattingService != null) - { - var spanToFormat = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); - var changes = formattingService.GetFormattingChangesAsync( - currentDocument, spanToFormat.Span.ToTextSpan(), cancellationToken).WaitAndGetResult(cancellationToken); - currentDocument.Project.Solution.Workspace.ApplyTextChanges(currentDocument.Id, changes, cancellationToken); - } + if (currentDocument != null && formattingService != null) + { + var spanToFormat = triggerSnapshotSpan.TranslateTo(subjectBuffer.CurrentSnapshot, SpanTrackingMode.EdgeInclusive); + var changes = formattingService.GetFormattingChangesAsync( + currentDocument, spanToFormat.Span.ToTextSpan(), cancellationToken).WaitAndGetResult(cancellationToken); + currentDocument.Project.Solution.Workspace.ApplyTextChanges(currentDocument.Id, changes, cancellationToken); } } + } - _recentItemsManager.MakeMostRecentItem(roslynItem.FilterText); + _recentItemsManager.MakeMostRecentItem(roslynItem.FilterText); - if (provider is INotifyCommittingItemCompletionProvider notifyProvider) + if (provider is INotifyCommittingItemCompletionProvider notifyProvider) + { + _ = _threadingContext.JoinableTaskFactory.RunAsync(async () => { - _ = _threadingContext.JoinableTaskFactory.RunAsync(async () => - { - // Make sure the notification isn't sent on UI thread. - await TaskScheduler.Default; - _ = notifyProvider.NotifyCommittingItemAsync(document, roslynItem, commitCharacter, cancellationToken).ReportNonFatalErrorAsync(); - }); - } + // Make sure the notification isn't sent on UI thread. + await TaskScheduler.Default; + _ = notifyProvider.NotifyCommittingItemAsync(document, roslynItem, commitCharacter, cancellationToken).ReportNonFatalErrorAsync(); + }); + } - if (includesCommitCharacter) - { - return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.SuppressFurtherTypeCharCommandHandlers); - } + if (includesCommitCharacter) + { + return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.SuppressFurtherTypeCharCommandHandlers); + } - if (commitCharacter == '\n' && SendEnterThroughToEditor(rules, roslynItem, filterText)) - { - return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.RaiseFurtherReturnKeyAndTabKeyCommandHandlers); - } + if (commitCharacter == '\n' && SendEnterThroughToEditor(rules, roslynItem, filterText)) + { + return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.RaiseFurtherReturnKeyAndTabKeyCommandHandlers); } return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None); diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManagerProvider.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManagerProvider.cs index d380b51132c51..317c19d9953ea 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManagerProvider.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManagerProvider.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Options; +using Microsoft.CodeAnalysis.Snippets; using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; @@ -22,7 +23,7 @@ internal class CommitManagerProvider : IAsyncCompletionCommitManagerProvider private readonly IThreadingContext _threadingContext; private readonly RecentItemsManager _recentItemsManager; private readonly IGlobalOptionService _globalOptions; - private readonly object _languageServerSnippetExpander; + private readonly RoslynLSPSnippetExpander _roslynLSPSnippetExpander; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] @@ -30,12 +31,12 @@ public CommitManagerProvider( IThreadingContext threadingContext, RecentItemsManager recentItemsManager, IGlobalOptionService globalOptions, - [Import("Microsoft.VisualStudio.LanguageServer.Client.Snippets.LanguageServerSnippetExpander")] object languageServerSnippetExpander) + RoslynLSPSnippetExpander roslynLSPSnippetExpander) { _threadingContext = threadingContext; _recentItemsManager = recentItemsManager; _globalOptions = globalOptions; - _languageServerSnippetExpander = languageServerSnippetExpander; + _roslynLSPSnippetExpander = roslynLSPSnippetExpander; } IAsyncCompletionCommitManager? IAsyncCompletionCommitManagerProvider.GetOrCreate(ITextView textView) @@ -45,7 +46,7 @@ public CommitManagerProvider( return null; } - return new CommitManager(textView, _recentItemsManager, _globalOptions, _threadingContext, _languageServerSnippetExpander); + return new CommitManager(textView, _recentItemsManager, _globalOptions, _threadingContext, _roslynLSPSnippetExpander); } } } diff --git a/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs b/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs new file mode 100644 index 0000000000000..62bb7bce9eaad --- /dev/null +++ b/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs @@ -0,0 +1,58 @@ +// 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; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Reflection; +using System.Text; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.LanguageServer; +using Microsoft.CodeAnalysis.Text; +using Microsoft.VisualStudio.LanguageServer.Protocol; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.CodeAnalysis.Snippets +{ + [Export(typeof(IRoslynLSPSnippetExpander))] + [Export(typeof(RoslynLSPSnippetExpander))] + internal class RoslynLSPSnippetExpander : IRoslynLSPSnippetExpander + { + protected object _lspSnippetExpander; + protected Type _expanderType; + protected MethodInfo? _expanderMethodInfo; + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public RoslynLSPSnippetExpander([Import("Microsoft.VisualStudio.LanguageServer.Client.Snippets.LanguageServerSnippetExpander")] object languageServerSnippetExpander) + { + _lspSnippetExpander = languageServerSnippetExpander; + _expanderType = languageServerSnippetExpander.GetType(); + _expanderMethodInfo = _expanderType.GetMethod("TryExpand"); + } + + public bool TryExpand(TextChange textChange, SourceText sourceText, string? lspSnippetText, ITextView textView, ITextSnapshot textSnapshot) + { + if (_expanderMethodInfo is null) + { + return false; + } + + var textEdit = new TextEdit() + { + Range = ProtocolConversions.TextSpanToRange(textChange.Span, sourceText), + NewText = lspSnippetText! + }; + + var expandMethodResult = _expanderMethodInfo.Invoke(_lspSnippetExpander, new object[] { textEdit, textView, textSnapshot }); + return expandMethodResult is not null && (bool)expandMethodResult; + } + + public bool CanExpandSnippet() + { + return _expanderMethodInfo is not null; + } + } +} diff --git a/src/Features/CSharp/Portable/Completion/CompletionProviders/Snippets/CSharpSnippetCompletionProvider.cs b/src/Features/CSharp/Portable/Completion/CompletionProviders/Snippets/CSharpSnippetCompletionProvider.cs index 0212dd8fe8c1f..000e66025c4d7 100644 --- a/src/Features/CSharp/Portable/Completion/CompletionProviders/Snippets/CSharpSnippetCompletionProvider.cs +++ b/src/Features/CSharp/Portable/Completion/CompletionProviders/Snippets/CSharpSnippetCompletionProvider.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.Completion.Providers.Snippets; using Microsoft.CodeAnalysis.CSharp.Completion.Providers; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Snippets; namespace Microsoft.CodeAnalysis.CSharp.Completion.CompletionProviders.Snippets { @@ -18,7 +19,8 @@ internal class CSharpSnippetCompletionProvider : AbstractSnippetCompletionProvid { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpSnippetCompletionProvider() + public CSharpSnippetCompletionProvider(IRoslynLSPSnippetExpander roslynLSPSnippetExpander) + : base(roslynLSPSnippetExpander) { } } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpConvertToLSPSnippetService.cs b/src/Features/CSharp/Portable/Snippets/CSharpConvertToLSPSnippetService.cs index 5700d25e6379e..ae3a16587c228 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpConvertToLSPSnippetService.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpConvertToLSPSnippetService.cs @@ -11,8 +11,8 @@ namespace Microsoft.CodeAnalysis.CSharp.Snippets { - [ExportLanguageService(typeof(ConvertToLSPSnippetService), LanguageNames.CSharp), Shared] - internal class CSharpConvertToLSPSnippetService : ConvertToLSPSnippetService + [ExportLanguageService(typeof(AbstractConvertToLSPSnippetService), LanguageNames.CSharp), Shared] + internal class CSharpConvertToLSPSnippetService : AbstractConvertToLSPSnippetService { [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs index 3bbbd48058b9b..8a005005891f4 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs @@ -15,6 +15,13 @@ namespace Microsoft.CodeAnalysis.Completion.Providers.Snippets { internal abstract class AbstractSnippetCompletionProvider : CompletionProvider { + private readonly IRoslynLSPSnippetExpander _roslynLSPSnippetExpander; + + public AbstractSnippetCompletionProvider(IRoslynLSPSnippetExpander roslynLSPSnippetExpander) + { + _roslynLSPSnippetExpander = roslynLSPSnippetExpander; + } + public override async Task GetChangeAsync(Document document, CompletionItem item, char? commitKey = null, CancellationToken cancellationToken = default) { // This retrieves the document without the text used to invoke completion @@ -37,36 +44,39 @@ public override async Task GetChangeAsync(Document document, C var allTextChanges = await allChangesDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); var change = Utilities.Collapse(allChangesText, allTextChanges.AsImmutable()); - var lspSnippetService = allChangesDocument.GetRequiredLanguageService(); + var lspSnippetService = allChangesDocument.GetRequiredLanguageService(); var lspSnippet = lspSnippetService.GenerateLSPSnippet(change, snippet.Placeholders); return CompletionChange.Create(change, allTextChanges.AsImmutable(), lspSnippet, newPosition: snippet.CursorPosition, includesCommitCharacter: true); } public override async Task ProvideCompletionsAsync(CompletionContext context) { - var document = context.Document; - var cancellationToken = context.CancellationToken; - var position = context.Position; - var service = document.GetLanguageService(); - - if (service == null) + if (_roslynLSPSnippetExpander.CanExpandSnippet()) { - return; - } + var document = context.Document; + var cancellationToken = context.CancellationToken; + var position = context.Position; + var service = document.GetLanguageService(); - var (strippedDocument, newPosition) = await GetDocumentWithoutInvokingTextAsync(document, position, cancellationToken).ConfigureAwait(false); + if (service == null) + { + return; + } - var snippets = await service.GetSnippetsAsync(strippedDocument, newPosition, cancellationToken).ConfigureAwait(false); + var (strippedDocument, newPosition) = await GetDocumentWithoutInvokingTextAsync(document, position, cancellationToken).ConfigureAwait(false); - foreach (var snippetData in snippets) - { - var completionItem = SnippetCompletionItem.Create( - displayText: snippetData.DisplayName, - displayTextSuffix: "", - position: position, - snippetIdentifier: snippetData.SnippetIdentifier, - glyph: Glyph.Snippet); - context.AddItem(completionItem); + var snippets = await service.GetSnippetsAsync(strippedDocument, newPosition, cancellationToken).ConfigureAwait(false); + + foreach (var snippetData in snippets) + { + var completionItem = SnippetCompletionItem.Create( + displayText: snippetData.DisplayName, + displayTextSuffix: "", + position: position, + snippetIdentifier: snippetData.SnippetIdentifier, + glyph: Glyph.Snippet); + context.AddItem(completionItem); + } } } diff --git a/src/Features/Core/Portable/Snippets/ConvertToLSPSnippetService.cs b/src/Features/Core/Portable/Snippets/ConvertToLSPSnippetService.cs index 6a5da4a275f1d..327e7be9128f0 100644 --- a/src/Features/Core/Portable/Snippets/ConvertToLSPSnippetService.cs +++ b/src/Features/Core/Portable/Snippets/ConvertToLSPSnippetService.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.Snippets { - internal class ConvertToLSPSnippetService : ILanguageService + internal class AbstractConvertToLSPSnippetService : ILanguageService { public string? GenerateLSPSnippet(TextChange textChange, List<(string, List)> placeholders) { diff --git a/src/Features/Core/Portable/Snippets/IRoslynLSPSnippetExpander.cs b/src/Features/Core/Portable/Snippets/IRoslynLSPSnippetExpander.cs new file mode 100644 index 0000000000000..7b0763207e898 --- /dev/null +++ b/src/Features/Core/Portable/Snippets/IRoslynLSPSnippetExpander.cs @@ -0,0 +1,16 @@ +// 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; +using System.Collections.Generic; +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Snippets +{ + internal interface IRoslynLSPSnippetExpander + { + bool CanExpandSnippet(); + } +} From 6588ac44f8081665b71705426297b35f5746e72e Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 25 Apr 2022 11:51:09 -0700 Subject: [PATCH 25/58] pr feedback, need to fix tests --- .../Snippets/AbstractCSharpLSPSnippetTests.cs | 2 +- .../AsyncCompletion/CommitManager.cs | 3 +- .../CSharpConvertToLSPSnippetService.cs | 23 ----- .../Portable/Completion/CompletionChange.cs | 22 ++--- .../AbstractSnippetCompletionProvider.cs | 87 ++++++++++++++++++- .../Core/Portable/FeaturesResources.resx | 2 +- .../Snippets/ConvertToLSPSnippetService.cs | 37 -------- .../Core/Portable/Snippets/ISnippetService.cs | 2 - .../Portable/Snippets/RoslynLSPSnippetItem.cs | 28 ++++++ .../Core/Portable/Snippets/SnippetChange.cs | 6 +- .../AbstractConsoleSnippetProvider.cs | 4 +- .../AbstractIfSnippetProvider.cs | 33 ++----- .../AbstractSnippetProvider.cs | 18 ++-- .../Portable/xlf/FeaturesResources.cs.xlf | 4 +- .../Portable/xlf/FeaturesResources.de.xlf | 4 +- .../Portable/xlf/FeaturesResources.es.xlf | 4 +- .../Portable/xlf/FeaturesResources.fr.xlf | 4 +- .../Portable/xlf/FeaturesResources.it.xlf | 4 +- .../Portable/xlf/FeaturesResources.ja.xlf | 4 +- .../Portable/xlf/FeaturesResources.ko.xlf | 4 +- .../Portable/xlf/FeaturesResources.pl.xlf | 4 +- .../Portable/xlf/FeaturesResources.pt-BR.xlf | 4 +- .../Portable/xlf/FeaturesResources.ru.xlf | 4 +- .../Portable/xlf/FeaturesResources.tr.xlf | 4 +- .../xlf/FeaturesResources.zh-Hans.xlf | 4 +- .../xlf/FeaturesResources.zh-Hant.xlf | 4 +- 26 files changed, 172 insertions(+), 147 deletions(-) delete mode 100644 src/Features/CSharp/Portable/Snippets/CSharpConvertToLSPSnippetService.cs delete mode 100644 src/Features/Core/Portable/Snippets/ConvertToLSPSnippetService.cs create mode 100644 src/Features/Core/Portable/Snippets/RoslynLSPSnippetItem.cs diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs index 04d8d3e6cb476..6b79935d13c66 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs @@ -43,7 +43,7 @@ private async Task VerifyCustomCommitProviderLSPSnippetAsync(Document document, Assert.Contains(itemToCommit, items.Select(x => x.DisplayText), GetStringComparer()); var completionItem = items.First(i => CompareItems(i.DisplayText, itemToCommit)); var commit = await service.GetChangeAsync(document, completionItem, commitChar, CancellationToken.None); - var generatedLSPSnippet = commit.LSPSnippet; + commit.Properties.TryGetValue("LSPSnippet", out var generatedLSPSnippet); AssertEx.EqualOrDiff(expectedLSPSnippet, generatedLSPSnippet); } } diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs index ae2c482c8232d..52994c477697a 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs @@ -248,7 +248,8 @@ private AsyncCompletionData.CommitResult Commit( // TryExpand method and determine if it succeeded or not. if (SnippetCompletionItem.IsSnippet(roslynItem)) { - var lspSnippetText = change.LSPSnippet; + change.Properties!.TryGetValue("LSPSnippet", out var lspSnippetText); + if (!document.TryGetText(out var sourceText)) { FatalError.ReportAndCatch(new InvalidOperationException("Document is not loaded and available."), ErrorSeverity.Critical); diff --git a/src/Features/CSharp/Portable/Snippets/CSharpConvertToLSPSnippetService.cs b/src/Features/CSharp/Portable/Snippets/CSharpConvertToLSPSnippetService.cs deleted file mode 100644 index ae3a16587c228..0000000000000 --- a/src/Features/CSharp/Portable/Snippets/CSharpConvertToLSPSnippetService.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Composition; -using System.Text; -using Microsoft.CodeAnalysis.Host.Mef; -using Microsoft.CodeAnalysis.Snippets; - -namespace Microsoft.CodeAnalysis.CSharp.Snippets -{ - [ExportLanguageService(typeof(AbstractConvertToLSPSnippetService), LanguageNames.CSharp), Shared] - internal class CSharpConvertToLSPSnippetService : AbstractConvertToLSPSnippetService - { - [ImportingConstructor] - [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public CSharpConvertToLSPSnippetService() - { - } - } -} diff --git a/src/Features/Core/Portable/Completion/CompletionChange.cs b/src/Features/Core/Portable/Completion/CompletionChange.cs index 7d76fe599109f..81a84ee01ec20 100644 --- a/src/Features/Core/Portable/Completion/CompletionChange.cs +++ b/src/Features/Core/Portable/Completion/CompletionChange.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.Text; @@ -40,21 +41,16 @@ public sealed class CompletionChange /// public bool IncludesCommitCharacter { get; } - internal string? LSPSnippet { get; } + internal ImmutableDictionary? Properties { get; } private CompletionChange( TextChange textChange, ImmutableArray textChanges, int? newPosition, bool includesCommitCharacter) + : this(textChange, textChanges, newPosition, includesCommitCharacter, null) { - TextChange = textChange; - NewPosition = newPosition; - IncludesCommitCharacter = includesCommitCharacter; - TextChanges = textChanges.NullToEmpty(); - if (TextChanges.IsEmpty) - TextChanges = ImmutableArray.Create(textChange); } private CompletionChange( - TextChange textChange, ImmutableArray textChanges, int? newPosition, bool includesCommitCharacter, string? lspSnippet) + TextChange textChange, ImmutableArray textChanges, int? newPosition, bool includesCommitCharacter, ImmutableDictionary? properties) { TextChange = textChange; NewPosition = newPosition; @@ -62,7 +58,7 @@ private CompletionChange( TextChanges = textChanges.NullToEmpty(); if (TextChanges.IsEmpty) TextChanges = ImmutableArray.Create(textChange); - LSPSnippet = lspSnippet; + Properties = properties; } /// @@ -114,11 +110,11 @@ public static CompletionChange Create( internal static CompletionChange Create( TextChange textChange, ImmutableArray textChanges, - string? lspSnippet, - int? newPosition = null, - bool includesCommitCharacter = false) + ImmutableDictionary? properties, + int? newPosition, + bool includesCommitCharacter) { - return new CompletionChange(textChange, textChanges, newPosition, includesCommitCharacter, lspSnippet); + return new CompletionChange(textChange, textChanges, newPosition, includesCommitCharacter, properties); } /// diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs index 8a005005891f4..d21b13eb8709a 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs @@ -4,12 +4,15 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.ConvertToInterpolatedString; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Snippets; using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Completion.Providers.Snippets { @@ -44,9 +47,87 @@ public override async Task GetChangeAsync(Document document, C var allTextChanges = await allChangesDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); var change = Utilities.Collapse(allChangesText, allTextChanges.AsImmutable()); - var lspSnippetService = allChangesDocument.GetRequiredLanguageService(); - var lspSnippet = lspSnippetService.GenerateLSPSnippet(change, snippet.Placeholders); - return CompletionChange.Create(change, allTextChanges.AsImmutable(), lspSnippet, newPosition: snippet.CursorPosition, includesCommitCharacter: true); + var finalTextChange = ExtendSnippetTextChange(change, snippet.Placeholders); + var lspSnippet = GenerateLSPSnippet(finalTextChange, snippet.Placeholders); + var props = ImmutableDictionary.Empty + .Add("LSPSnippet", lspSnippet!); + + return CompletionChange.Create(change, allTextChanges.AsImmutable(), properties: props, snippet.CursorPosition, includesCommitCharacter: true); + } + + private static string? GenerateLSPSnippet(TextChange textChange, ImmutableArray placeholders) + { + var textChangeStart = textChange.Span.Start; + var textChangeText = textChange.NewText!; + var lspSnippetString = ""; + + for (var i = 0; i < textChangeText.Length;) + { + var (str, strCount) = GetStringInPosition(placeholders, i, textChangeStart); + if (str.IsEmpty()) + { + lspSnippetString += textChangeText[i]; + i++; + } + else + { + lspSnippetString += str; + i += strCount; + + if (strCount == 0) + { + lspSnippetString += textChangeText[i]; + i++; + } + } + } + + return lspSnippetString; + } + + private static (string, int) GetStringInPosition(ImmutableArray placeholders, int position, int textChangeStart) + { + foreach (var placeholder in placeholders) + { + if (placeholder.CaretPosition.HasValue && placeholder.CaretPosition.Value - textChangeStart == position) + { + return ("$0", 0); + } + + foreach (var span in placeholder.PlaceHolderSpans) + { + if (span.Start - textChangeStart == position) + { + return ($"${{{placeholder.Priority}:{placeholder.Identifier}}}", placeholder.Identifier!.Length); + } + } + } + + return (string.Empty, 0); + + } + + private static TextChange ExtendSnippetTextChange(TextChange textChange, ImmutableArray lspSnippetItems) + { + var newTextChange = textChange; + foreach (var lspSnippetItem in lspSnippetItems) + { + foreach (var placeholder in lspSnippetItem.PlaceHolderSpans) + { + if (newTextChange.Span.Start > placeholder.Start) + { + newTextChange = new TextChange(new TextSpan(placeholder.Start, 0), textChange.NewText); + } + } + + if (lspSnippetItem.CaretPosition is not null && textChange.Span.Start > lspSnippetItem.CaretPosition) + { + newTextChange = new TextChange(new TextSpan(lspSnippetItem.CaretPosition.Value, 0), textChange.NewText); + } + + } + + return newTextChange; } public override async Task ProvideCompletionsAsync(CompletionContext context) diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 1d4ce682a5896..18207390150d4 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -3133,6 +3133,6 @@ Zero-width positive lookbehind assertions are typically used at the beginning of Sort Imports or usings - Insert an if statement + Insert an 'if' statement \ No newline at end of file diff --git a/src/Features/Core/Portable/Snippets/ConvertToLSPSnippetService.cs b/src/Features/Core/Portable/Snippets/ConvertToLSPSnippetService.cs deleted file mode 100644 index 327e7be9128f0..0000000000000 --- a/src/Features/Core/Portable/Snippets/ConvertToLSPSnippetService.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Text; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.CodeAnalysis.Snippets -{ - internal class AbstractConvertToLSPSnippetService : ILanguageService - { - public string? GenerateLSPSnippet(TextChange textChange, List<(string, List)> placeholders) - { - var textChangeText = textChange.NewText!; - - for (var i = 0; i < placeholders.Count; i++) - { - var (identifier, placeholderList) = placeholders[i]; - if (identifier.Length != 0) - { - var newStr = $"${{{i}:{identifier}}}"; - textChangeText = textChangeText.Replace(identifier, newStr); - } - else - { - var location = placeholderList[0]; - textChangeText = textChangeText.Insert(location.Start - textChange.Span.Start, $"$0"); - } - } - - return textChangeText; - } - } -} diff --git a/src/Features/Core/Portable/Snippets/ISnippetService.cs b/src/Features/Core/Portable/Snippets/ISnippetService.cs index 3d6f3049f1945..f005325da09da 100644 --- a/src/Features/Core/Portable/Snippets/ISnippetService.cs +++ b/src/Features/Core/Portable/Snippets/ISnippetService.cs @@ -26,7 +26,5 @@ internal interface ISnippetService : ILanguageService /// Called upon by the AbstractSnippetCompletionProvider /// ISnippetProvider GetSnippetProvider(string snippetIdentifier); - - } } diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetItem.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetItem.cs new file mode 100644 index 0000000000000..75d137625036c --- /dev/null +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetItem.cs @@ -0,0 +1,28 @@ +// 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; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Text; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.Snippets +{ + internal readonly struct RoslynLSPSnippetItem + { + public readonly string? Identifier; + public readonly int Priority; + public readonly int? CaretPosition; + public readonly ImmutableArray PlaceHolderSpans; + + public RoslynLSPSnippetItem(string? identifier, int priority, int? caretPosition, ImmutableArray placeholderSpans) + { + Identifier = identifier; + Priority = priority; + CaretPosition = caretPosition; + PlaceHolderSpans = placeholderSpans; + } + } +} diff --git a/src/Features/Core/Portable/Snippets/SnippetChange.cs b/src/Features/Core/Portable/Snippets/SnippetChange.cs index f08e0182a9f15..b478590aed6b8 100644 --- a/src/Features/Core/Portable/Snippets/SnippetChange.cs +++ b/src/Features/Core/Portable/Snippets/SnippetChange.cs @@ -28,16 +28,16 @@ internal readonly struct SnippetChange /// The items that we will want to rename as well as the ordering /// in which to visit those items. /// - public readonly List<(string, List)> Placeholders; + public readonly ImmutableArray Placeholders; public SnippetChange( ImmutableArray textChanges, int? cursorPosition, - List<(string, List)> placeholders) + ImmutableArray placeholders) { if (textChanges.IsEmpty) { - throw new ArgumentException($"{ textChanges.Length } must not be empty"); + throw new ArgumentException($"textChanges must not be empty"); } TextChanges = textChanges; diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs index dcd515b21f9d2..c7f3bac7bb42e 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs @@ -102,7 +102,7 @@ protected override async Task AnnotateNodesToReformatAsync(Document return root.ReplaceNode(snippetExpressionNode, reformatSnippetNode); } - protected override List<(string, List)> GetRenameLocationsMap(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + /*protected override List<(string, List)> GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { var renameLocationsMap = new List<(string, List)>(); var openParenToken = GetOpenParenToken(node, syntaxFacts); @@ -120,7 +120,7 @@ protected override async Task AnnotateNodesToReformatAsync(Document renameLocationsMap.Add(("", list1)); return renameLocationsMap; - } + }*/ private static SyntaxToken? GetOpenParenToken(SyntaxNode node, ISyntaxFacts syntaxFacts) { diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs index 783cec4e4dc95..fe84c96145d7d 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs @@ -12,6 +12,7 @@ using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Extensions.ContextQuery; using Microsoft.CodeAnalysis.Simplification; @@ -72,44 +73,28 @@ protected override async Task AnnotateNodesToReformatAsync(Document return root.ReplaceNode(snippetExpressionNode, reformatSnippetNode); } - protected override List<(string, List)> GetRenameLocationsMap(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { - var renameLocationsMap = new List<(string, List)>(); + using var pooledDisposer = ArrayBuilder.GetInstance(out var arrayBuilder); GetPartsOfIfStatement(node, out _, out var condition, out var statement); - var list1 = new List - { - // Need to add 1 to the span start because we are retrieving a block and want the cursor to appear inside the block. - new TextSpan(statement.SpanStart + 1, statement.Span.Length) - }; - - renameLocationsMap.Add(("", list1)); + arrayBuilder.Add(new RoslynLSPSnippetItem(identifier: null, 0, statement.SpanStart + 1, ImmutableArray.Empty)); var list2 = new List { - new TextSpan(condition.SpanStart, condition.Span.Length) + new TextSpan(condition.SpanStart, 0) }; - renameLocationsMap.Add((condition.ToString(), list2)); + arrayBuilder.Add(new RoslynLSPSnippetItem(condition.ToString(), 1, caretPosition: null, list2.ToImmutableArray())); - return renameLocationsMap; + return arrayBuilder.ToImmutableArray(); } protected override SyntaxNode? FindAddedSnippetSyntaxNode(SyntaxNode root, int position, ISyntaxFacts syntaxFacts) { - var closestNode = root.FindNode(TextSpan.FromBounds(position, position)); - - SyntaxNode? statementOfGlobalStatement = null; - if (syntaxFacts.IsGlobalStatement(closestNode)) - { - statementOfGlobalStatement = syntaxFacts.GetStatementOfGlobalStatement(closestNode); - } + var closestNode = root.FindNode(TextSpan.FromBounds(position, position), getInnermostNodeForTie: true); - var nearestStatement = statementOfGlobalStatement is not null - ? syntaxFacts.IsIfStatement(statementOfGlobalStatement) - ? statementOfGlobalStatement - : null - : closestNode.DescendantNodesAndSelf(syntaxFacts.IsIfStatement).FirstOrDefault(); + var nearestStatement = closestNode.DescendantNodesAndSelf(syntaxFacts.IsIfStatement).FirstOrDefault(); if (nearestStatement is null) { diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs index 92f7298c5035c..2a52d28b69489 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -110,7 +110,7 @@ public async Task GetSnippetAsync(Document document, int position // Gets a listing of the identifiers that need to be found in the snippet TextChange // and their associated TextSpan so they can later be converted into an LSP snippet format. - var placeholders = GetRenameLocationsMap(mainChangeNode, syntaxFacts, cancellationToken); + var placeholders = GetPlaceHolderLocationsList(mainChangeNode, syntaxFacts, cancellationToken); // All the changes from the original document to the most updated. Will later be // collpased into one collapsed TextChange. @@ -131,16 +131,12 @@ public async Task GetSnippetAsync(Document document, int position return null; } - var nodeWithTrivia = node.ReplaceTokens(node.DescendantTokens(descendIntoTrivia: true), AddAnnotations); + var nodeWithTrivia = node.ReplaceTokens(node.DescendantTokens(descendIntoTrivia: true), + (oldToken, _) => oldToken.WithAdditionalAnnotations(SyntaxAnnotation.ElasticAnnotation) + .WithAppendedTrailingTrivia(syntaxFacts.ElasticMarker) + .WithPrependedLeadingTrivia(syntaxFacts.ElasticMarker)); return nodeWithTrivia; - - SyntaxToken AddAnnotations(SyntaxToken oldToken, SyntaxToken newToken) - { - return oldToken.WithAdditionalAnnotations(SyntaxAnnotation.ElasticAnnotation) - .WithAppendedTrailingTrivia(syntaxFacts.ElasticMarker) - .WithPrependedLeadingTrivia(syntaxFacts.ElasticMarker); - } } private async Task CleanupDocumentAsync( @@ -207,9 +203,9 @@ private async Task AddFormatAnnotationAsync(Document document, int pos return document; } - protected virtual List<(string, List)> GetRenameLocationsMap(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + protected virtual ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { - return new List<(string, List)>(); + return ImmutableArray.Empty; } } } diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index a783ce1ec0181..c1561057b1731 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -936,8 +936,8 @@ Ujistěte se, že specifikátor tt použijete pro jazyky, pro které je nezbytn - Insert an if statement - Insert an if statement + Insert an 'if' statement + Insert an 'if' statement diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index bcc4edf0e5c81..43cd6fe4e1833 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -936,8 +936,8 @@ Stellen Sie sicher, dass Sie den Bezeichner "tt" für Sprachen verwenden, für d - Insert an if statement - Insert an if statement + Insert an 'if' statement + Insert an 'if' statement diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index ac4139294340a..4b4518a222447 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -936,8 +936,8 @@ Asegúrese de usar el especificador "tt" para los idiomas para los que es necesa - Insert an if statement - Insert an if statement + Insert an 'if' statement + Insert an 'if' statement diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index 486f12bdf415d..a58ce13e7084f 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -936,8 +936,8 @@ Veillez à utiliser le spécificateur "tt" pour les langues où il est nécessai - Insert an if statement - Insert an if statement + Insert an 'if' statement + Insert an 'if' statement diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index 15335bbea5f94..eab5a7a53a38a 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -936,8 +936,8 @@ Assicurarsi di usare l'identificatore "tt" per le lingue per le quali è necessa - Insert an if statement - Insert an if statement + Insert an 'if' statement + Insert an 'if' statement diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 9c9f5aebea59a..fd9cfde43eec9 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -936,8 +936,8 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma - Insert an if statement - Insert an if statement + Insert an 'if' statement + Insert an 'if' statement diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 1074611d78692..818c70b3d57d6 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -936,8 +936,8 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma - Insert an if statement - Insert an if statement + Insert an 'if' statement + Insert an 'if' statement diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 69c86984a3703..502b637d988f1 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -936,8 +936,8 @@ Pamiętaj, aby nie używać specyfikatora „tt” dla wszystkich języków, w k - Insert an if statement - Insert an if statement + Insert an 'if' statement + Insert an 'if' statement diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index e0bfd34cd1712..438b99fa9f48d 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -936,8 +936,8 @@ Verifique se o especificador "tt" foi usado para idiomas para os quais é necess - Insert an if statement - Insert an if statement + Insert an 'if' statement + Insert an 'if' statement diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index a30e8835fdd76..c5015824176b4 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -936,8 +936,8 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma - Insert an if statement - Insert an if statement + Insert an 'if' statement + Insert an 'if' statement diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 89a38bcf542ef..2aaef5799b918 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -936,8 +936,8 @@ AM ve PM arasındaki farkın korunmasının gerekli olduğu diller için "tt" be - Insert an if statement - Insert an if statement + Insert an 'if' statement + Insert an 'if' statement diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index 498d534fa357c..d11903920d600 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -936,8 +936,8 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma - Insert an if statement - Insert an if statement + Insert an 'if' statement + Insert an 'if' statement diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index 4244d59b27fcf..8b8e1d8dbf1d9 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -936,8 +936,8 @@ Make sure to use the "tt" specifier for languages for which it's necessary to ma - Insert an if statement - Insert an if statement + Insert an 'if' statement + Insert an 'if' statement From b09573df21d71f5cca9abfd04c73593b7c9168ef Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 25 Apr 2022 18:14:10 -0700 Subject: [PATCH 26/58] fix tests --- .../CSharpIfSnippetCompletionProviderTests.cs | 8 +++++ ...TestRoslynLanguageServerSnippetExpander.cs | 29 +++++++++++++++++++ .../Core/Snippets/RoslynLSPSnippetExpander.cs | 15 ++++++---- 3 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/TestRoslynLanguageServerSnippetExpander.cs diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs index c5fe54fd9e17f..b9ad4f2afadc6 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs @@ -4,10 +4,13 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Text; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.CSharp.Completion.CompletionProviders.Snippets; +using Microsoft.CodeAnalysis.Snippets; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; using Xunit; @@ -18,6 +21,11 @@ public class CSharpIfSnippetCompletionProviderTests : AbstractCSharpCompletionPr { private static readonly string s_itemToCommit = FeaturesResources.Insert_an_if_statement; + protected override TestComposition GetComposition() + => base.GetComposition() + .AddExcludedPartTypes(typeof(IRoslynLSPSnippetExpander)) + .AddParts(typeof(TestRoslynLanguageServerSnippetExpander)); + internal override Type GetCompletionProviderType() => typeof(CSharpSnippetCompletionProvider); diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/TestRoslynLanguageServerSnippetExpander.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/TestRoslynLanguageServerSnippetExpander.cs new file mode 100644 index 0000000000000..8b687e0331897 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/TestRoslynLanguageServerSnippetExpander.cs @@ -0,0 +1,29 @@ +// 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; +using System.Collections.Generic; +using System.Composition; +using System.Text; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Snippets; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders.Snippets +{ + [Export(typeof(IRoslynLSPSnippetExpander))] + [Shared] + internal class TestRoslynLanguageServerSnippetExpander : IRoslynLSPSnippetExpander + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public TestRoslynLanguageServerSnippetExpander() + { + } + + public bool CanExpandSnippet() + { + return true; + } + } +} diff --git a/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs b/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs index 62bb7bce9eaad..7980d522e845a 100644 --- a/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs +++ b/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs @@ -20,17 +20,22 @@ namespace Microsoft.CodeAnalysis.Snippets [Export(typeof(RoslynLSPSnippetExpander))] internal class RoslynLSPSnippetExpander : IRoslynLSPSnippetExpander { - protected object _lspSnippetExpander; - protected Type _expanderType; + protected object? _lspSnippetExpander; + protected Type? _expanderType; protected MethodInfo? _expanderMethodInfo; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public RoslynLSPSnippetExpander([Import("Microsoft.VisualStudio.LanguageServer.Client.Snippets.LanguageServerSnippetExpander")] object languageServerSnippetExpander) + public RoslynLSPSnippetExpander( + [Import("Microsoft.VisualStudio.LanguageServer.Client.Snippets.LanguageServerSnippetExpander", AllowDefault = true)] object? languageServerSnippetExpander) { _lspSnippetExpander = languageServerSnippetExpander; - _expanderType = languageServerSnippetExpander.GetType(); - _expanderMethodInfo = _expanderType.GetMethod("TryExpand"); + + if (_lspSnippetExpander is not null) + { + _expanderType = _lspSnippetExpander.GetType(); + _expanderMethodInfo = _expanderType.GetMethod("TryExpand"); + } } public bool TryExpand(TextChange textChange, SourceText sourceText, string? lspSnippetText, ITextView textView, ITextSnapshot textSnapshot) From 76c70e1da47144aef2b8b67baa6e355517d779fd Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 25 Apr 2022 20:33:40 -0700 Subject: [PATCH 27/58] fix/add comments --- .../CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs index fbcf91e55b856..e03fa26dcaf90 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs @@ -125,9 +125,10 @@ private static void AddInitializerSuppressOperations(List lis return null; } + // Special case for formatting if-statements blocks on new lines if (CommonFormattingHelpers.HasAnyWhitespaceElasticTrivia(previousToken, currentToken) && currentToken.IsKind(SyntaxKind.OpenBraceToken) && - currentToken.Parent.Parent.IsKind(SyntaxKind.IfStatement)) + currentToken.Parent is not null && currentToken.Parent.Parent.IsKind(SyntaxKind.IfStatement)) { var num = LineBreaksAfter(previousToken, currentToken); From 29e3e17c62e579983a19d78a90bdda34065a7651 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 25 Apr 2022 23:12:07 -0700 Subject: [PATCH 28/58] more fixes --- .../Snippets/AbstractCSharpLSPSnippetTests.cs | 4 +- ...actCSharpSnippetCompletionProviderTests.cs | 28 +++++++++++ ...rpConsoleSnippetCompletionProviderTests.cs | 43 ++++++++--------- .../CSharpIfSnippetCompletionProviderTests.cs | 46 ++++++++----------- 4 files changed, 68 insertions(+), 53 deletions(-) create mode 100644 src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpSnippetCompletionProviderTests.cs diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs index 6b79935d13c66..dfe29a3ab2baf 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -43,7 +41,7 @@ private async Task VerifyCustomCommitProviderLSPSnippetAsync(Document document, Assert.Contains(itemToCommit, items.Select(x => x.DisplayText), GetStringComparer()); var completionItem = items.First(i => CompareItems(i.DisplayText, itemToCommit)); var commit = await service.GetChangeAsync(document, completionItem, commitChar, CancellationToken.None); - commit.Properties.TryGetValue("LSPSnippet", out var generatedLSPSnippet); + commit.Properties!.TryGetValue("LSPSnippet", out var generatedLSPSnippet); AssertEx.EqualOrDiff(expectedLSPSnippet, generatedLSPSnippet); } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpSnippetCompletionProviderTests.cs new file mode 100644 index 0000000000000..f4681a9ad43ae --- /dev/null +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpSnippetCompletionProviderTests.cs @@ -0,0 +1,28 @@ +// 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; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.Completion.CompletionProviders.Snippets; +using Microsoft.CodeAnalysis.Snippets; +using Microsoft.CodeAnalysis.Test.Utilities; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders.Snippets +{ + public abstract class AbstractCSharpSnippetCompletionProviderTests : AbstractCSharpCompletionProviderTests + { + protected abstract string ItemToCommit { get; } + + protected override TestComposition GetComposition() + => base.GetComposition() + .AddExcludedPartTypes(typeof(IRoslynLSPSnippetExpander)) + .AddParts(typeof(TestRoslynLanguageServerSnippetExpander)); + + internal override Type GetCompletionProviderType() + => typeof(CSharpSnippetCompletionProvider); + } +} diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpConsoleSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpConsoleSnippetCompletionProviderTests.cs index 5d93fc25af3bc..c25fdbd04b758 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpConsoleSnippetCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpConsoleSnippetCompletionProviderTests.cs @@ -11,12 +11,9 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders.Snippets { - public class CSharpConsoleSnippetCompletionProviderTests : AbstractCSharpCompletionProviderTests + public class CSharpConsoleSnippetCompletionProviderTests : AbstractCSharpSnippetCompletionProviderTests { - private static readonly string s_itemToCommit = FeaturesResources.Write_to_the_console; - - internal override Type GetCompletionProviderType() - => typeof(CSharpSnippetCompletionProvider); + protected override string ItemToCommit => FeaturesResources.Write_to_the_console; [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] public async Task InsertConsoleSnippetInMethodTest() @@ -40,7 +37,7 @@ public void Method() Console.WriteLine($$); } }"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -65,7 +62,7 @@ public async Task MethodAsync() await Console.Out.WriteLineAsync($$); } }"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -90,7 +87,7 @@ public async Task MethodAsync() { } }"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -108,7 +105,7 @@ public async Task MethodAsync() } } }"; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -125,7 +122,7 @@ public async Task MethodAsync() } } "; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -152,7 +149,7 @@ public Program() Console.WriteLine($$); } }"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } /// @@ -189,7 +186,7 @@ void LocalMethod() } } }"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } /// @@ -222,7 +219,7 @@ static void Main(string[] args) }; }"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } /// @@ -249,7 +246,7 @@ public async Task InsertConsoleSnippetInParenthesizedLambdaExpressionTest() global::System.Console.WriteLine($$); return x == y; };"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -272,7 +269,7 @@ public void Method() }; } }"; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -286,7 +283,7 @@ public void Method() Func f = x => $$; } }"; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -301,7 +298,7 @@ public void Method() } }"; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -326,7 +323,7 @@ public Test(string val) } }"; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -340,7 +337,7 @@ public void Method(int x, $$) } }"; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -354,7 +351,7 @@ public async Task NoConsoleSnippetInRecordDeclarationTest() public string LastName { get; init; } = default!; };"; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -369,7 +366,7 @@ public void Method() } }"; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -394,7 +391,7 @@ public void Method() Console.WriteLine($$); } }"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -419,7 +416,7 @@ public void Method() Console.WriteLine($$); } }"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } } } diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs index b9ad4f2afadc6..3f8ce882649c9 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpIfSnippetCompletionProviderTests.cs @@ -17,17 +17,9 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders.Snippets { - public class CSharpIfSnippetCompletionProviderTests : AbstractCSharpCompletionProviderTests + public class CSharpIfSnippetCompletionProviderTests : AbstractCSharpSnippetCompletionProviderTests { - private static readonly string s_itemToCommit = FeaturesResources.Insert_an_if_statement; - - protected override TestComposition GetComposition() - => base.GetComposition() - .AddExcludedPartTypes(typeof(IRoslynLSPSnippetExpander)) - .AddParts(typeof(TestRoslynLanguageServerSnippetExpander)); - - internal override Type GetCompletionProviderType() - => typeof(CSharpSnippetCompletionProvider); + protected override string ItemToCommit => FeaturesResources.Insert_an_if_statement; [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] public async Task InsertIfSnippetInMethodTest() @@ -51,7 +43,7 @@ public void Method() } } }"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -66,7 +58,7 @@ public async Task InsertIfSnippetInGlobalContextTest() {$$ } "; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -84,7 +76,7 @@ public async Task MethodAsync() } } }"; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -101,7 +93,7 @@ public async Task MethodAsync() } } "; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -128,7 +120,7 @@ public Program() } } }"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -161,7 +153,7 @@ void LocalMethod() } } }"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -190,7 +182,7 @@ static void Main(string[] args) }; }"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -212,7 +204,7 @@ public async Task InsertIfSnippetInParenthesizedLambdaExpressionTest() return x == y; };"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -235,7 +227,7 @@ public void Method() }; } }"; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -249,7 +241,7 @@ public void Method() Func f = x => $$; } }"; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -264,7 +256,7 @@ public void Method() } }"; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -289,7 +281,7 @@ public Test(string val) } }"; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -303,7 +295,7 @@ public void Method(int x, $$) } }"; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -317,7 +309,7 @@ public async Task NoIfSnippetInRecordDeclarationTest() public string LastName { get; init; } = default!; };"; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -332,7 +324,7 @@ public void Method() } }"; - await VerifyItemIsAbsentAsync(markupBeforeCommit, s_itemToCommit); + await VerifyItemIsAbsentAsync(markupBeforeCommit, ItemToCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -357,7 +349,7 @@ public void Method() } } }"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] @@ -382,7 +374,7 @@ public void Method() } } }"; - await VerifyCustomCommitProviderAsync(markupBeforeCommit, s_itemToCommit, expectedCodeAfterCommit); + await VerifyCustomCommitProviderAsync(markupBeforeCommit, ItemToCommit, expectedCodeAfterCommit); } } } From 51e7377f0c972c79e0f1606141220117b68f394d Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 25 Apr 2022 23:15:24 -0700 Subject: [PATCH 29/58] more pr feedback --- .../Providers/Snippets/AbstractSnippetCompletionProvider.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs index d21b13eb8709a..1e4150443a0fe 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs @@ -104,7 +104,6 @@ private static (string, int) GetStringInPosition(ImmutableArray lspSnippetItems) @@ -124,7 +123,6 @@ private static TextChange ExtendSnippetTextChange(TextChange textChange, Immutab { newTextChange = new TextChange(new TextSpan(lspSnippetItem.CaretPosition.Value, 0), textChange.NewText); } - } return newTextChange; From 05e839ff0855b7d77ef190b3b5cfb24bae6b16c0 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Tue, 26 Apr 2022 09:03:21 -0700 Subject: [PATCH 30/58] lots of fixes --- .../Snippets/AbstractCSharpLSPSnippetTests.cs | 48 ------------------- ...rpConsoleSnippetCompletionProviderTests.cs | 1 + .../Snippets/CSharpLSPSnippetTests.cs | 38 +++++++++++++-- .../Snippets/CSharpIfSnippetProvider.cs | 3 +- .../AbstractConsoleSnippetProvider.cs | 14 +++--- .../AbstractIfSnippetProvider.cs | 16 ++++--- 6 files changed, 54 insertions(+), 66 deletions(-) delete mode 100644 src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs deleted file mode 100644 index dfe29a3ab2baf..0000000000000 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/AbstractCSharpLSPSnippetTests.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders; -using Roslyn.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Test.Utilities.Snippets -{ - public abstract class AbstractCSharpLSPSnippetTests : AbstractCSharpCompletionProviderTests - { - protected override async Task VerifyCustomCommitProviderWorkerAsync(string codeBeforeCommit, int position, string itemToCommit, string expectedLSPSnippet, SourceCodeKind sourceCodeKind, char? commitChar = null) - { - using var workspaceFixture = GetOrCreateWorkspaceFixture(); - var workspace = workspaceFixture.Target.GetWorkspace(); - - // Set options that are not CompletionOptions - workspace.SetOptions(WithChangedNonCompletionOptions(workspace.Options)); - - var document1 = workspaceFixture.Target.UpdateDocument(codeBeforeCommit, sourceCodeKind); - await VerifyCustomCommitProviderLSPSnippetAsync(document1, position, itemToCommit, expectedLSPSnippet, commitChar); - - if (await CanUseSpeculativeSemanticModelAsync(document1, position)) - { - var document2 = workspaceFixture.Target.UpdateDocument(codeBeforeCommit, sourceCodeKind, cleanBeforeUpdate: false); - await VerifyCustomCommitProviderLSPSnippetAsync(document2, position, itemToCommit, expectedLSPSnippet, commitChar); - } - } - - private async Task VerifyCustomCommitProviderLSPSnippetAsync(Document document, int position, string itemToCommit, string expectedLSPSnippet, char? commitChar = null) - { - var service = GetCompletionService(document.Project); - var completionList = await GetCompletionListAsync(service, document, position, CompletionTrigger.Invoke); - var items = completionList.Items; - - Assert.Contains(itemToCommit, items.Select(x => x.DisplayText), GetStringComparer()); - var completionItem = items.First(i => CompareItems(i.DisplayText, itemToCommit)); - var commit = await service.GetChangeAsync(document, completionItem, commitChar, CancellationToken.None); - commit.Properties!.TryGetValue("LSPSnippet", out var generatedLSPSnippet); - AssertEx.EqualOrDiff(expectedLSPSnippet, generatedLSPSnippet); - } - } -} diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpConsoleSnippetCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpConsoleSnippetCompletionProviderTests.cs index c25fdbd04b758..6ffacd38dfa24 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpConsoleSnippetCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpConsoleSnippetCompletionProviderTests.cs @@ -81,6 +81,7 @@ public async Task MethodAsync() @"using System; Console.WriteLine($$); + class Program { public async Task MethodAsync() diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs index 7d8eb89509b8f..5889feb00019e 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs @@ -6,19 +6,51 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.CSharp.Completion.CompletionProviders.Snippets; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; using Microsoft.CodeAnalysis.Test.Utilities; -using Microsoft.CodeAnalysis.Test.Utilities.Snippets; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders.Snippets { - public class CSharpLSPSnippetTests : AbstractCSharpLSPSnippetTests + public class CSharpLSPSnippetTests : AbstractCSharpSnippetCompletionProviderTests { - internal override Type GetCompletionProviderType() => typeof(CSharpSnippetCompletionProvider); + protected override string ItemToCommit => throw new NotImplementedException(); + + protected override async Task VerifyCustomCommitProviderWorkerAsync(string codeBeforeCommit, int position, string itemToCommit, string expectedLSPSnippet, SourceCodeKind sourceCodeKind, char? commitChar = null) + { + using var workspaceFixture = GetOrCreateWorkspaceFixture(); + var workspace = workspaceFixture.Target.GetWorkspace(); + + // Set options that are not CompletionOptions + workspace.SetOptions(WithChangedNonCompletionOptions(workspace.Options)); + + var document1 = workspaceFixture.Target.UpdateDocument(codeBeforeCommit, sourceCodeKind); + await VerifyCustomCommitProviderLSPSnippetAsync(document1, position, itemToCommit, expectedLSPSnippet, commitChar); + + if (await CanUseSpeculativeSemanticModelAsync(document1, position)) + { + var document2 = workspaceFixture.Target.UpdateDocument(codeBeforeCommit, sourceCodeKind, cleanBeforeUpdate: false); + await VerifyCustomCommitProviderLSPSnippetAsync(document2, position, itemToCommit, expectedLSPSnippet, commitChar); + } + } + + private async Task VerifyCustomCommitProviderLSPSnippetAsync(Document document, int position, string itemToCommit, string expectedLSPSnippet, char? commitChar = null) + { + var service = GetCompletionService(document.Project); + var completionList = await GetCompletionListAsync(service, document, position, CompletionTrigger.Invoke); + var items = completionList.Items; + + Assert.Contains(itemToCommit, items.Select(x => x.DisplayText), GetStringComparer()); + var completionItem = items.First(i => CompareItems(i.DisplayText, itemToCommit)); + var commit = await service.GetChangeAsync(document, completionItem, commitChar, CancellationToken.None); + commit.Properties!.TryGetValue("LSPSnippet", out var generatedLSPSnippet); + AssertEx.EqualOrDiff(expectedLSPSnippet, generatedLSPSnippet); + } [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] public async Task InsertConsoleSnippetInMethodTest() diff --git a/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs index 4b1837517a9fc..b89164637fb9a 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs @@ -34,10 +34,9 @@ public CSharpIfSnippetProvider() { } - protected override void GetPartsOfIfStatement(SyntaxNode node, out SyntaxToken openParen, out SyntaxNode condition, out SyntaxNode statement) + protected override void GetPartsOfIfStatement(SyntaxNode node, out SyntaxNode condition, out SyntaxNode statement) { var ifStatement = (IfStatementSyntax)node; - openParen = ifStatement.OpenParenToken; condition = ifStatement.Condition; statement = ifStatement.Statement; } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs index c7f3bac7bb42e..531e0e5e70721 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs @@ -102,25 +102,25 @@ protected override async Task AnnotateNodesToReformatAsync(Document return root.ReplaceNode(snippetExpressionNode, reformatSnippetNode); } - /*protected override List<(string, List)> GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { - var renameLocationsMap = new List<(string, List)>(); + using var pooledDisposer = ArrayBuilder.GetInstance(out var arrayBuilder); var openParenToken = GetOpenParenToken(node, syntaxFacts); if (openParenToken is null) { - return renameLocationsMap; + return ImmutableArray.Empty; } var list1 = new List { - new TextSpan(openParenToken.Value.Span.End, openParenToken.Value.Span.Length) + new TextSpan(openParenToken.Value.Span.End, 0) }; - renameLocationsMap.Add(("", list1)); + arrayBuilder.Add(new RoslynLSPSnippetItem(null, 0, openParenToken.Value.Span.End, ImmutableArray.Empty)); - return renameLocationsMap; - }*/ + return arrayBuilder.ToImmutable(); + } private static SyntaxToken? GetOpenParenToken(SyntaxNode node, ISyntaxFacts syntaxFacts) { diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs index fe84c96145d7d..6e0f5def5f4b6 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs @@ -22,14 +22,15 @@ namespace Microsoft.CodeAnalysis.Snippets { internal abstract class AbstractIfSnippetProvider : AbstractSnippetProvider { - /// - /// Gets the corresponding pieces of the if statement that will be implemented differently per language. - /// - protected abstract void GetPartsOfIfStatement(SyntaxNode node, out SyntaxToken openParen, out SyntaxNode condition, out SyntaxNode statement); public override string SnippetIdentifier => "if"; public override string SnippetDisplayName => FeaturesResources.Insert_an_if_statement; + /// + /// Gets the corresponding pieces of the if statement that will be implemented differently per language. + /// + protected abstract void GetPartsOfIfStatement(SyntaxNode node, out SyntaxNode condition, out SyntaxNode statement); + protected override async Task IsValidSnippetLocationAsync(Document document, int position, CancellationToken cancellationToken) { var semanticModel = await document.ReuseExistingSpeculativeModelAsync(position, cancellationToken).ConfigureAwait(false); @@ -54,7 +55,9 @@ private static TextChange GenerateSnippetTextChange(Document document, int posit protected override int? GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget) { - GetPartsOfIfStatement(caretTarget, out _, out _, out var statement); + GetPartsOfIfStatement(caretTarget, out _, out var statement); + + // Need to get the statement span start and add 1 to insert between the the curly braces return statement.SpanStart + 1; } @@ -76,8 +79,9 @@ protected override async Task AnnotateNodesToReformatAsync(Document protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { using var pooledDisposer = ArrayBuilder.GetInstance(out var arrayBuilder); - GetPartsOfIfStatement(node, out _, out var condition, out var statement); + GetPartsOfIfStatement(node, out var condition, out var statement); + // Need to get the statement span start and add 1 to insert between the the curly braces arrayBuilder.Add(new RoslynLSPSnippetItem(identifier: null, 0, statement.SpanStart + 1, ImmutableArray.Empty)); var list2 = new List From 631d80db4df4a00df5492ec3e5810f4431fb5fd6 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Tue, 26 Apr 2022 14:26:38 -0700 Subject: [PATCH 31/58] pr feedback --- .../AsyncCompletion/CommitManager.cs | 20 +--- .../Core/Snippets/RoslynLSPSnippetExpander.cs | 38 ++++--- .../Snippets/CSharpIfSnippetProvider.cs | 7 -- .../Portable/Completion/CompletionChange.cs | 8 +- .../AbstractSnippetCompletionProvider.cs | 79 +------------ .../Snippets/SnippetCompletionItem.cs | 2 + .../Snippets/RoslynLSPSnippetConverter.cs | 107 ++++++++++++++++++ .../Portable/Snippets/RoslynLSPSnippetItem.cs | 23 ++++ .../Snippets/RoslynLSPSnippetStringItem.cs | 35 ++++++ .../AbstractConsoleSnippetProvider.cs | 9 +- .../AbstractIfSnippetProvider.cs | 20 +--- .../AbstractSnippetProvider.cs | 12 +- .../Rules/ElasticTriviaFormattingRule.cs | 2 +- .../Services/SyntaxFacts/CSharpSyntaxFacts.cs | 7 ++ .../Core/Services/SyntaxFacts/ISyntaxFacts.cs | 1 + .../SyntaxFacts/VisualBasicSyntaxFacts.vb | 5 + 16 files changed, 228 insertions(+), 147 deletions(-) create mode 100644 src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs create mode 100644 src/Features/Core/Portable/Snippets/RoslynLSPSnippetStringItem.cs diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs index 52994c477697a..5762e623e8ae8 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs @@ -9,13 +9,14 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; +using Microsoft.CodeAnalysis.Completion.Providers.Snippets; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.ErrorReporting; -using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Indentation; +using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Snippets; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; using Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion; @@ -26,10 +27,6 @@ using AsyncCompletionData = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data; using RoslynCompletionItem = Microsoft.CodeAnalysis.Completion.CompletionItem; using VSCompletionItem = Microsoft.VisualStudio.Language.Intellisense.AsyncCompletion.Data.CompletionItem; -using LSP = Microsoft.VisualStudio.LanguageServer.Protocol; -using Microsoft.CodeAnalysis.Completion.Providers.Snippets; -using Microsoft.CodeAnalysis.LanguageServer; -using Microsoft.CodeAnalysis.Snippets; namespace Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.AsyncCompletion { @@ -248,21 +245,14 @@ private AsyncCompletionData.CommitResult Commit( // TryExpand method and determine if it succeeded or not. if (SnippetCompletionItem.IsSnippet(roslynItem)) { - change.Properties!.TryGetValue("LSPSnippet", out var lspSnippetText); + change.Properties!.TryGetValue(SnippetCompletionItem.LSPSnippetKey, out var lspSnippetText); - if (!document.TryGetText(out var sourceText)) - { - FatalError.ReportAndCatch(new InvalidOperationException("Document is not loaded and available."), ErrorSeverity.Critical); - return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None); - } - - if (!_roslynLSPSnippetExpander.TryExpand(change.TextChange, sourceText, lspSnippetText, _textView, triggerSnapshot)) + if (!_roslynLSPSnippetExpander.TryExpand(change.TextChange.Span, lspSnippetText, _textView, triggerSnapshot)) { FatalError.ReportAndCatch(new InvalidOperationException(""), ErrorSeverity.Critical); } return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None); - } var textChange = change.TextChange; diff --git a/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs b/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs index 7980d522e845a..2c1ff29349e4f 100644 --- a/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs +++ b/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs @@ -13,6 +13,7 @@ using Microsoft.VisualStudio.LanguageServer.Protocol; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Snippets { @@ -20,44 +21,49 @@ namespace Microsoft.CodeAnalysis.Snippets [Export(typeof(RoslynLSPSnippetExpander))] internal class RoslynLSPSnippetExpander : IRoslynLSPSnippetExpander { - protected object? _lspSnippetExpander; - protected Type? _expanderType; - protected MethodInfo? _expanderMethodInfo; + protected readonly object? LspSnippetExpander; + protected readonly Type? ExpanderType; + protected readonly MethodInfo? ExpanderMethodInfo; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public RoslynLSPSnippetExpander( [Import("Microsoft.VisualStudio.LanguageServer.Client.Snippets.LanguageServerSnippetExpander", AllowDefault = true)] object? languageServerSnippetExpander) { - _lspSnippetExpander = languageServerSnippetExpander; + LspSnippetExpander = languageServerSnippetExpander; - if (_lspSnippetExpander is not null) + if (LspSnippetExpander is not null) { - _expanderType = _lspSnippetExpander.GetType(); - _expanderMethodInfo = _expanderType.GetMethod("TryExpand"); + ExpanderType = LspSnippetExpander.GetType(); + ExpanderMethodInfo = ExpanderType.GetMethod("TryExpand"); } } - public bool TryExpand(TextChange textChange, SourceText sourceText, string? lspSnippetText, ITextView textView, ITextSnapshot textSnapshot) + public bool TryExpand(TextSpan textSpan, string? lspSnippetText, ITextView textView, ITextSnapshot textSnapshot) { - if (_expanderMethodInfo is null) - { - return false; - } + Contract.ThrowIfFalse(CanExpandSnippet()); var textEdit = new TextEdit() { - Range = ProtocolConversions.TextSpanToRange(textChange.Span, sourceText), + Range = ProtocolConversions.TextSpanToRange(textSpan, textSnapshot.AsText()), NewText = lspSnippetText! }; - var expandMethodResult = _expanderMethodInfo.Invoke(_lspSnippetExpander, new object[] { textEdit, textView, textSnapshot }); - return expandMethodResult is not null && (bool)expandMethodResult; + try + { + // ExpanderMethodInfo should not be null at this point. + var expandMethodResult = ExpanderMethodInfo!.Invoke(LspSnippetExpander, new object[] { textEdit, textView, textSnapshot }); + return expandMethodResult is not null && (bool)expandMethodResult; + } + catch + { + return false; + } } public bool CanExpandSnippet() { - return _expanderMethodInfo is not null; + return ExpanderMethodInfo is not null; } } } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs index b89164637fb9a..5b935419784ae 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs @@ -33,12 +33,5 @@ internal class CSharpIfSnippetProvider : AbstractIfSnippetProvider public CSharpIfSnippetProvider() { } - - protected override void GetPartsOfIfStatement(SyntaxNode node, out SyntaxNode condition, out SyntaxNode statement) - { - var ifStatement = (IfStatementSyntax)node; - condition = ifStatement.Condition; - statement = ifStatement.Statement; - } } } diff --git a/src/Features/Core/Portable/Completion/CompletionChange.cs b/src/Features/Core/Portable/Completion/CompletionChange.cs index 81a84ee01ec20..1e59751452d00 100644 --- a/src/Features/Core/Portable/Completion/CompletionChange.cs +++ b/src/Features/Core/Portable/Completion/CompletionChange.cs @@ -41,16 +41,16 @@ public sealed class CompletionChange /// public bool IncludesCommitCharacter { get; } - internal ImmutableDictionary? Properties { get; } + internal ImmutableDictionary Properties { get; } private CompletionChange( TextChange textChange, ImmutableArray textChanges, int? newPosition, bool includesCommitCharacter) - : this(textChange, textChanges, newPosition, includesCommitCharacter, null) + : this(textChange, textChanges, newPosition, includesCommitCharacter, ImmutableDictionary.Empty) { } private CompletionChange( - TextChange textChange, ImmutableArray textChanges, int? newPosition, bool includesCommitCharacter, ImmutableDictionary? properties) + TextChange textChange, ImmutableArray textChanges, int? newPosition, bool includesCommitCharacter, ImmutableDictionary properties) { TextChange = textChange; NewPosition = newPosition; @@ -110,7 +110,7 @@ public static CompletionChange Create( internal static CompletionChange Create( TextChange textChange, ImmutableArray textChanges, - ImmutableDictionary? properties, + ImmutableDictionary properties, int? newPosition, bool includesCommitCharacter) { diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs index 1e4150443a0fe..fb5f50712b6f4 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs @@ -47,87 +47,14 @@ public override async Task GetChangeAsync(Document document, C var allTextChanges = await allChangesDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); var change = Utilities.Collapse(allChangesText, allTextChanges.AsImmutable()); - var finalTextChange = ExtendSnippetTextChange(change, snippet.Placeholders); - var lspSnippet = GenerateLSPSnippet(finalTextChange, snippet.Placeholders); + var finalTextChange = RoslynLSPSnippetConverter.ExtendSnippetTextChange(change, snippet.Placeholders); + var lspSnippet = RoslynLSPSnippetConverter.GenerateLSPSnippet(finalTextChange, snippet.Placeholders); var props = ImmutableDictionary.Empty - .Add("LSPSnippet", lspSnippet!); + .Add(SnippetCompletionItem.LSPSnippetKey, lspSnippet!); return CompletionChange.Create(change, allTextChanges.AsImmutable(), properties: props, snippet.CursorPosition, includesCommitCharacter: true); } - private static string? GenerateLSPSnippet(TextChange textChange, ImmutableArray placeholders) - { - var textChangeStart = textChange.Span.Start; - var textChangeText = textChange.NewText!; - var lspSnippetString = ""; - - for (var i = 0; i < textChangeText.Length;) - { - var (str, strCount) = GetStringInPosition(placeholders, i, textChangeStart); - if (str.IsEmpty()) - { - lspSnippetString += textChangeText[i]; - i++; - } - else - { - lspSnippetString += str; - i += strCount; - - if (strCount == 0) - { - lspSnippetString += textChangeText[i]; - i++; - } - } - } - - return lspSnippetString; - } - - private static (string, int) GetStringInPosition(ImmutableArray placeholders, int position, int textChangeStart) - { - foreach (var placeholder in placeholders) - { - if (placeholder.CaretPosition.HasValue && placeholder.CaretPosition.Value - textChangeStart == position) - { - return ("$0", 0); - } - - foreach (var span in placeholder.PlaceHolderSpans) - { - if (span.Start - textChangeStart == position) - { - return ($"${{{placeholder.Priority}:{placeholder.Identifier}}}", placeholder.Identifier!.Length); - } - } - } - - return (string.Empty, 0); - } - - private static TextChange ExtendSnippetTextChange(TextChange textChange, ImmutableArray lspSnippetItems) - { - var newTextChange = textChange; - foreach (var lspSnippetItem in lspSnippetItems) - { - foreach (var placeholder in lspSnippetItem.PlaceHolderSpans) - { - if (newTextChange.Span.Start > placeholder.Start) - { - newTextChange = new TextChange(new TextSpan(placeholder.Start, 0), textChange.NewText); - } - } - - if (lspSnippetItem.CaretPosition is not null && textChange.Span.Start > lspSnippetItem.CaretPosition) - { - newTextChange = new TextChange(new TextSpan(lspSnippetItem.CaretPosition.Value, 0), textChange.NewText); - } - } - - return newTextChange; - } - public override async Task ProvideCompletionsAsync(CompletionContext context) { if (_roslynLSPSnippetExpander.CanExpandSnippet()) diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs index 63d5190fba3f9..72a5d77fc71ba 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs @@ -10,6 +10,8 @@ namespace Microsoft.CodeAnalysis.Completion.Providers.Snippets { internal class SnippetCompletionItem { + public static string LSPSnippetKey = "LSPSnippet"; + public static CompletionItem Create( string displayText, string displayTextSuffix, diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs new file mode 100644 index 0000000000000..ff744e5b38898 --- /dev/null +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -0,0 +1,107 @@ +// 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; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Text; +using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.Snippets +{ + internal static class RoslynLSPSnippetConverter + { + public static string? GenerateLSPSnippet(TextChange textChange, ImmutableArray placeholders) + { + var textChangeStart = textChange.Span.Start; + var textChangeText = textChange.NewText!; + using var _ = PooledStringBuilder.GetInstance(out var lspSnippetString); + var map = GetMapOfSpanStartsToLSPStringItem(placeholders, textChangeStart); + + for (var i = 0; i < textChangeText.Length;) + { + var (str, strLength) = GetStringInPosition(map, position: i); + if (str.IsEmpty()) + { + lspSnippetString.Append(textChangeText[i]); + i++; + } + else + { + lspSnippetString.Append(str); + i += strLength; + + if (strLength == 0) + { + lspSnippetString.Append(textChangeText[i]); + i++; + } + } + } + + return lspSnippetString.ToString(); + } + + private static Dictionary GetMapOfSpanStartsToLSPStringItem(ImmutableArray placeholders, int textChangeStart) + { + var map = new Dictionary(); + + foreach (var placeholder in placeholders) + { + foreach (var span in placeholder.PlaceHolderSpans) + { + map.Add(span.Start - textChangeStart, new RoslynLSPSnippetStringItem(placeholder.Identifier, placeholder.Priority)); + } + + if (placeholder.CaretPosition.HasValue) + { + map.Add(placeholder.CaretPosition.Value - textChangeStart, new RoslynLSPSnippetStringItem(placeholder.Identifier, placeholder.Priority)); + } + } + + return map; + } + + private static (string str, int strLength) GetStringInPosition(Dictionary map, int position) + { + if (map.TryGetValue(position, out var lspStringItem)) + { + if (lspStringItem.Identifier is not null) + { + return ($"${{{lspStringItem.Priority}:{lspStringItem.Identifier}}}", lspStringItem.Identifier.Length); + } + else + { + return ("$0", 0); + } + } + + return (string.Empty, 0); + } + + public static TextChange ExtendSnippetTextChange(TextChange textChange, ImmutableArray lspSnippetItems) + { + var newTextChange = textChange; + foreach (var lspSnippetItem in lspSnippetItems) + { + foreach (var placeholder in lspSnippetItem.PlaceHolderSpans) + { + if (newTextChange.Span.Start > placeholder.Start) + { + newTextChange = new TextChange(new TextSpan(placeholder.Start, 0), textChange.NewText!); + } + } + + if (lspSnippetItem.CaretPosition is not null && textChange.Span.Start > lspSnippetItem.CaretPosition) + { + newTextChange = new TextChange(new TextSpan(lspSnippetItem.CaretPosition.Value, 0), textChange.NewText!); + } + } + + return newTextChange; + } + } +} diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetItem.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetItem.cs index 75d137625036c..3efc7ab0038df 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetItem.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetItem.cs @@ -12,9 +12,32 @@ namespace Microsoft.CodeAnalysis.Snippets { internal readonly struct RoslynLSPSnippetItem { + /// + /// The identifier in the snippet that needs to be renamed. + /// Will be null in the case of the final tab stop location, + /// the '$0' case. + /// public readonly string? Identifier; + + /// + /// The value associated with the identifier. + /// EX: if (${1:true}) + /// {$0 + /// } + /// The '1' and '0' are represented by this value. + /// public readonly int Priority; + + /// + /// Where we want the caret to end up as the final tab-stop location. + /// If we can't find a caret position, we return null. + /// public readonly int? CaretPosition; + + /// + /// The spans associated with the identifier that will need to + /// be converted into LSP formatted strings. + /// public readonly ImmutableArray PlaceHolderSpans; public RoslynLSPSnippetItem(string? identifier, int priority, int? caretPosition, ImmutableArray placeholderSpans) diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetStringItem.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetStringItem.cs new file mode 100644 index 0000000000000..ff307218fd58c --- /dev/null +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetStringItem.cs @@ -0,0 +1,35 @@ +// 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; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.CodeAnalysis.Snippets +{ + internal readonly struct RoslynLSPSnippetStringItem + { + /// + /// The identifier in the snippet that needs to be renamed. + /// Will be null in the case of the final tab stop location, + /// the '$0' case. + /// + public readonly string? Identifier; + + /// + /// The value associated with the identifier. + /// EX: if (${1:true}) + /// {$0 + /// } + /// The '1' and '0' are represented by this value. + /// + public readonly int Priority; + + public RoslynLSPSnippetStringItem(string? identifier, int priority) + { + Identifier = identifier; + Priority = priority; + } + } +} diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs index 531e0e5e70721..89f0bccaae519 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs @@ -104,7 +104,7 @@ protected override async Task AnnotateNodesToReformatAsync(Document protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { - using var pooledDisposer = ArrayBuilder.GetInstance(out var arrayBuilder); + using var _ = ArrayBuilder.GetInstance(out var arrayBuilder); var openParenToken = GetOpenParenToken(node, syntaxFacts); if (openParenToken is null) @@ -112,12 +112,7 @@ protected override ImmutableArray GetPlaceHolderLocationsL return ImmutableArray.Empty; } - var list1 = new List - { - new TextSpan(openParenToken.Value.Span.End, 0) - }; - - arrayBuilder.Add(new RoslynLSPSnippetItem(null, 0, openParenToken.Value.Span.End, ImmutableArray.Empty)); + arrayBuilder.Add(new RoslynLSPSnippetItem(identifier: null, priority: 0, caretPosition: openParenToken.Value.Span.End, placeholderSpans: ImmutableArray.Empty)); return arrayBuilder.ToImmutable(); } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs index 6e0f5def5f4b6..7e0adb48e8d8d 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs @@ -26,11 +26,6 @@ internal abstract class AbstractIfSnippetProvider : AbstractSnippetProvider public override string SnippetDisplayName => FeaturesResources.Insert_an_if_statement; - /// - /// Gets the corresponding pieces of the if statement that will be implemented differently per language. - /// - protected abstract void GetPartsOfIfStatement(SyntaxNode node, out SyntaxNode condition, out SyntaxNode statement); - protected override async Task IsValidSnippetLocationAsync(Document document, int position, CancellationToken cancellationToken) { var semanticModel = await document.ReuseExistingSpeculativeModelAsync(position, cancellationToken).ConfigureAwait(false); @@ -55,7 +50,7 @@ private static TextChange GenerateSnippetTextChange(Document document, int posit protected override int? GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget) { - GetPartsOfIfStatement(caretTarget, out _, out var statement); + syntaxFacts.GetPartsOfIfStatement(caretTarget, out _, out var statement); // Need to get the statement span start and add 1 to insert between the the curly braces return statement.SpanStart + 1; @@ -78,18 +73,13 @@ protected override async Task AnnotateNodesToReformatAsync(Document protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { - using var pooledDisposer = ArrayBuilder.GetInstance(out var arrayBuilder); - GetPartsOfIfStatement(node, out var condition, out var statement); + using var _ = ArrayBuilder.GetInstance(out var arrayBuilder); + syntaxFacts.GetPartsOfIfStatement(node, out var condition, out var statement); // Need to get the statement span start and add 1 to insert between the the curly braces - arrayBuilder.Add(new RoslynLSPSnippetItem(identifier: null, 0, statement.SpanStart + 1, ImmutableArray.Empty)); - - var list2 = new List - { - new TextSpan(condition.SpanStart, 0) - }; + arrayBuilder.Add(new RoslynLSPSnippetItem(identifier: null, priority: 0, caretPosition: statement.SpanStart + 1, placeholderSpans: ImmutableArray.Empty)); - arrayBuilder.Add(new RoslynLSPSnippetItem(condition.ToString(), 1, caretPosition: null, list2.ToImmutableArray())); + arrayBuilder.Add(new RoslynLSPSnippetItem(identifier: condition.ToString(), priority: 1, caretPosition: null, placeholderSpans: ImmutableArray.Create(new TextSpan(condition.SpanStart, 0)))); return arrayBuilder.ToImmutableArray(); } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs index 2a52d28b69489..2f67c7c032733 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -54,6 +54,11 @@ internal abstract class AbstractSnippetProvider : ISnippetProvider /// protected abstract SyntaxNode? FindAddedSnippetSyntaxNode(SyntaxNode root, int position, ISyntaxFacts syntaxFacts); + /// + /// Method to find the locations that must be renamed and where tab stops must be inserted into the snippet. + /// + protected abstract ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken); + /// /// Determines if the location is valid for a snippet, /// if so, then it creates a SnippetData. @@ -124,7 +129,7 @@ public async Task GetSnippetAsync(Document document, int position /// /// Descends into the inserted snippet to add back trivia on every token. /// - protected static SyntaxNode? GenerateElasticTriviaForSyntax(ISyntaxFacts syntaxFacts, SyntaxNode? node) + private static SyntaxNode? GenerateElasticTriviaForSyntax(ISyntaxFacts syntaxFacts, SyntaxNode? node) { if (node is null) { @@ -202,10 +207,5 @@ private async Task AddFormatAnnotationAsync(Document document, int pos document = document.WithSyntaxRoot(annotatedSnippetRoot); return document; } - - protected virtual ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) - { - return ImmutableArray.Empty; - } } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs index e03fa26dcaf90..85721afca1226 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Formatting/Rules/ElasticTriviaFormattingRule.cs @@ -128,7 +128,7 @@ private static void AddInitializerSuppressOperations(List lis // Special case for formatting if-statements blocks on new lines if (CommonFormattingHelpers.HasAnyWhitespaceElasticTrivia(previousToken, currentToken) && currentToken.IsKind(SyntaxKind.OpenBraceToken) && - currentToken.Parent is not null && currentToken.Parent.Parent.IsKind(SyntaxKind.IfStatement)) + currentToken.Parent.IsParentKind(SyntaxKind.IfStatement)) { var num = LineBreaksAfter(previousToken, currentToken); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index ced67273c7524..705bb5e204ae4 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -1654,6 +1654,13 @@ public void GetPartsOfConditionalExpression(SyntaxNode node, out SyntaxNode cond whenFalse = conditionalExpression.WhenFalse; } + public void GetPartsOfIfStatement(SyntaxNode node, out SyntaxNode condition, out SyntaxNode statement) + { + var ifStatement = (IfStatementSyntax)node; + condition = ifStatement.Condition; + statement = ifStatement.Statement; + } + public void GetPartsOfInvocationExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxNode? argumentList) { var invocation = (InvocationExpressionSyntax)node; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs index e87b36144bd5c..232c4890b3837 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs @@ -538,6 +538,7 @@ void GetPartsOfInterpolationExpression(SyntaxNode node, void GetPartsOfCompilationUnit(SyntaxNode node, out SyntaxList imports, out SyntaxList attributeLists, out SyntaxList members); void GetPartsOfConditionalAccessExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxToken operatorToken, out SyntaxNode whenNotNull); void GetPartsOfConditionalExpression(SyntaxNode node, out SyntaxNode condition, out SyntaxNode whenTrue, out SyntaxNode whenFalse); + void GetPartsOfIfStatement(SyntaxNode node, out SyntaxNode condition, out SyntaxNode statement); void GetPartsOfInvocationExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxNode? argumentList); void GetPartsOfIsPatternExpression(SyntaxNode node, out SyntaxNode left, out SyntaxToken isToken, out SyntaxNode right); void GetPartsOfMemberAccessExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxToken operatorToken, out SyntaxNode name); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb index 215340ff4f09d..e440d25123763 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb @@ -1871,6 +1871,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices whenFalse = conditionalExpression.WhenFalse End Sub + Public Sub GetPartsOfIfStatement(node As SyntaxNode, ByRef condition As SyntaxNode, ByRef statement As SyntaxNode) Implements ISyntaxFacts.GetPartsOfIfStatement + Throw New NotImplementedException + End Sub + Public Sub GetPartsOfInvocationExpression(node As SyntaxNode, ByRef expression As SyntaxNode, ByRef argumentList As SyntaxNode) Implements ISyntaxFacts.GetPartsOfInvocationExpression Dim invocation = DirectCast(node, InvocationExpressionSyntax) expression = invocation.Expression @@ -1940,6 +1944,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices Public Function GetStatementOfGlobalStatement(node As SyntaxNode) As SyntaxNode Implements ISyntaxFacts.GetStatementOfGlobalStatement Throw New NotImplementedException() End Function + Public Function GetInitializersOfObjectMemberInitializer(node As SyntaxNode) As SeparatedSyntaxList(Of SyntaxNode) Implements ISyntaxFacts.GetInitializersOfObjectMemberInitializer Dim initializer = TryCast(node, ObjectMemberInitializerSyntax) If initializer Is Nothing Then From f257f522b0ac37871ac86de6a0f15819cce9ac26 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Tue, 26 Apr 2022 14:31:27 -0700 Subject: [PATCH 32/58] naming --- .../Portable/Snippets/RoslynLSPSnippetConverter.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs index ff744e5b38898..f2dee3e040aa9 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -82,22 +82,22 @@ private static (string str, int strLength) GetStringInPosition(Dictionary lspSnippetItems) + public static TextChange ExtendSnippetTextChange(TextChange textChange, ImmutableArray placeholders) { var newTextChange = textChange; - foreach (var lspSnippetItem in lspSnippetItems) + foreach (var placeholder in placeholders) { - foreach (var placeholder in lspSnippetItem.PlaceHolderSpans) + foreach (var span in placeholder.PlaceHolderSpans) { - if (newTextChange.Span.Start > placeholder.Start) + if (newTextChange.Span.Start > span.Start) { - newTextChange = new TextChange(new TextSpan(placeholder.Start, 0), textChange.NewText!); + newTextChange = new TextChange(new TextSpan(span.Start, 0), textChange.NewText!); } } - if (lspSnippetItem.CaretPosition is not null && textChange.Span.Start > lspSnippetItem.CaretPosition) + if (placeholder.CaretPosition is not null && textChange.Span.Start > placeholder.CaretPosition) { - newTextChange = new TextChange(new TextSpan(lspSnippetItem.CaretPosition.Value, 0), textChange.NewText!); + newTextChange = new TextChange(new TextSpan(placeholder.CaretPosition.Value, 0), textChange.NewText!); } } From 7f7c0879a3ffdb910bbfd61a4543b59a4a94ea21 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 2 May 2022 10:07:22 -0700 Subject: [PATCH 33/58] lots of changes + added new tests --- src/Compilers/Test/Core/Traits/Traits.cs | 1 + .../AsyncCompletion/CommitManager.cs | 4 +- .../Core/Snippets/RoslynLSPSnippetExpander.cs | 22 ++-- .../Snippets/RoslynLSPSnippetConvertTests.cs | 75 +++++++++++ .../AbstractSnippetCompletionProvider.cs | 5 +- .../Snippets/RoslynLSPSnippetConverter.cs | 117 ++++++++++++------ .../Portable/Snippets/RoslynLSPSnippetItem.cs | 51 -------- .../Core/Portable/Snippets/SnippetChange.cs | 8 +- ...petStringItem.cs => SnippetPlaceholder.cs} | 19 ++- .../AbstractConsoleSnippetProvider.cs | 22 +--- .../AbstractIfSnippetProvider.cs | 13 +- .../AbstractSnippetProvider.cs | 4 +- 12 files changed, 192 insertions(+), 149 deletions(-) create mode 100644 src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs delete mode 100644 src/Features/Core/Portable/Snippets/RoslynLSPSnippetItem.cs rename src/Features/Core/Portable/Snippets/{RoslynLSPSnippetStringItem.cs => SnippetPlaceholder.cs} (56%) diff --git a/src/Compilers/Test/Core/Traits/Traits.cs b/src/Compilers/Test/Core/Traits/Traits.cs index 942c762f2247a..de4093efe2aee 100644 --- a/src/Compilers/Test/Core/Traits/Traits.cs +++ b/src/Compilers/Test/Core/Traits/Traits.cs @@ -290,6 +290,7 @@ public static class Features public const string RemoveUnnecessaryLineContinuation = nameof(RemoveUnnecessaryLineContinuation); public const string Rename = nameof(Rename); public const string RenameTracking = nameof(RenameTracking); + public const string RoslynLSPSnippetConverter = nameof(RoslynLSPSnippetConverter); public const string SignatureHelp = nameof(SignatureHelp); public const string Simplification = nameof(Simplification); public const string SmartIndent = nameof(SmartIndent); diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs index 5762e623e8ae8..4cc3cc29cffb2 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs @@ -245,9 +245,9 @@ private AsyncCompletionData.CommitResult Commit( // TryExpand method and determine if it succeeded or not. if (SnippetCompletionItem.IsSnippet(roslynItem)) { - change.Properties!.TryGetValue(SnippetCompletionItem.LSPSnippetKey, out var lspSnippetText); + change.Properties.TryGetValue(SnippetCompletionItem.LSPSnippetKey, out var lspSnippetText); - if (!_roslynLSPSnippetExpander.TryExpand(change.TextChange.Span, lspSnippetText, _textView, triggerSnapshot)) + if (!_roslynLSPSnippetExpander.TryExpand(change.TextChange.Span, lspSnippetText!, _textView, triggerSnapshot)) { FatalError.ReportAndCatch(new InvalidOperationException(""), ErrorSeverity.Critical); } diff --git a/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs b/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs index 2c1ff29349e4f..cceedaa240806 100644 --- a/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs +++ b/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs @@ -21,38 +21,38 @@ namespace Microsoft.CodeAnalysis.Snippets [Export(typeof(RoslynLSPSnippetExpander))] internal class RoslynLSPSnippetExpander : IRoslynLSPSnippetExpander { - protected readonly object? LspSnippetExpander; - protected readonly Type? ExpanderType; - protected readonly MethodInfo? ExpanderMethodInfo; + private readonly object? _lspSnippetExpander; + private readonly Type? _expanderType; + private readonly MethodInfo? _expanderMethodInfo; [ImportingConstructor] [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] public RoslynLSPSnippetExpander( [Import("Microsoft.VisualStudio.LanguageServer.Client.Snippets.LanguageServerSnippetExpander", AllowDefault = true)] object? languageServerSnippetExpander) { - LspSnippetExpander = languageServerSnippetExpander; + _lspSnippetExpander = languageServerSnippetExpander; - if (LspSnippetExpander is not null) + if (_lspSnippetExpander is not null) { - ExpanderType = LspSnippetExpander.GetType(); - ExpanderMethodInfo = ExpanderType.GetMethod("TryExpand"); + _expanderType = _lspSnippetExpander.GetType(); + _expanderMethodInfo = _expanderType.GetMethod("TryExpand"); } } - public bool TryExpand(TextSpan textSpan, string? lspSnippetText, ITextView textView, ITextSnapshot textSnapshot) + public bool TryExpand(TextSpan textSpan, string lspSnippetText, ITextView textView, ITextSnapshot textSnapshot) { Contract.ThrowIfFalse(CanExpandSnippet()); var textEdit = new TextEdit() { Range = ProtocolConversions.TextSpanToRange(textSpan, textSnapshot.AsText()), - NewText = lspSnippetText! + NewText = lspSnippetText }; try { // ExpanderMethodInfo should not be null at this point. - var expandMethodResult = ExpanderMethodInfo!.Invoke(LspSnippetExpander, new object[] { textEdit, textView, textSnapshot }); + var expandMethodResult = _expanderMethodInfo!.Invoke(_lspSnippetExpander, new object[] { textEdit, textView, textSnapshot }); return expandMethodResult is not null && (bool)expandMethodResult; } catch @@ -63,7 +63,7 @@ public bool TryExpand(TextSpan textSpan, string? lspSnippetText, ITextView textV public bool CanExpandSnippet() { - return ExpanderMethodInfo is not null; + return _expanderMethodInfo is not null; } } } diff --git a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs new file mode 100644 index 0000000000000..40eceecafb344 --- /dev/null +++ b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs @@ -0,0 +1,75 @@ +// 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; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Microsoft.CodeAnalysis.Snippets; +using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.UnitTests.Snippets +{ + [UseExportProvider] + public class RoslynLSPSnippetConvertTests + { + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeForwards() + { + var markup = +@"[|if ({|placeholder:true|}) +{ +}|] $$"; + + var expectedLSPSnippet = +@"if (${1:true}) +{ +} $0"; + MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); + var stringSpan = dictionary[""].First(); + var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString[..stringSpan.Length]); + var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); + return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("true", placeholders)), textChange); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeBackwards() + { + var markup = +@"$$ [|if ({|placeholder:true|}) +{ +}|]"; + + var expectedLSPSnippet = +@"$0if (${1:true}) +{ +}"; + MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); + var stringSpan = dictionary[""].First(); + var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString.Substring(stringSpan.Start, stringSpan.Length - 1)); + var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); + return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("true", placeholders)), textChange); + } + + protected static TestWorkspace CreateWorkspaceFromCode(string code) + => TestWorkspace.CreateCSharp(code); + + private static async Task TestAsync(string markup, string expectedLSPSnippet, int? cursorPosition, ImmutableArray placeholders, TextChange textChange) + { + using var workspace = CreateWorkspaceFromCode(markup); + var document = workspace.CurrentSolution.GetDocument(workspace.Documents.First().Id); + var lspSnippetString = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document, cursorPosition!.Value, placeholders, textChange).ConfigureAwait(false); + + AssertEx.EqualOrDiff(expectedLSPSnippet, lspSnippetString); + } + } +} diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs index fb5f50712b6f4..ef51bf5079d20 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs @@ -47,10 +47,9 @@ public override async Task GetChangeAsync(Document document, C var allTextChanges = await allChangesDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); var change = Utilities.Collapse(allChangesText, allTextChanges.AsImmutable()); - var finalTextChange = RoslynLSPSnippetConverter.ExtendSnippetTextChange(change, snippet.Placeholders); - var lspSnippet = RoslynLSPSnippetConverter.GenerateLSPSnippet(finalTextChange, snippet.Placeholders); + var lspSnippet = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(allChangesDocument, snippet.CursorPosition, snippet.Placeholders, change).ConfigureAwait(false); var props = ImmutableDictionary.Empty - .Add(SnippetCompletionItem.LSPSnippetKey, lspSnippet!); + .Add(SnippetCompletionItem.LSPSnippetKey, lspSnippet); return CompletionChange.Create(change, allTextChanges.AsImmutable(), properties: props, snippet.CursorPosition, includesCommitCharacter: true); } diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs index f2dee3e040aa9..fbf899b25f34c 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -6,6 +6,9 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; @@ -14,94 +17,128 @@ namespace Microsoft.CodeAnalysis.Snippets { internal static class RoslynLSPSnippetConverter { - public static string? GenerateLSPSnippet(TextChange textChange, ImmutableArray placeholders) + public static async Task GenerateLSPSnippetAsync(Document document, int caretPosition, ImmutableArray placeholders, TextChange collapsedTextChange) + { + var extendedTextChange = await ExtendSnippetTextChangeAsync(document, collapsedTextChange, placeholders, caretPosition).ConfigureAwait(false); + return ConvertToLSPSnippetString(extendedTextChange, placeholders, caretPosition); + } + + private static string ConvertToLSPSnippetString(TextChange textChange, ImmutableArray placeholders, int caretPosition) { var textChangeStart = textChange.Span.Start; var textChangeText = textChange.NewText!; using var _ = PooledStringBuilder.GetInstance(out var lspSnippetString); var map = GetMapOfSpanStartsToLSPStringItem(placeholders, textChangeStart); - for (var i = 0; i < textChangeText.Length;) + for (var i = 0; i < textChangeText.Length + 1;) { - var (str, strLength) = GetStringInPosition(map, position: i); - if (str.IsEmpty()) + if (i == caretPosition - textChangeStart) { - lspSnippetString.Append(textChangeText[i]); + lspSnippetString.Append("$0"); i++; } - else - { - lspSnippetString.Append(str); - i += strLength; - if (strLength == 0) + if (i < textChangeText.Length) + { + var (str, strLength) = GetStringInPosition(map, position: i); + if (str.IsEmpty()) { lspSnippetString.Append(textChangeText[i]); i++; } + else + { + lspSnippetString.Append(str); + i += strLength; + } + } + else + { + break; } } return lspSnippetString.ToString(); } - private static Dictionary GetMapOfSpanStartsToLSPStringItem(ImmutableArray placeholders, int textChangeStart) + private static Dictionary GetMapOfSpanStartsToLSPStringItem(ImmutableArray placeholders, int textChangeStart) { - var map = new Dictionary(); + var map = new Dictionary(); - foreach (var placeholder in placeholders) + for (var i = 0; i < placeholders.Length; i++) { - foreach (var span in placeholder.PlaceHolderSpans) + var placeholder = placeholders[i]; + foreach (var position in placeholder.PlaceHolderPositions) { - map.Add(span.Start - textChangeStart, new RoslynLSPSnippetStringItem(placeholder.Identifier, placeholder.Priority)); - } - - if (placeholder.CaretPosition.HasValue) - { - map.Add(placeholder.CaretPosition.Value - textChangeStart, new RoslynLSPSnippetStringItem(placeholder.Identifier, placeholder.Priority)); + map.Add(position - textChangeStart, (placeholder.Identifier, i + 1)); } } return map; } - private static (string str, int strLength) GetStringInPosition(Dictionary map, int position) + private static (string str, int strLength) GetStringInPosition(Dictionary map, int position) { - if (map.TryGetValue(position, out var lspStringItem)) + if (map.TryGetValue(position, out var placeholderInfo)) { - if (lspStringItem.Identifier is not null) - { - return ($"${{{lspStringItem.Priority}:{lspStringItem.Identifier}}}", lspStringItem.Identifier.Length); - } - else - { - return ("$0", 0); - } + return ($"${{{placeholderInfo.priority}:{placeholderInfo.identifier}}}", placeholderInfo.identifier.Length); } return (string.Empty, 0); } - public static TextChange ExtendSnippetTextChange(TextChange textChange, ImmutableArray placeholders) + private static async Task ExtendSnippetTextChangeAsync(Document document, TextChange textChange, ImmutableArray placeholders, int caretPosition) + { + var extendedSpan = GetUpdatedTextSpan(textChange, placeholders, caretPosition); + + if (extendedSpan.Length == 0) + { + return textChange; + } + + var documentText = await document.GetTextAsync().ConfigureAwait(false); + var newString = documentText.ToString(extendedSpan); + var newTextChange = new TextChange(new TextSpan(extendedSpan.Start, 0), newString); + + return newTextChange; + } + + private static TextSpan GetUpdatedTextSpan(TextChange textChange, ImmutableArray placeholders, int caretPosition) { - var newTextChange = textChange; + var textSpanLength = textChange.NewText!.Length; + + var startPosition = textChange.Span.Start; + var endPosition = textChange.Span.Start + textSpanLength; + foreach (var placeholder in placeholders) { - foreach (var span in placeholder.PlaceHolderSpans) + foreach (var position in placeholder.PlaceHolderPositions) { - if (newTextChange.Span.Start > span.Start) + if (startPosition > position) { - newTextChange = new TextChange(new TextSpan(span.Start, 0), textChange.NewText!); + endPosition += startPosition - caretPosition; + startPosition = position; } - } - if (placeholder.CaretPosition is not null && textChange.Span.Start > placeholder.CaretPosition) - { - newTextChange = new TextChange(new TextSpan(placeholder.CaretPosition.Value, 0), textChange.NewText!); + if (startPosition + textSpanLength < position) + { + endPosition = position; + } } } - return newTextChange; + if (startPosition > caretPosition) + { + endPosition += startPosition - caretPosition; + startPosition = caretPosition; + } + + if (startPosition + textSpanLength < caretPosition) + { + endPosition = caretPosition; + } + + return TextSpan.FromBounds(startPosition, endPosition); } } } diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetItem.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetItem.cs deleted file mode 100644 index 3efc7ab0038df..0000000000000 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetItem.cs +++ /dev/null @@ -1,51 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Text; -using Microsoft.CodeAnalysis.Text; - -namespace Microsoft.CodeAnalysis.Snippets -{ - internal readonly struct RoslynLSPSnippetItem - { - /// - /// The identifier in the snippet that needs to be renamed. - /// Will be null in the case of the final tab stop location, - /// the '$0' case. - /// - public readonly string? Identifier; - - /// - /// The value associated with the identifier. - /// EX: if (${1:true}) - /// {$0 - /// } - /// The '1' and '0' are represented by this value. - /// - public readonly int Priority; - - /// - /// Where we want the caret to end up as the final tab-stop location. - /// If we can't find a caret position, we return null. - /// - public readonly int? CaretPosition; - - /// - /// The spans associated with the identifier that will need to - /// be converted into LSP formatted strings. - /// - public readonly ImmutableArray PlaceHolderSpans; - - public RoslynLSPSnippetItem(string? identifier, int priority, int? caretPosition, ImmutableArray placeholderSpans) - { - Identifier = identifier; - Priority = priority; - CaretPosition = caretPosition; - PlaceHolderSpans = placeholderSpans; - } - } -} diff --git a/src/Features/Core/Portable/Snippets/SnippetChange.cs b/src/Features/Core/Portable/Snippets/SnippetChange.cs index b478590aed6b8..a529300d1b9f7 100644 --- a/src/Features/Core/Portable/Snippets/SnippetChange.cs +++ b/src/Features/Core/Portable/Snippets/SnippetChange.cs @@ -22,18 +22,18 @@ internal readonly struct SnippetChange /// /// The position that the cursor should end up on /// - public readonly int? CursorPosition; + public readonly int CursorPosition; /// /// The items that we will want to rename as well as the ordering /// in which to visit those items. /// - public readonly ImmutableArray Placeholders; + public readonly ImmutableArray Placeholders; public SnippetChange( ImmutableArray textChanges, - int? cursorPosition, - ImmutableArray placeholders) + int cursorPosition, + ImmutableArray placeholders) { if (textChanges.IsEmpty) { diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetStringItem.cs b/src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs similarity index 56% rename from src/Features/Core/Portable/Snippets/RoslynLSPSnippetStringItem.cs rename to src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs index ff307218fd58c..09ca32cc2f69a 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetStringItem.cs +++ b/src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs @@ -4,32 +4,31 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Text; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Snippets { - internal readonly struct RoslynLSPSnippetStringItem + internal readonly struct SnippetPlaceholder { /// /// The identifier in the snippet that needs to be renamed. /// Will be null in the case of the final tab stop location, /// the '$0' case. /// - public readonly string? Identifier; + public readonly string Identifier; /// - /// The value associated with the identifier. - /// EX: if (${1:true}) - /// {$0 - /// } - /// The '1' and '0' are represented by this value. + /// The spans associated with the identifier that will need to + /// be converted into LSP formatted strings. /// - public readonly int Priority; + public readonly ImmutableArray PlaceHolderPositions; - public RoslynLSPSnippetStringItem(string? identifier, int priority) + public SnippetPlaceholder(string identifier, ImmutableArray placeholderPositions) { Identifier = identifier; - Priority = priority; + PlaceHolderPositions = placeholderPositions; } } } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs index 89f0bccaae519..39de55e0202a4 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs @@ -74,14 +74,10 @@ private async Task GenerateSnippetTextChangeAsync(Document document, return new TextChange(TextSpan.FromBounds(position, position), expressionStatement.NormalizeWhitespace().ToFullString()); } - protected override int? GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget) + protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget) { var openParenToken = GetOpenParenToken(caretTarget, syntaxFacts); - if (openParenToken is null) - { - return null; - } - + Contract.ThrowIfNull(openParenToken); return openParenToken.Value.Span.End; } @@ -102,19 +98,9 @@ protected override async Task AnnotateNodesToReformatAsync(Document return root.ReplaceNode(snippetExpressionNode, reformatSnippetNode); } - protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var arrayBuilder); - var openParenToken = GetOpenParenToken(node, syntaxFacts); - - if (openParenToken is null) - { - return ImmutableArray.Empty; - } - - arrayBuilder.Add(new RoslynLSPSnippetItem(identifier: null, priority: 0, caretPosition: openParenToken.Value.Span.End, placeholderSpans: ImmutableArray.Empty)); - - return arrayBuilder.ToImmutable(); + return ImmutableArray.Empty; } private static SyntaxToken? GetOpenParenToken(SyntaxNode node, ISyntaxFacts syntaxFacts) diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs index 7e0adb48e8d8d..70e49f45ece12 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs @@ -48,7 +48,7 @@ private static TextChange GenerateSnippetTextChange(Document document, int posit return new TextChange(TextSpan.FromBounds(position, position), ifStatement.ToFullString()); } - protected override int? GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget) + protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget) { syntaxFacts.GetPartsOfIfStatement(caretTarget, out _, out var statement); @@ -71,15 +71,12 @@ protected override async Task AnnotateNodesToReformatAsync(Document return root.ReplaceNode(snippetExpressionNode, reformatSnippetNode); } - protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) + protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { - using var _ = ArrayBuilder.GetInstance(out var arrayBuilder); - syntaxFacts.GetPartsOfIfStatement(node, out var condition, out var statement); + using var _ = ArrayBuilder.GetInstance(out var arrayBuilder); + syntaxFacts.GetPartsOfIfStatement(node, out var condition, out var _); - // Need to get the statement span start and add 1 to insert between the the curly braces - arrayBuilder.Add(new RoslynLSPSnippetItem(identifier: null, priority: 0, caretPosition: statement.SpanStart + 1, placeholderSpans: ImmutableArray.Empty)); - - arrayBuilder.Add(new RoslynLSPSnippetItem(identifier: condition.ToString(), priority: 1, caretPosition: null, placeholderSpans: ImmutableArray.Create(new TextSpan(condition.SpanStart, 0)))); + arrayBuilder.Add(new SnippetPlaceholder(identifier: condition.ToString(), placeholderPositions: ImmutableArray.Create(condition.SpanStart))); return arrayBuilder.ToImmutableArray(); } diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs index 2f67c7c032733..7b675e72864fd 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -47,7 +47,7 @@ internal abstract class AbstractSnippetProvider : ISnippetProvider /// Method for each snippet to locate the inserted SyntaxNode to reformat /// protected abstract Task AnnotateNodesToReformatAsync(Document document, SyntaxAnnotation reformatAnnotation, SyntaxAnnotation cursorAnnotation, int position, CancellationToken cancellationToken); - protected abstract int? GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget); + protected abstract int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget); /// /// Every SnippetProvider will need a method to retrieve the "main" snippet syntax once it has been inserted as a TextChange. @@ -57,7 +57,7 @@ internal abstract class AbstractSnippetProvider : ISnippetProvider /// /// Method to find the locations that must be renamed and where tab stops must be inserted into the snippet. /// - protected abstract ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken); + protected abstract ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken); /// /// Determines if the location is valid for a snippet, From 6ef9f5df55611ba2a219f08a390605878cc359e8 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 2 May 2022 10:33:53 -0700 Subject: [PATCH 34/58] comments --- .../Snippets/SnippetCompletionItem.cs | 7 +++--- .../Snippets/RoslynLSPSnippetConverter.cs | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs index 72a5d77fc71ba..1ef95d39a95f6 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/SnippetCompletionItem.cs @@ -11,6 +11,7 @@ namespace Microsoft.CodeAnalysis.Completion.Providers.Snippets internal class SnippetCompletionItem { public static string LSPSnippetKey = "LSPSnippet"; + public static string SnippetIdentifierKey = "SnippetIdentifier"; public static CompletionItem Create( string displayText, @@ -21,7 +22,7 @@ public static CompletionItem Create( { var props = ImmutableDictionary.Empty .Add("Position", position.ToString()) - .Add("SnippetIdentifier", snippetIdentifier); + .Add(SnippetIdentifierKey, snippetIdentifier); return CommonCompletionItem.Create( displayText: displayText, @@ -34,7 +35,7 @@ public static CompletionItem Create( public static string GetSnippetIdentifier(CompletionItem item) { - Contract.ThrowIfFalse(item.Properties.TryGetValue("SnippetIdentifier", out var text)); + Contract.ThrowIfFalse(item.Properties.TryGetValue(SnippetIdentifierKey, out var text)); return text; } @@ -47,7 +48,7 @@ public static int GetInvocationPosition(CompletionItem item) public static bool IsSnippet(CompletionItem item) { - return item.Properties.TryGetValue("SnippetIdentifier", out var _); + return item.Properties.TryGetValue(SnippetIdentifierKey, out var _); } } } diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs index fbf899b25f34c..d96183194875d 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -23,6 +23,10 @@ public static async Task GenerateLSPSnippetAsync(Document document, int return ConvertToLSPSnippetString(extendedTextChange, placeholders, caretPosition); } + /// + /// Iterates through every index in the snippet string and determines where the + /// LSP formatted chunks should be inserted for each placeholder. + /// private static string ConvertToLSPSnippetString(TextChange textChange, ImmutableArray placeholders, int caretPosition) { var textChangeStart = textChange.Span.Start; @@ -61,6 +65,10 @@ private static string ConvertToLSPSnippetString(TextChange textChange, Immutable return lspSnippetString.ToString(); } + /// + /// Preprocesses the list of placeholders into a dictionary that maps the insertion position + /// in the string to the placeholder's identifier and the number associated with it. + /// private static Dictionary GetMapOfSpanStartsToLSPStringItem(ImmutableArray placeholders, int textChangeStart) { var map = new Dictionary(); @@ -77,6 +85,11 @@ private static string ConvertToLSPSnippetString(TextChange textChange, Immutable return map; } + /// + /// Tries to see if a value exists at that position in the map, and if so it + /// generates a string that is LSP formatted as well as passes back the length + /// of the identifier so that it can skip forward in the string. + /// private static (string str, int strLength) GetStringInPosition(Dictionary map, int position) { if (map.TryGetValue(position, out var placeholderInfo)) @@ -87,6 +100,12 @@ private static (string str, int strLength) GetStringInPosition(Dictionary + /// We need to extend the snippet's TextChange if any of the placeholders or + /// if the caret position comes before or after the span of the TextChange. + /// If so, then find the new string that encompasses all of the placeholders + /// and caret position. + /// private static async Task ExtendSnippetTextChangeAsync(Document document, TextChange textChange, ImmutableArray placeholders, int caretPosition) { var extendedSpan = GetUpdatedTextSpan(textChange, placeholders, caretPosition); @@ -103,6 +122,11 @@ private static async Task ExtendSnippetTextChangeAsync(Document docu return newTextChange; } + /// + /// Iterates through the placeholders and determines if any of the positions + /// come before or after what is indicated by the snippet's TextChange. + /// If so, adjust the starting and ending position accordingly. + /// private static TextSpan GetUpdatedTextSpan(TextChange textChange, ImmutableArray placeholders, int caretPosition) { var textSpanLength = textChange.NewText!.Length; From c281d2ab548733815f5de74eb11cfe10332ece0c Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 2 May 2022 11:41:38 -0700 Subject: [PATCH 35/58] more fixes --- .../Snippets/RoslynLSPSnippetConvertTests.cs | 22 +++++++++++++++++++ .../Snippets/RoslynLSPSnippetConverter.cs | 8 +++++++ 2 files changed, 30 insertions(+) diff --git a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs index 40eceecafb344..6233c50b9dcfa 100644 --- a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs +++ b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs @@ -60,6 +60,28 @@ public Task TestExtendSnippetTextChangeBackwards() return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("true", placeholders)), textChange); } + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestForLoopSnippet() + { + var markup = +@"[|for (var {|placeholder1:i|} = 0; {|placeholder1:i|} < {|placeholder2:length|}; {|placeholder1:i|}++) +{$$ +}|]"; + + var expectedLSPSnippet = +@"for (var ${1:i} = 0; ${1:i} < ${2:length}; ${1:i}++) +{$0 +}"; + MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); + var stringSpan = dictionary[""].First(); + var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString); + var placeholders1 = dictionary["placeholder1"].Select(span => span.Start).ToImmutableArray(); + var placeholders2 = dictionary["placeholder2"].Select(span => span.Start).ToImmutableArray(); + return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create( + new SnippetPlaceholder("i", placeholders1), + new SnippetPlaceholder("length", placeholders2)), textChange); + } + protected static TestWorkspace CreateWorkspaceFromCode(string code) => TestWorkspace.CreateCSharp(code); diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs index d96183194875d..bf59638698afc 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -39,6 +39,14 @@ private static string ConvertToLSPSnippetString(TextChange textChange, Immutable if (i == caretPosition - textChangeStart) { lspSnippetString.Append("$0"); + + // Special case for cursor position since they will occur between positions + // so we still wants to insert the character following the cursor position. + if (i < textChangeText.Length) + { + lspSnippetString.Append(textChangeText[i]); + } + i++; } From 70025639b84af5e2586d24fec191660da7e3a246 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 2 May 2022 11:45:26 -0700 Subject: [PATCH 36/58] bug --- .../Core/Portable/Snippets/RoslynLSPSnippetConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs index bf59638698afc..72cdaa7413bde 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -148,7 +148,7 @@ private static TextSpan GetUpdatedTextSpan(TextChange textChange, ImmutableArray { if (startPosition > position) { - endPosition += startPosition - caretPosition; + endPosition += startPosition - position; startPosition = position; } From 1940bb2f16276b51e5e88c2b61eceab1a16b6c2d Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 2 May 2022 11:51:05 -0700 Subject: [PATCH 37/58] fix --- .../Test/Snippets/RoslynLSPSnippetConvertTests.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs index 6233c50b9dcfa..f8ae8e3b40f87 100644 --- a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs +++ b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs @@ -50,7 +50,7 @@ public Task TestExtendSnippetTextChangeBackwards() }|]"; var expectedLSPSnippet = -@"$0if (${1:true}) +@"$0 if (${1:true}) { }"; MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); @@ -89,9 +89,12 @@ private static async Task TestAsync(string markup, string expectedLSPSnippet, in { using var workspace = CreateWorkspaceFromCode(markup); var document = workspace.CurrentSolution.GetDocument(workspace.Documents.First().Id); - var lspSnippetString = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document, cursorPosition!.Value, placeholders, textChange).ConfigureAwait(false); - AssertEx.EqualOrDiff(expectedLSPSnippet, lspSnippetString); + if (document is not null) + { + var lspSnippetString = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document, cursorPosition!.Value, placeholders, textChange).ConfigureAwait(false); + AssertEx.EqualOrDiff(expectedLSPSnippet, lspSnippetString); + } } } } From e6254ed30102e36640d183554eddd802f1d96800 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 2 May 2022 13:28:25 -0700 Subject: [PATCH 38/58] options changed --- .../SnippetProviders/AbstractSnippetProvider.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs index 7b675e72864fd..6a2d71c40826d 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -149,18 +149,20 @@ private async Task CleanupDocumentAsync( { if (document.SupportsSyntaxTree) { - var options = await CodeCleanupOptions.FromDocumentAsync(document, fallbackOptions: null, cancellationToken).ConfigureAwait(false); + var addImportPlacementOptions = await document.GetAddImportPlacementOptionsAsync(fallbackOptions: null, cancellationToken).ConfigureAwait(false); + var simplifierOptions = await document.GetSimplifierOptionsAsync(fallbackOptions: null, cancellationToken).ConfigureAwait(false); + var syntaxFormattingOptions = await document.GetSyntaxFormattingOptionsAsync(fallbackOptions: null, cancellationToken).ConfigureAwait(false); document = await ImportAdder.AddImportsFromSymbolAnnotationAsync( - document, _findSnippetAnnotation, options.AddImportOptions, cancellationToken: cancellationToken).ConfigureAwait(false); + document, _findSnippetAnnotation, addImportPlacementOptions, cancellationToken: cancellationToken).ConfigureAwait(false); - document = await Simplifier.ReduceAsync(document, _findSnippetAnnotation, options.SimplifierOptions, cancellationToken: cancellationToken).ConfigureAwait(false); + document = await Simplifier.ReduceAsync(document, _findSnippetAnnotation, simplifierOptions, cancellationToken: cancellationToken).ConfigureAwait(false); // format any node with explicit formatter annotation - document = await Formatter.FormatAsync(document, _findSnippetAnnotation, options.FormattingOptions, cancellationToken: cancellationToken).ConfigureAwait(false); + document = await Formatter.FormatAsync(document, _findSnippetAnnotation, syntaxFormattingOptions, cancellationToken: cancellationToken).ConfigureAwait(false); // format any elastic whitespace - document = await Formatter.FormatAsync(document, SyntaxAnnotation.ElasticAnnotation, options.FormattingOptions, cancellationToken: cancellationToken).ConfigureAwait(false); + document = await Formatter.FormatAsync(document, SyntaxAnnotation.ElasticAnnotation, syntaxFormattingOptions, cancellationToken: cancellationToken).ConfigureAwait(false); } return document; From a4e669f619b32ea875493a50c6ebbbb6aef57221 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 2 May 2022 14:52:28 -0700 Subject: [PATCH 39/58] tests wrong and I did stupid stuff as a result --- .../Snippets/RoslynLSPSnippetConvertTests.cs | 44 +++++++++++++++++-- .../Snippets/RoslynLSPSnippetConverter.cs | 18 ++++---- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs index f8ae8e3b40f87..e5b2fe20013fd 100644 --- a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs +++ b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs @@ -23,7 +23,7 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.Snippets public class RoslynLSPSnippetConvertTests { [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] - public Task TestExtendSnippetTextChangeForwards() + public Task TestExtendSnippetTextChangeForwardsForCaret() { var markup = @"[|if ({|placeholder:true|}) @@ -42,7 +42,7 @@ public Task TestExtendSnippetTextChangeForwards() } [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] - public Task TestExtendSnippetTextChangeBackwards() + public Task TestExtendSnippetTextChangeBackwardsForCaret() { var markup = @"$$ [|if ({|placeholder:true|}) @@ -55,11 +55,49 @@ public Task TestExtendSnippetTextChangeBackwards() }"; MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); var stringSpan = dictionary[""].First(); - var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString.Substring(stringSpan.Start, stringSpan.Length - 1)); + var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString.Substring(stringSpan.Start)); var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("true", placeholders)), textChange); } + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeForwardsForPlaceholder() + { + var markup = +@"[|if (true) +{$$ +}|] {|placeholder:test|}"; + + var expectedLSPSnippet = +@"if (true) +{$0 +} ${1:test}"; + MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); + var stringSpan = dictionary[""].First(); + var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString[..stringSpan.Length]); + var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); + return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeBackwardsForPlaceholder() + { + var markup = +@"{|placeholder:test|} [|if (true) +{$$ +}|]"; + + var expectedLSPSnippet = +@"${1:test} if (true) +{$0 +}"; + MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); + var stringSpan = dictionary[""].First(); + var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString.Substring(stringSpan.Start)); + var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); + return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); + } + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] public Task TestForLoopSnippet() { diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs index 72cdaa7413bde..9eeefd47c5632 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -50,23 +50,23 @@ private static string ConvertToLSPSnippetString(TextChange textChange, Immutable i++; } - if (i < textChangeText.Length) + var (str, strLength) = GetStringInPosition(map, position: i); + if (str.IsEmpty()) { - var (str, strLength) = GetStringInPosition(map, position: i); - if (str.IsEmpty()) + if (i < textChangeText.Length) { lspSnippetString.Append(textChangeText[i]); i++; } else { - lspSnippetString.Append(str); - i += strLength; + break; } } else { - break; + lspSnippetString.Append(str); + i += strLength; } } @@ -148,11 +148,10 @@ private static TextSpan GetUpdatedTextSpan(TextChange textChange, ImmutableArray { if (startPosition > position) { - endPosition += startPosition - position; startPosition = position; } - if (startPosition + textSpanLength < position) + if (endPosition < position) { endPosition = position; } @@ -161,11 +160,10 @@ private static TextSpan GetUpdatedTextSpan(TextChange textChange, ImmutableArray if (startPosition > caretPosition) { - endPosition += startPosition - caretPosition; startPosition = caretPosition; } - if (startPosition + textSpanLength < caretPosition) + if (endPosition < caretPosition) { endPosition = caretPosition; } From 93c8e9a74d8c53e17627d3f0e8816faa4c6a4ecc Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 2 May 2022 14:55:57 -0700 Subject: [PATCH 40/58] remove unnecessary code --- .../Core/Portable/Snippets/RoslynLSPSnippetConverter.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs index 9eeefd47c5632..19939f68b9797 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -58,10 +58,6 @@ private static string ConvertToLSPSnippetString(TextChange textChange, Immutable lspSnippetString.Append(textChangeText[i]); i++; } - else - { - break; - } } else { From 75970d1e12a8c9b3f7c6d2a7d80c71fa95f4cf92 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 2 May 2022 14:58:01 -0700 Subject: [PATCH 41/58] comments --- .../Providers/Snippets/AbstractSnippetCompletionProvider.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs index ef51bf5079d20..2b54d9cb20398 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs @@ -47,6 +47,8 @@ public override async Task GetChangeAsync(Document document, C var allTextChanges = await allChangesDocument.GetTextChangesAsync(document, cancellationToken).ConfigureAwait(false); var change = Utilities.Collapse(allChangesText, allTextChanges.AsImmutable()); + + // Converts the snippet to an LSP formatted snippet string. var lspSnippet = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(allChangesDocument, snippet.CursorPosition, snippet.Placeholders, change).ConfigureAwait(false); var props = ImmutableDictionary.Empty .Add(SnippetCompletionItem.LSPSnippetKey, lspSnippet); From d26a3a41bb0dc93eb33e32dcef3225efda12aeca Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 2 May 2022 15:00:24 -0700 Subject: [PATCH 42/58] cleanup, need to add more tests --- .../Test/Snippets/RoslynLSPSnippetConvertTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs index e5b2fe20013fd..69855f1c3535f 100644 --- a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs +++ b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs @@ -55,7 +55,7 @@ public Task TestExtendSnippetTextChangeBackwardsForCaret() }"; MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); var stringSpan = dictionary[""].First(); - var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString.Substring(stringSpan.Start)); + var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString[stringSpan.Start..]); var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("true", placeholders)), textChange); } @@ -93,7 +93,7 @@ public Task TestExtendSnippetTextChangeBackwardsForPlaceholder() }"; MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); var stringSpan = dictionary[""].First(); - var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString.Substring(stringSpan.Start)); + var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString[stringSpan.Start..]); var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); } From fae4cff31c07a73f83eec7110cd7c8f5eb9f9000 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 2 May 2022 15:29:07 -0700 Subject: [PATCH 43/58] tests --- .../Snippets/RoslynLSPSnippetConvertTests.cs | 38 +++++++++++++++++++ .../Snippets/RoslynLSPSnippetConverter.cs | 4 ++ 2 files changed, 42 insertions(+) diff --git a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs index 69855f1c3535f..962be6c4d5b10 100644 --- a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs +++ b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs @@ -98,6 +98,44 @@ public Task TestExtendSnippetTextChangeBackwardsForPlaceholder() return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); } + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeForwardsForPlaceholderAndCaret() + { + var markup = +@"[|if (true) +{ +}|] {|placeholder:test|} $$"; + + var expectedLSPSnippet = +@"if (true) +{ +} ${1:test} $0"; + MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); + var stringSpan = dictionary[""].First(); + var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString[..stringSpan.Length]); + var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); + return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeBackwardsForPlaceholderAndCaret() + { + var markup = +@"{|placeholder:test|} $$[|if (true) +{ +}|]"; + + var expectedLSPSnippet = +@"${1:test} $0if (true) +{ +}"; + MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); + var stringSpan = dictionary[""].First(); + var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString[stringSpan.Start..]); + var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); + return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); + } + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] public Task TestForLoopSnippet() { diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs index 19939f68b9797..9eeefd47c5632 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -58,6 +58,10 @@ private static string ConvertToLSPSnippetString(TextChange textChange, Immutable lspSnippetString.Append(textChangeText[i]); i++; } + else + { + break; + } } else { From 08c22d6970b6e31dbdcbb27ef1ad6db260400f0f Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 2 May 2022 15:43:35 -0700 Subject: [PATCH 44/58] every iteration --- .../Snippets/RoslynLSPSnippetConvertTests.cs | 84 ++++++++++++++++++- 1 file changed, 80 insertions(+), 4 deletions(-) diff --git a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs index 962be6c4d5b10..fd51f5733f4f5 100644 --- a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs +++ b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs @@ -99,7 +99,7 @@ public Task TestExtendSnippetTextChangeBackwardsForPlaceholder() } [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] - public Task TestExtendSnippetTextChangeForwardsForPlaceholderAndCaret() + public Task TestExtendSnippetTextChangeForwardsForPlaceholderThenCaret() { var markup = @"[|if (true) @@ -118,15 +118,53 @@ public Task TestExtendSnippetTextChangeForwardsForPlaceholderAndCaret() } [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] - public Task TestExtendSnippetTextChangeBackwardsForPlaceholderAndCaret() + public Task TestExtendSnippetTextChangeForwardsForCaretThenPlaceholder() { var markup = -@"{|placeholder:test|} $$[|if (true) +@"[|if (true) +{ +}|] $$ {|placeholder:test|}"; + + var expectedLSPSnippet = +@"if (true) +{ +} $0 ${1:test}"; + MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); + var stringSpan = dictionary[""].First(); + var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString[..stringSpan.Length]); + var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); + return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeBackwardsForPlaceholderThenCaret() + { + var markup = +@"{|placeholder:test|} $$ [|if (true) +{ +}|]"; + + var expectedLSPSnippet = +@"${1:test} $0 if (true) +{ +}"; + MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); + var stringSpan = dictionary[""].First(); + var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString[stringSpan.Start..]); + var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); + return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeBackwardsForCaretThenPlaceholder() + { + var markup = +@"$$ {|placeholder:test|} [|if (true) { }|]"; var expectedLSPSnippet = -@"${1:test} $0if (true) +@"$0 ${1:test} if (true) { }"; MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); @@ -136,6 +174,44 @@ public Task TestExtendSnippetTextChangeBackwardsForPlaceholderAndCaret() return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); } + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeBackwardsForCaretForwardsForPlaceholder() + { + var markup = +@"$$ [|if (true) +{ +}|] {|placeholder:test|}"; + + var expectedLSPSnippet = +@"$0 if (true) +{ +} ${1:test}"; + MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); + var stringSpan = dictionary[""].First(); + var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString.Substring(stringSpan.Start, stringSpan.Length)); + var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); + return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeBackwardsForPlaceholderForwardsForCaret() + { + var markup = +@"{|placeholder:test|} [|if (true) +{ +}|] $$"; + + var expectedLSPSnippet = +@"${1:test} if (true) +{ +} $0"; + MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); + var stringSpan = dictionary[""].First(); + var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString.Substring(stringSpan.Start, stringSpan.Length)); + var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); + return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); + } + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] public Task TestForLoopSnippet() { From 973b95b8aaf60d9d47dfa0dea21fd98286871072 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 2 May 2022 15:47:51 -0700 Subject: [PATCH 45/58] regions for easier viewing --- .../Test/Snippets/RoslynLSPSnippetConvertTests.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs index fd51f5733f4f5..c50862fcad9be 100644 --- a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs +++ b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs @@ -22,6 +22,8 @@ namespace Microsoft.CodeAnalysis.Editor.UnitTests.Snippets [UseExportProvider] public class RoslynLSPSnippetConvertTests { + #region Edgecase extend TextChange tests + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] public Task TestExtendSnippetTextChangeForwardsForCaret() { @@ -212,6 +214,10 @@ public Task TestExtendSnippetTextChangeBackwardsForPlaceholderForwardsForCaret() return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); } + #endregion + + #region LSP Snippet generation tests + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] public Task TestForLoopSnippet() { @@ -234,6 +240,8 @@ public Task TestForLoopSnippet() new SnippetPlaceholder("length", placeholders2)), textChange); } + #endregion + protected static TestWorkspace CreateWorkspaceFromCode(string code) => TestWorkspace.CreateCSharp(code); From ed9492216b3f1c3ee0a45185e473b43656fa566c Mon Sep 17 00:00:00 2001 From: akhera99 Date: Tue, 3 May 2022 10:39:26 -0700 Subject: [PATCH 46/58] no changes --- .../Snippets/SnippetProviders/AbstractSnippetProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs index 6a2d71c40826d..4db5e05e93ebb 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -137,9 +137,9 @@ public async Task GetSnippetAsync(Document document, int position } var nodeWithTrivia = node.ReplaceTokens(node.DescendantTokens(descendIntoTrivia: true), - (oldToken, _) => oldToken.WithAdditionalAnnotations(SyntaxAnnotation.ElasticAnnotation) - .WithAppendedTrailingTrivia(syntaxFacts.ElasticMarker) - .WithPrependedLeadingTrivia(syntaxFacts.ElasticMarker)); + (oldtoken, _) => oldtoken.WithAdditionalAnnotations(SyntaxAnnotation.ElasticAnnotation) + .WithAppendedTrailingTrivia(syntaxFacts.ElasticMarker) + .WithPrependedLeadingTrivia(syntaxFacts.ElasticMarker)); return nodeWithTrivia; } From 1a87816cff2ae6cfc889b23ff60b5125e4de8a24 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Tue, 3 May 2022 14:31:34 -0700 Subject: [PATCH 47/58] feedback --- .../AsyncCompletion/CommitManager.cs | 6 +- .../Core/Snippets/RoslynLSPSnippetExpander.cs | 4 +- .../Snippets/RoslynLSPSnippetConvertTests.cs | 107 ++++++++---------- 3 files changed, 51 insertions(+), 66 deletions(-) diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs index 4cc3cc29cffb2..2bf7814b0894f 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs @@ -245,11 +245,11 @@ private AsyncCompletionData.CommitResult Commit( // TryExpand method and determine if it succeeded or not. if (SnippetCompletionItem.IsSnippet(roslynItem)) { - change.Properties.TryGetValue(SnippetCompletionItem.LSPSnippetKey, out var lspSnippetText); + var lspSnippetText = change.Properties[SnippetCompletionItem.LSPSnippetKey]; - if (!_roslynLSPSnippetExpander.TryExpand(change.TextChange.Span, lspSnippetText!, _textView, triggerSnapshot)) + if (!_roslynLSPSnippetExpander.TryExpand(change.TextChange.Span, lspSnippetText, _textView, triggerSnapshot)) { - FatalError.ReportAndCatch(new InvalidOperationException(""), ErrorSeverity.Critical); + return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None); } return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None); diff --git a/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs b/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs index cceedaa240806..be59ade583079 100644 --- a/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs +++ b/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs @@ -7,6 +7,7 @@ using System.ComponentModel.Composition; using System.Reflection; using System.Text; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.Text; @@ -55,8 +56,9 @@ public bool TryExpand(TextSpan textSpan, string lspSnippetText, ITextView textVi var expandMethodResult = _expanderMethodInfo!.Invoke(_lspSnippetExpander, new object[] { textEdit, textView, textSnapshot }); return expandMethodResult is not null && (bool)expandMethodResult; } - catch + catch (Exception e) { + FatalError.ReportAndCatch(e); return false; } } diff --git a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs index c50862fcad9be..5f9671a1a78bb 100644 --- a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs +++ b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Snippets; using Microsoft.CodeAnalysis.Test.Utilities; @@ -36,11 +37,8 @@ public Task TestExtendSnippetTextChangeForwardsForCaret() @"if (${1:true}) { } $0"; - MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); - var stringSpan = dictionary[""].First(); - var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString[..stringSpan.Length]); - var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); - return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("true", placeholders)), textChange); + + return TestAsync(markup, expectedLSPSnippet); } [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] @@ -55,11 +53,8 @@ public Task TestExtendSnippetTextChangeBackwardsForCaret() @"$0 if (${1:true}) { }"; - MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); - var stringSpan = dictionary[""].First(); - var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString[stringSpan.Start..]); - var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); - return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("true", placeholders)), textChange); + + return TestAsync(markup, expectedLSPSnippet); } [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] @@ -74,11 +69,8 @@ public Task TestExtendSnippetTextChangeForwardsForPlaceholder() @"if (true) {$0 } ${1:test}"; - MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); - var stringSpan = dictionary[""].First(); - var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString[..stringSpan.Length]); - var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); - return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); + + return TestAsync(markup, expectedLSPSnippet); } [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] @@ -93,11 +85,8 @@ public Task TestExtendSnippetTextChangeBackwardsForPlaceholder() @"${1:test} if (true) {$0 }"; - MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); - var stringSpan = dictionary[""].First(); - var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString[stringSpan.Start..]); - var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); - return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); + + return TestAsync(markup, expectedLSPSnippet); } [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] @@ -112,11 +101,8 @@ public Task TestExtendSnippetTextChangeForwardsForPlaceholderThenCaret() @"if (true) { } ${1:test} $0"; - MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); - var stringSpan = dictionary[""].First(); - var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString[..stringSpan.Length]); - var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); - return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); + + return TestAsync(markup, expectedLSPSnippet); } [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] @@ -131,11 +117,8 @@ public Task TestExtendSnippetTextChangeForwardsForCaretThenPlaceholder() @"if (true) { } $0 ${1:test}"; - MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); - var stringSpan = dictionary[""].First(); - var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString[..stringSpan.Length]); - var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); - return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); + + return TestAsync(markup, expectedLSPSnippet); } [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] @@ -150,11 +133,8 @@ public Task TestExtendSnippetTextChangeBackwardsForPlaceholderThenCaret() @"${1:test} $0 if (true) { }"; - MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); - var stringSpan = dictionary[""].First(); - var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString[stringSpan.Start..]); - var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); - return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); + + return TestAsync(markup, expectedLSPSnippet); } [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] @@ -169,11 +149,8 @@ public Task TestExtendSnippetTextChangeBackwardsForCaretThenPlaceholder() @"$0 ${1:test} if (true) { }"; - MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); - var stringSpan = dictionary[""].First(); - var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString[stringSpan.Start..]); - var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); - return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); + + return TestAsync(markup, expectedLSPSnippet); } [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] @@ -188,11 +165,8 @@ public Task TestExtendSnippetTextChangeBackwardsForCaretForwardsForPlaceholder() @"$0 if (true) { } ${1:test}"; - MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); - var stringSpan = dictionary[""].First(); - var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString.Substring(stringSpan.Start, stringSpan.Length)); - var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); - return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); + + return TestAsync(markup, expectedLSPSnippet); } [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] @@ -207,11 +181,8 @@ public Task TestExtendSnippetTextChangeBackwardsForPlaceholderForwardsForCaret() @"${1:test} if (true) { } $0"; - MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); - var stringSpan = dictionary[""].First(); - var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString.Substring(stringSpan.Start, stringSpan.Length)); - var placeholders = dictionary["placeholder"].Select(span => span.Start).ToImmutableArray(); - return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create(new SnippetPlaceholder("test", placeholders)), textChange); + + return TestAsync(markup, expectedLSPSnippet); } #endregion @@ -230,14 +201,8 @@ public Task TestForLoopSnippet() @"for (var ${1:i} = 0; ${1:i} < ${2:length}; ${1:i}++) {$0 }"; - MarkupTestFile.GetPositionAndSpans(markup, out var outString, out var cursorPosition, out IDictionary> dictionary); - var stringSpan = dictionary[""].First(); - var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), outString); - var placeholders1 = dictionary["placeholder1"].Select(span => span.Start).ToImmutableArray(); - var placeholders2 = dictionary["placeholder2"].Select(span => span.Start).ToImmutableArray(); - return TestAsync(markup, expectedLSPSnippet, cursorPosition, ImmutableArray.Create( - new SnippetPlaceholder("i", placeholders1), - new SnippetPlaceholder("length", placeholders2)), textChange); + + return TestAsync(markup, expectedLSPSnippet); } #endregion @@ -245,16 +210,34 @@ public Task TestForLoopSnippet() protected static TestWorkspace CreateWorkspaceFromCode(string code) => TestWorkspace.CreateCSharp(code); - private static async Task TestAsync(string markup, string expectedLSPSnippet, int? cursorPosition, ImmutableArray placeholders, TextChange textChange) + private static async Task TestAsync(string markup, string output) { + MarkupTestFile.GetPositionAndSpans(markup, out var text, out var cursorPosition, out IDictionary> placeholderDictionary); + var stringSpan = placeholderDictionary[""].First(); + var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), text.Substring(stringSpan.Start, stringSpan.Length)); + var placeholders = GetSnippetPlaceholders(text, placeholderDictionary); using var workspace = CreateWorkspaceFromCode(markup); var document = workspace.CurrentSolution.GetDocument(workspace.Documents.First().Id); - if (document is not null) + var lspSnippetString = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document!, cursorPosition!.Value, placeholders, textChange).ConfigureAwait(false); + AssertEx.EqualOrDiff(output, lspSnippetString); + } + + private static ImmutableArray GetSnippetPlaceholders(string text, IDictionary> placeholderDictionary) + { + using var _ = ArrayBuilder.GetInstance(out var arrayBuilder); + foreach (var kvp in placeholderDictionary) { - var lspSnippetString = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document, cursorPosition!.Value, placeholders, textChange).ConfigureAwait(false); - AssertEx.EqualOrDiff(expectedLSPSnippet, lspSnippetString); + if (kvp.Key.Length > 0) + { + var spans = kvp.Value; + var identifier = text.Substring(spans[0].Start, spans[0].Length); + var placeholders = spans.Select(span => span.Start).ToImmutableArray(); + arrayBuilder.Add(new SnippetPlaceholder(identifier, placeholders)); + } } + + return arrayBuilder.AsImmutable(); } } } From 6baf3b38efbe9ac7b6397538c97c36f0f84e15d0 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Tue, 3 May 2022 16:31:31 -0700 Subject: [PATCH 48/58] pr feedback --- .../Core/Snippets/RoslynLSPSnippetExpander.cs | 12 +- .../Snippets/RoslynLSPSnippetConvertTests.cs | 2 +- .../Snippets/CSharpIfSnippetProvider.cs | 7 ++ .../AbstractSnippetCompletionProvider.cs | 2 +- .../Snippets/RoslynLSPSnippetConverter.cs | 107 ++++++++---------- .../Portable/Snippets/SnippetPlaceholder.cs | 11 +- .../AbstractIfSnippetProvider.cs | 12 +- .../AbstractSnippetProvider.cs | 4 +- .../Services/SyntaxFacts/CSharpSyntaxFacts.cs | 10 -- .../Core/Services/SyntaxFacts/ISyntaxFacts.cs | 2 - .../SyntaxFacts/VisualBasicSyntaxFacts.vb | 4 - 11 files changed, 82 insertions(+), 91 deletions(-) diff --git a/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs b/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs index be59ade583079..173405f3759df 100644 --- a/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs +++ b/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs @@ -54,7 +54,17 @@ public bool TryExpand(TextSpan textSpan, string lspSnippetText, ITextView textVi { // ExpanderMethodInfo should not be null at this point. var expandMethodResult = _expanderMethodInfo!.Invoke(_lspSnippetExpander, new object[] { textEdit, textView, textSnapshot }); - return expandMethodResult is not null && (bool)expandMethodResult; + if (expandMethodResult is null) + { + throw new Exception("The result of the invoked LSP snippet expander is null."); + } + + if (!(bool)expandMethodResult) + { + throw new Exception("The invoked LSP snippet expander came back as false."); + } + + return true; } catch (Exception e) { diff --git a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs index 5f9671a1a78bb..dff34f6165b24 100644 --- a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs +++ b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs @@ -219,7 +219,7 @@ private static async Task TestAsync(string markup, string output) using var workspace = CreateWorkspaceFromCode(markup); var document = workspace.CurrentSolution.GetDocument(workspace.Documents.First().Id); - var lspSnippetString = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document!, cursorPosition!.Value, placeholders, textChange).ConfigureAwait(false); + var lspSnippetString = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document!, cursorPosition!.Value, placeholders, textChange, CancellationToken.None).ConfigureAwait(false); AssertEx.EqualOrDiff(output, lspSnippetString); } diff --git a/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs b/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs index 5b935419784ae..d9893eac487ae 100644 --- a/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs +++ b/src/Features/CSharp/Portable/Snippets/CSharpIfSnippetProvider.cs @@ -33,5 +33,12 @@ internal class CSharpIfSnippetProvider : AbstractIfSnippetProvider public CSharpIfSnippetProvider() { } + + protected override void GetIfStatementConditionAndCursorPosition(SyntaxNode node, out SyntaxNode condition, out int cursorPositionNode) + { + var ifStatement = (IfStatementSyntax)node; + condition = ifStatement.Condition; + cursorPositionNode = ifStatement.Statement.SpanStart + 1; + } } } diff --git a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs index 2b54d9cb20398..6787d2b7f3b61 100644 --- a/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/Snippets/AbstractSnippetCompletionProvider.cs @@ -49,7 +49,7 @@ public override async Task GetChangeAsync(Document document, C var change = Utilities.Collapse(allChangesText, allTextChanges.AsImmutable()); // Converts the snippet to an LSP formatted snippet string. - var lspSnippet = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(allChangesDocument, snippet.CursorPosition, snippet.Placeholders, change).ConfigureAwait(false); + var lspSnippet = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(allChangesDocument, snippet.CursorPosition, snippet.Placeholders, change, cancellationToken).ConfigureAwait(false); var props = ImmutableDictionary.Empty .Add(SnippetCompletionItem.LSPSnippetKey, lspSnippet); diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs index 9eeefd47c5632..b75badd416da8 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -17,9 +18,13 @@ namespace Microsoft.CodeAnalysis.Snippets { internal static class RoslynLSPSnippetConverter { - public static async Task GenerateLSPSnippetAsync(Document document, int caretPosition, ImmutableArray placeholders, TextChange collapsedTextChange) + /// + /// Extends the TextChange to encompass all placeholder positions as well as caret position. + /// Generates a LSP formatted snippet from a TextChange, list of placeholders, and caret position. + /// + public static async Task GenerateLSPSnippetAsync(Document document, int caretPosition, ImmutableArray placeholders, TextChange textChange, CancellationToken cancellationToken) { - var extendedTextChange = await ExtendSnippetTextChangeAsync(document, collapsedTextChange, placeholders, caretPosition).ConfigureAwait(false); + var extendedTextChange = await ExtendSnippetTextChangeAsync(document, textChange, placeholders, caretPosition, cancellationToken).ConfigureAwait(false); return ConvertToLSPSnippetString(extendedTextChange, placeholders, caretPosition); } @@ -30,10 +35,17 @@ public static async Task GenerateLSPSnippetAsync(Document document, int private static string ConvertToLSPSnippetString(TextChange textChange, ImmutableArray placeholders, int caretPosition) { var textChangeStart = textChange.Span.Start; - var textChangeText = textChange.NewText!; + var textChangeText = textChange.NewText; + Contract.ThrowIfNull(textChangeText); + using var _ = PooledStringBuilder.GetInstance(out var lspSnippetString); - var map = GetMapOfSpanStartsToLSPStringItem(placeholders, textChangeStart); + using var disposer = PooledDictionary.GetInstance(out var dictionary); + GetMapOfSpanStartsToLSPStringItem(ref dictionary, placeholders, textChangeStart); + // Need to go through the length + 1 since caret postions occur before and after the + // character position. + // If there is a caret at the end of the line, then it's position + // will be equivalent to the length of the TextChange. for (var i = 0; i < textChangeText.Length + 1;) { if (i == caretPosition - textChangeStart) @@ -42,6 +54,8 @@ private static string ConvertToLSPSnippetString(TextChange textChange, Immutable // Special case for cursor position since they will occur between positions // so we still wants to insert the character following the cursor position. + // Will not happen for placeholders since they have a direct mapping from position + // of the identifier to their position in the TextChange text. if (i < textChangeText.Length) { lspSnippetString.Append(textChangeText[i]); @@ -50,8 +64,17 @@ private static string ConvertToLSPSnippetString(TextChange textChange, Immutable i++; } - var (str, strLength) = GetStringInPosition(map, position: i); - if (str.IsEmpty()) + //Tries to see if a value exists at that position in the map, and if so it + // generates a string that is LSP formatted. + if (dictionary.TryGetValue(i, out var placeholderInfo)) + { + var str = $"${{{placeholderInfo.priority}:{placeholderInfo.identifier}}}"; + lspSnippetString.Append(str); + + // Skip past the entire identifier in the TextChange text + i += placeholderInfo.identifier.Length; + } + else { if (i < textChangeText.Length) { @@ -63,11 +86,6 @@ private static string ConvertToLSPSnippetString(TextChange textChange, Immutable break; } } - else - { - lspSnippetString.Append(str); - i += strLength; - } } return lspSnippetString.ToString(); @@ -75,37 +93,20 @@ private static string ConvertToLSPSnippetString(TextChange textChange, Immutable /// /// Preprocesses the list of placeholders into a dictionary that maps the insertion position - /// in the string to the placeholder's identifier and the number associated with it. + /// in the string to the placeholder's identifier and the priority associated with it. /// - private static Dictionary GetMapOfSpanStartsToLSPStringItem(ImmutableArray placeholders, int textChangeStart) + private static void GetMapOfSpanStartsToLSPStringItem(ref PooledDictionary dictionary, ImmutableArray placeholders, int textChangeStart) { - var map = new Dictionary(); - for (var i = 0; i < placeholders.Length; i++) { var placeholder = placeholders[i]; foreach (var position in placeholder.PlaceHolderPositions) { - map.Add(position - textChangeStart, (placeholder.Identifier, i + 1)); + // i + 1 since the placeholder priority is set according to the index in the + // placeholders array, starting at 1. + dictionary.Add(position - textChangeStart, (placeholder.Identifier, i + 1)); } } - - return map; - } - - /// - /// Tries to see if a value exists at that position in the map, and if so it - /// generates a string that is LSP formatted as well as passes back the length - /// of the identifier so that it can skip forward in the string. - /// - private static (string str, int strLength) GetStringInPosition(Dictionary map, int position) - { - if (map.TryGetValue(position, out var placeholderInfo)) - { - return ($"${{{placeholderInfo.priority}:{placeholderInfo.identifier}}}", placeholderInfo.identifier.Length); - } - - return (string.Empty, 0); } /// @@ -113,8 +114,10 @@ private static (string str, int strLength) GetStringInPosition(Dictionary - private static async Task ExtendSnippetTextChangeAsync(Document document, TextChange textChange, ImmutableArray placeholders, int caretPosition) + private static async Task ExtendSnippetTextChangeAsync(Document document, TextChange textChange, ImmutableArray placeholders, int caretPosition, CancellationToken cancellationToken) { var extendedSpan = GetUpdatedTextSpan(textChange, placeholders, caretPosition); @@ -123,7 +126,7 @@ private static async Task ExtendSnippetTextChangeAsync(Document docu return textChange; } - var documentText = await document.GetTextAsync().ConfigureAwait(false); + var documentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var newString = documentText.ToString(extendedSpan); var newTextChange = new TextChange(new TextSpan(extendedSpan.Start, 0), newString); @@ -137,36 +140,16 @@ private static async Task ExtendSnippetTextChangeAsync(Document docu /// private static TextSpan GetUpdatedTextSpan(TextChange textChange, ImmutableArray placeholders, int caretPosition) { - var textSpanLength = textChange.NewText!.Length; + var textChangeText = textChange.NewText; + Contract.ThrowIfNull(textChangeText); var startPosition = textChange.Span.Start; - var endPosition = textChange.Span.Start + textSpanLength; - - foreach (var placeholder in placeholders) - { - foreach (var position in placeholder.PlaceHolderPositions) - { - if (startPosition > position) - { - startPosition = position; - } + var endPosition = textChange.Span.Start + textChangeText.Length; - if (endPosition < position) - { - endPosition = position; - } - } - } - - if (startPosition > caretPosition) - { - startPosition = caretPosition; - } - - if (endPosition < caretPosition) - { - endPosition = caretPosition; - } + startPosition = Math.Min(startPosition, placeholders.Min(placeholder => placeholder.PlaceHolderPositions.Min())); + endPosition = Math.Max(endPosition, placeholders.Max(placeholder => placeholder.PlaceHolderPositions.Max())); + startPosition = Math.Min(startPosition, caretPosition); + endPosition = Math.Max(endPosition, caretPosition); return TextSpan.FromBounds(startPosition, endPosition); } diff --git a/src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs b/src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs index 09ca32cc2f69a..cfb1c5a9b3832 100644 --- a/src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs +++ b/src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs @@ -14,17 +14,22 @@ internal readonly struct SnippetPlaceholder { /// /// The identifier in the snippet that needs to be renamed. - /// Will be null in the case of the final tab stop location, - /// the '$0' case. /// public readonly string Identifier; /// - /// The spans associated with the identifier that will need to + /// The positions associated with the identifier that will need to /// be converted into LSP formatted strings. /// public readonly ImmutableArray PlaceHolderPositions; + /// + /// Example: + /// For loop would have two placeholders: + /// for (var {1:i} = 0; {1:i} < {2:length}; {i}++) + /// Identifier: i, 3 associated positions + /// IdentifierL length, 1 associated position + /// public SnippetPlaceholder(string identifier, ImmutableArray placeholderPositions) { Identifier = identifier; diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs index 70e49f45ece12..8d9a4e2eb9e6a 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractIfSnippetProvider.cs @@ -26,6 +26,8 @@ internal abstract class AbstractIfSnippetProvider : AbstractSnippetProvider public override string SnippetDisplayName => FeaturesResources.Insert_an_if_statement; + protected abstract void GetIfStatementConditionAndCursorPosition(SyntaxNode node, out SyntaxNode condition, out int cursorPositionNode); + protected override async Task IsValidSnippetLocationAsync(Document document, int position, CancellationToken cancellationToken) { var semanticModel = await document.ReuseExistingSpeculativeModelAsync(position, cancellationToken).ConfigureAwait(false); @@ -50,10 +52,11 @@ private static TextChange GenerateSnippetTextChange(Document document, int posit protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget) { - syntaxFacts.GetPartsOfIfStatement(caretTarget, out _, out var statement); + GetIfStatementConditionAndCursorPosition(caretTarget, out _, out var cursorPosition); - // Need to get the statement span start and add 1 to insert between the the curly braces - return statement.SpanStart + 1; + // Place at the end of the node specified for cursor position. + // Is the statement node in C# and the "Then" keyword + return cursorPosition; } protected override async Task AnnotateNodesToReformatAsync(Document document, @@ -74,8 +77,7 @@ protected override async Task AnnotateNodesToReformatAsync(Document protected override ImmutableArray GetPlaceHolderLocationsList(SyntaxNode node, ISyntaxFacts syntaxFacts, CancellationToken cancellationToken) { using var _ = ArrayBuilder.GetInstance(out var arrayBuilder); - syntaxFacts.GetPartsOfIfStatement(node, out var condition, out var _); - + GetIfStatementConditionAndCursorPosition(node, out var condition, out var unusedVariable); arrayBuilder.Add(new SnippetPlaceholder(identifier: condition.ToString(), placeholderPositions: ImmutableArray.Create(condition.SpanStart))); return arrayBuilder.ToImmutableArray(); diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs index 4db5e05e93ebb..0a758e1a0fa58 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractSnippetProvider.cs @@ -106,8 +106,8 @@ public async Task GetSnippetAsync(Document document, int position var reformattedDocument = await CleanupDocumentAsync(formatAnnotatedSnippetDocument, cancellationToken).ConfigureAwait(false); var reformattedRoot = await reformattedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var caretTarget = reformattedRoot.GetAnnotatedNodes(_cursorAnnotation).SingleOrDefault(); - var mainChangeNode = reformattedRoot.GetAnnotatedNodes(_findSnippetAnnotation).SingleOrDefault(); + var caretTarget = reformattedRoot.GetAnnotatedNodes(_cursorAnnotation).FirstOrDefault(); + var mainChangeNode = reformattedRoot.GetAnnotatedNodes(_findSnippetAnnotation).FirstOrDefault(); // All the TextChanges from the original document. Will include any imports (if necessary) and all snippet associated // changes after having been formatted. diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs index 705bb5e204ae4..482b277ca6cc5 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Services/SyntaxFacts/CSharpSyntaxFacts.cs @@ -1654,13 +1654,6 @@ public void GetPartsOfConditionalExpression(SyntaxNode node, out SyntaxNode cond whenFalse = conditionalExpression.WhenFalse; } - public void GetPartsOfIfStatement(SyntaxNode node, out SyntaxNode condition, out SyntaxNode statement) - { - var ifStatement = (IfStatementSyntax)node; - condition = ifStatement.Condition; - statement = ifStatement.Statement; - } - public void GetPartsOfInvocationExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxNode? argumentList) { var invocation = (InvocationExpressionSyntax)node; @@ -1733,9 +1726,6 @@ public SyntaxNode GetExpressionOfAwaitExpression(SyntaxNode node) public SyntaxNode GetExpressionOfThrowExpression(SyntaxNode node) => ((ThrowExpressionSyntax)node).Expression; - public SyntaxNode GetStatementOfGlobalStatement(SyntaxNode node) - => ((GlobalStatementSyntax)node).Statement; - public SeparatedSyntaxList GetInitializersOfObjectMemberInitializer(SyntaxNode node) => node is InitializerExpressionSyntax(SyntaxKind.ObjectInitializerExpression) initExpr ? initExpr.Expressions : default; diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs index 232c4890b3837..f53ce39b9840b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Services/SyntaxFacts/ISyntaxFacts.cs @@ -538,7 +538,6 @@ void GetPartsOfInterpolationExpression(SyntaxNode node, void GetPartsOfCompilationUnit(SyntaxNode node, out SyntaxList imports, out SyntaxList attributeLists, out SyntaxList members); void GetPartsOfConditionalAccessExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxToken operatorToken, out SyntaxNode whenNotNull); void GetPartsOfConditionalExpression(SyntaxNode node, out SyntaxNode condition, out SyntaxNode whenTrue, out SyntaxNode whenFalse); - void GetPartsOfIfStatement(SyntaxNode node, out SyntaxNode condition, out SyntaxNode statement); void GetPartsOfInvocationExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxNode? argumentList); void GetPartsOfIsPatternExpression(SyntaxNode node, out SyntaxNode left, out SyntaxToken isToken, out SyntaxNode right); void GetPartsOfMemberAccessExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxToken operatorToken, out SyntaxNode name); @@ -561,7 +560,6 @@ void GetPartsOfInterpolationExpression(SyntaxNode node, SyntaxNode GetExpressionOfExpressionStatement(SyntaxNode node); SyntaxNode? GetExpressionOfReturnStatement(SyntaxNode node); SyntaxNode GetExpressionOfThrowExpression(SyntaxNode node); - SyntaxNode GetStatementOfGlobalStatement(SyntaxNode node); SyntaxNode? GetValueOfEqualsValueClause(SyntaxNode? node); SeparatedSyntaxList GetInitializersOfObjectMemberInitializer(SyntaxNode node); diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb index e440d25123763..d70034607a63b 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb @@ -1871,10 +1871,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices whenFalse = conditionalExpression.WhenFalse End Sub - Public Sub GetPartsOfIfStatement(node As SyntaxNode, ByRef condition As SyntaxNode, ByRef statement As SyntaxNode) Implements ISyntaxFacts.GetPartsOfIfStatement - Throw New NotImplementedException - End Sub - Public Sub GetPartsOfInvocationExpression(node As SyntaxNode, ByRef expression As SyntaxNode, ByRef argumentList As SyntaxNode) Implements ISyntaxFacts.GetPartsOfInvocationExpression Dim invocation = DirectCast(node, InvocationExpressionSyntax) expression = invocation.Expression From 5021c4ebea77b0ce53d1782dd8d7de18feaa0336 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Tue, 3 May 2022 20:09:23 -0700 Subject: [PATCH 49/58] remove unused method --- .../Services/SyntaxFacts/VisualBasicSyntaxFacts.vb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb index d70034607a63b..c0b518ef9e0e2 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/VisualBasic/Services/SyntaxFacts/VisualBasicSyntaxFacts.vb @@ -1937,10 +1937,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.LanguageServices Throw New NotImplementedException() End Function - Public Function GetStatementOfGlobalStatement(node As SyntaxNode) As SyntaxNode Implements ISyntaxFacts.GetStatementOfGlobalStatement - Throw New NotImplementedException() - End Function - Public Function GetInitializersOfObjectMemberInitializer(node As SyntaxNode) As SeparatedSyntaxList(Of SyntaxNode) Implements ISyntaxFacts.GetInitializersOfObjectMemberInitializer Dim initializer = TryCast(node, ObjectMemberInitializerSyntax) If initializer Is Nothing Then From 2c2be6bcafe2e9da3702c07bf289f257929ecd46 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Wed, 4 May 2022 08:54:06 -0700 Subject: [PATCH 50/58] fix bug --- .../Core/Portable/Snippets/RoslynLSPSnippetConverter.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs index b75badd416da8..4b72eaad9a347 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -146,8 +146,12 @@ private static TextSpan GetUpdatedTextSpan(TextChange textChange, ImmutableArray var startPosition = textChange.Span.Start; var endPosition = textChange.Span.Start + textChangeText.Length; - startPosition = Math.Min(startPosition, placeholders.Min(placeholder => placeholder.PlaceHolderPositions.Min())); - endPosition = Math.Max(endPosition, placeholders.Max(placeholder => placeholder.PlaceHolderPositions.Max())); + if (placeholders.Length > 0) + { + startPosition = Math.Min(startPosition, placeholders.Min(placeholder => placeholder.PlaceHolderPositions.Min())); + endPosition = Math.Max(endPosition, placeholders.Max(placeholder => placeholder.PlaceHolderPositions.Max())); + } + startPosition = Math.Min(startPosition, caretPosition); endPosition = Math.Max(endPosition, caretPosition); From c244e002ff7ea2f809e4557a1db7f7c56c6c1556 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Fri, 6 May 2022 01:40:45 -0700 Subject: [PATCH 51/58] feedback --- .../Snippets/CSharpLSPSnippetTests.cs | 99 ------------------- .../AsyncCompletion/CommitManager.cs | 6 +- .../Core/Snippets/RoslynLSPSnippetExpander.cs | 8 +- .../Snippets/RoslynLSPSnippetConvertTests.cs | 22 ++++- .../Snippets/RoslynLSPSnippetConverter.cs | 10 +- .../Core/Portable/Snippets/SnippetChange.cs | 2 +- .../Portable/Snippets/SnippetPlaceholder.cs | 11 ++- .../AbstractConsoleSnippetProvider.cs | 21 +++- 8 files changed, 53 insertions(+), 126 deletions(-) delete mode 100644 src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs deleted file mode 100644 index 5889feb00019e..0000000000000 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/Snippets/CSharpLSPSnippetTests.cs +++ /dev/null @@ -1,99 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis.Completion; -using Microsoft.CodeAnalysis.CSharp.Completion.CompletionProviders.Snippets; -using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; -using Microsoft.CodeAnalysis.Test.Utilities; -using Roslyn.Test.Utilities; -using Xunit; - -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders.Snippets -{ - public class CSharpLSPSnippetTests : AbstractCSharpSnippetCompletionProviderTests - { - protected override string ItemToCommit => throw new NotImplementedException(); - - protected override async Task VerifyCustomCommitProviderWorkerAsync(string codeBeforeCommit, int position, string itemToCommit, string expectedLSPSnippet, SourceCodeKind sourceCodeKind, char? commitChar = null) - { - using var workspaceFixture = GetOrCreateWorkspaceFixture(); - var workspace = workspaceFixture.Target.GetWorkspace(); - - // Set options that are not CompletionOptions - workspace.SetOptions(WithChangedNonCompletionOptions(workspace.Options)); - - var document1 = workspaceFixture.Target.UpdateDocument(codeBeforeCommit, sourceCodeKind); - await VerifyCustomCommitProviderLSPSnippetAsync(document1, position, itemToCommit, expectedLSPSnippet, commitChar); - - if (await CanUseSpeculativeSemanticModelAsync(document1, position)) - { - var document2 = workspaceFixture.Target.UpdateDocument(codeBeforeCommit, sourceCodeKind, cleanBeforeUpdate: false); - await VerifyCustomCommitProviderLSPSnippetAsync(document2, position, itemToCommit, expectedLSPSnippet, commitChar); - } - } - - private async Task VerifyCustomCommitProviderLSPSnippetAsync(Document document, int position, string itemToCommit, string expectedLSPSnippet, char? commitChar = null) - { - var service = GetCompletionService(document.Project); - var completionList = await GetCompletionListAsync(service, document, position, CompletionTrigger.Invoke); - var items = completionList.Items; - - Assert.Contains(itemToCommit, items.Select(x => x.DisplayText), GetStringComparer()); - var completionItem = items.First(i => CompareItems(i.DisplayText, itemToCommit)); - var commit = await service.GetChangeAsync(document, completionItem, commitChar, CancellationToken.None); - commit.Properties!.TryGetValue("LSPSnippet", out var generatedLSPSnippet); - AssertEx.EqualOrDiff(expectedLSPSnippet, generatedLSPSnippet); - } - - [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task InsertConsoleSnippetInMethodTest() - { - var markupBeforeCommit = -@"class Program -{ - public void Method() - { - $$ - } -}"; - - var expectedLSPSnippet = -@"using System; - -class Program -{ - public void Method() - { - Console.WriteLine($0);"; - - await VerifyCustomCommitProviderAsync(markupBeforeCommit, FeaturesResources.Write_to_the_console, expectedLSPSnippet); - } - - [WpfFact, Trait(Traits.Feature, Traits.Features.Completion)] - public async Task InsertIfSnippetInMethodTest() - { - var markupBeforeCommit = -@"class Program -{ - public void Method() - { - $$ - } -}"; - - var expectedLSPSnippet = -@"if (${1:true}) - {$0 - }"; - - await VerifyCustomCommitProviderAsync(markupBeforeCommit, FeaturesResources.Insert_an_if_statement, expectedLSPSnippet); - } - } -} diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs index 2bf7814b0894f..a2e5f351f2f15 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs @@ -247,11 +247,7 @@ private AsyncCompletionData.CommitResult Commit( { var lspSnippetText = change.Properties[SnippetCompletionItem.LSPSnippetKey]; - if (!_roslynLSPSnippetExpander.TryExpand(change.TextChange.Span, lspSnippetText, _textView, triggerSnapshot)) - { - return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None); - } - + _roslynLSPSnippetExpander.TryExpand(change.TextChange.Span, lspSnippetText, _textView, triggerSnapshot); return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None); } diff --git a/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs b/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs index 173405f3759df..6b8560e91d373 100644 --- a/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs +++ b/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs @@ -40,7 +40,7 @@ public RoslynLSPSnippetExpander( } } - public bool TryExpand(TextSpan textSpan, string lspSnippetText, ITextView textView, ITextSnapshot textSnapshot) + public void TryExpand(TextSpan textSpan, string lspSnippetText, ITextView textView, ITextSnapshot textSnapshot) { Contract.ThrowIfFalse(CanExpandSnippet()); @@ -63,13 +63,9 @@ public bool TryExpand(TextSpan textSpan, string lspSnippetText, ITextView textVi { throw new Exception("The invoked LSP snippet expander came back as false."); } - - return true; } - catch (Exception e) + catch (Exception e) when (FatalError.ReportAndCatch(e)) { - FatalError.ReportAndCatch(e); - return false; } } diff --git a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs index dff34f6165b24..4a881cff6def5 100644 --- a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs +++ b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs @@ -185,6 +185,22 @@ public Task TestExtendSnippetTextChangeBackwardsForPlaceholderForwardsForCaret() return TestAsync(markup, expectedLSPSnippet); } + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodBackwardsForPlaceholderForwardsForCaret() + { + var markup = +@"{|placeholder:test|} [|if (true) +{ +}|] $$"; + + var expectedLSPSnippet = +@"${1:test} if (true) +{ +} $0"; + + return TestAsync(markup, expectedLSPSnippet); + } + #endregion #region LSP Snippet generation tests @@ -217,9 +233,9 @@ private static async Task TestAsync(string markup, string output) var textChange = new TextChange(new TextSpan(stringSpan.Start, 0), text.Substring(stringSpan.Start, stringSpan.Length)); var placeholders = GetSnippetPlaceholders(text, placeholderDictionary); using var workspace = CreateWorkspaceFromCode(markup); - var document = workspace.CurrentSolution.GetDocument(workspace.Documents.First().Id); + var document = workspace.CurrentSolution.GetRequiredDocument(workspace.Documents.First().Id); - var lspSnippetString = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document!, cursorPosition!.Value, placeholders, textChange, CancellationToken.None).ConfigureAwait(false); + var lspSnippetString = await RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document, cursorPosition!.Value, placeholders, textChange, CancellationToken.None).ConfigureAwait(false); AssertEx.EqualOrDiff(output, lspSnippetString); } @@ -237,7 +253,7 @@ private static ImmutableArray GetSnippetPlaceholders(string } } - return arrayBuilder.AsImmutable(); + return arrayBuilder.ToImmutable(); } } } diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs index 4b72eaad9a347..b4c6078336a9d 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -38,9 +38,9 @@ private static string ConvertToLSPSnippetString(TextChange textChange, Immutable var textChangeText = textChange.NewText; Contract.ThrowIfNull(textChangeText); - using var _ = PooledStringBuilder.GetInstance(out var lspSnippetString); - using var disposer = PooledDictionary.GetInstance(out var dictionary); - GetMapOfSpanStartsToLSPStringItem(ref dictionary, placeholders, textChangeStart); + using var _1 = PooledStringBuilder.GetInstance(out var lspSnippetString); + using var _2 = PooledDictionary.GetInstance(out var dictionary); + PopulateMapOfSpanStartsToLSPStringItem(dictionary, placeholders, textChangeStart); // Need to go through the length + 1 since caret postions occur before and after the // character position. @@ -95,7 +95,7 @@ private static string ConvertToLSPSnippetString(TextChange textChange, Immutable /// Preprocesses the list of placeholders into a dictionary that maps the insertion position /// in the string to the placeholder's identifier and the priority associated with it. /// - private static void GetMapOfSpanStartsToLSPStringItem(ref PooledDictionary dictionary, ImmutableArray placeholders, int textChangeStart) + private static void PopulateMapOfSpanStartsToLSPStringItem(Dictionary dictionary, ImmutableArray placeholders, int textChangeStart) { for (var i = 0; i < placeholders.Length; i++) { @@ -151,7 +151,7 @@ private static TextSpan GetUpdatedTextSpan(TextChange textChange, ImmutableArray startPosition = Math.Min(startPosition, placeholders.Min(placeholder => placeholder.PlaceHolderPositions.Min())); endPosition = Math.Max(endPosition, placeholders.Max(placeholder => placeholder.PlaceHolderPositions.Max())); } - + startPosition = Math.Min(startPosition, caretPosition); endPosition = Math.Max(endPosition, caretPosition); diff --git a/src/Features/Core/Portable/Snippets/SnippetChange.cs b/src/Features/Core/Portable/Snippets/SnippetChange.cs index a529300d1b9f7..a0a3ca9814a2f 100644 --- a/src/Features/Core/Portable/Snippets/SnippetChange.cs +++ b/src/Features/Core/Portable/Snippets/SnippetChange.cs @@ -37,7 +37,7 @@ public SnippetChange( { if (textChanges.IsEmpty) { - throw new ArgumentException($"textChanges must not be empty"); + throw new ArgumentException($"{nameof(textChanges)} must not be empty"); } TextChanges = textChanges; diff --git a/src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs b/src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs index cfb1c5a9b3832..051a5fc33f952 100644 --- a/src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs +++ b/src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs @@ -24,11 +24,14 @@ internal readonly struct SnippetPlaceholder public readonly ImmutableArray PlaceHolderPositions; /// - /// Example: + /// /// For loop would have two placeholders: - /// for (var {1:i} = 0; {1:i} < {2:length}; {i}++) - /// Identifier: i, 3 associated positions - /// IdentifierL length, 1 associated position + /// + /// for (var {1:i} = 0; {1:i} < {2:length}; {1:i}++) + /// + /// Identifier: i, 3 associated positions
+ /// Identifier: length, 1 associated position
+ ///
///
public SnippetPlaceholder(string identifier, ImmutableArray placeholderPositions) { diff --git a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs index 39de55e0202a4..51ca5a533c985 100644 --- a/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs +++ b/src/Features/Core/Portable/Snippets/SnippetProviders/AbstractConsoleSnippetProvider.cs @@ -74,11 +74,26 @@ private async Task GenerateSnippetTextChangeAsync(Document document, return new TextChange(TextSpan.FromBounds(position, position), expressionStatement.NormalizeWhitespace().ToFullString()); } + /// + /// Tries to get the location after the open parentheses in the argument list. + /// If it can't, then we default to the end of the snippet's span. + /// protected override int GetTargetCaretPosition(ISyntaxFactsService syntaxFacts, SyntaxNode caretTarget) { - var openParenToken = GetOpenParenToken(caretTarget, syntaxFacts); - Contract.ThrowIfNull(openParenToken); - return openParenToken.Value.Span.End; + var invocationExpression = caretTarget.DescendantNodes().Where(syntaxFacts.IsInvocationExpression).FirstOrDefault(); + if (invocationExpression is null) + { + return caretTarget.Span.End; + } + + var argumentListNode = syntaxFacts.GetArgumentListOfInvocationExpression(invocationExpression); + if (argumentListNode is null) + { + return caretTarget.Span.End; + } + + syntaxFacts.GetPartsOfArgumentList(argumentListNode, out var openParenToken, out _, out _); + return openParenToken.Span.End; } protected override async Task AnnotateNodesToReformatAsync(Document document, From 74111f974e625cd4be4231dfc98b3a536cdce1c3 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Fri, 6 May 2022 11:43:50 -0700 Subject: [PATCH 52/58] way more tests --- .../Snippets/RoslynLSPSnippetConvertTests.cs | 201 +++++++++++++++++- 1 file changed, 198 insertions(+), 3 deletions(-) diff --git a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs index 4a881cff6def5..d9e21544a9fd2 100644 --- a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs +++ b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs @@ -185,18 +185,213 @@ public Task TestExtendSnippetTextChangeBackwardsForPlaceholderForwardsForCaret() return TestAsync(markup, expectedLSPSnippet); } + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodForwardsForCaret() + { + var markup = +@"public void Method() +{ + [|if ({|placeholder:true|}) + { + }|] $$ +}"; + + var expectedLSPSnippet = +@"if (${1:true}) + { + } $0"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodBackwardsForCaret() + { + var markup = +@"public void Method() +{ + $$ [|if ({|placeholder:true|}) + { + }|] +}"; + + var expectedLSPSnippet = +@"$0 if (${1:true}) + { + }"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodForwardsForPlaceholder() + { + var markup = +@"public void Method() +{ + [|if (true) + {$$ + }|] {|placeholder:test|} +}"; + + var expectedLSPSnippet = +@"if (true) + {$0 + } ${1:test}"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodBackwardsForPlaceholder() + { + var markup = +@"public void Method() +{ + {|placeholder:test|} [|if (true) + {$$ + }|]"; + + var expectedLSPSnippet = +@"${1:test} if (true) + {$0 + }"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodForwardsForPlaceholderThenCaret() + { + var markup = +@"public void Method() +{ + [|if (true) + { + }|] {|placeholder:test|} $$ +}"; + + var expectedLSPSnippet = +@"if (true) + { + } ${1:test} $0"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodForwardsForCaretThenPlaceholder() + { + var markup = +@"public void Method() +{ + [|if (true) + { + }|] $$ {|placeholder:test|} +}"; + + var expectedLSPSnippet = +@"if (true) + { + } $0 ${1:test}"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodBackwardsForPlaceholderThenCaret() + { + var markup = +@"public void Method() +{ + {|placeholder:test|} $$ [|if (true) + { + }|] +}"; + + var expectedLSPSnippet = +@"${1:test} $0 if (true) + { + }"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodBackwardsForCaretThenPlaceholder() + { + var markup = +@"public void Method() +{ + $$ {|placeholder:test|} [|if (true) + { + }|] +}"; + + var expectedLSPSnippet = +@"$0 ${1:test} if (true) + { + }"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodBackwardsForCaretForwardsForPlaceholder() + { + var markup = +@"public void Method() +{ + $$ [|if (true) + { + }|] {|placeholder:test|} +}"; + + var expectedLSPSnippet = +@"$0 if (true) + { + } ${1:test}"; + + return TestAsync(markup, expectedLSPSnippet); + } + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] public Task TestExtendSnippetTextChangeInMethodBackwardsForPlaceholderForwardsForCaret() { var markup = -@"{|placeholder:test|} [|if (true) +@"public void Method() { -}|] $$"; + {|placeholder:test|} [|if (true) + { + }|] $$ +}"; var expectedLSPSnippet = @"${1:test} if (true) + { + } $0"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestExtendSnippetTextChangeInMethodWithCodeBeforeAndAfterBackwardsForPlaceholderForwardsForCaret() + { + var markup = +@"public void Method() { -} $0"; + var x = 5; + {|placeholder:test|} [|if (true) + { + }|] $$ + + x = 3; +}"; + + var expectedLSPSnippet = +@"${1:test} if (true) + { + } $0"; return TestAsync(markup, expectedLSPSnippet); } From af2aada44d1b0b4cbf77d459fda893e298dfff52 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Fri, 6 May 2022 13:36:23 -0700 Subject: [PATCH 53/58] handle same positions better --- .../Snippets/RoslynLSPSnippetConvertTests.cs | 44 +++++++++++++++++++ .../Snippets/RoslynLSPSnippetConverter.cs | 11 ----- .../Core/Portable/Snippets/SnippetChange.cs | 2 +- .../Portable/Snippets/SnippetPlaceholder.cs | 5 +++ 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs index d9e21544a9fd2..41fb7e4d4d3de 100644 --- a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs +++ b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs @@ -416,6 +416,50 @@ public Task TestForLoopSnippet() return TestAsync(markup, expectedLSPSnippet); } + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestIfSnippetSamePlaceholderCursorLocation() + { + var markup = +@"public void Method() +{ + var x = 5; + [|if ({|placeholder:true|}$$) + { + }|] + + x = 3; +}"; + + var expectedLSPSnippet = +@"if (${1:true}$0) + { + }"; + + return TestAsync(markup, expectedLSPSnippet); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public Task TestIfSnippetSameCursorPlaceholderLocation() + { + var markup = +@"public void Method() +{ + var x = 5; + [|if ($${|placeholder:true|}) + { + }|] + + x = 3; +}"; + + var expectedLSPSnippet = +@"if ($0${1:true}) + { + }"; + + return TestAsync(markup, expectedLSPSnippet); + } + #endregion protected static TestWorkspace CreateWorkspaceFromCode(string code) diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs index b4c6078336a9d..d8ad830da51d7 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -51,17 +51,6 @@ private static string ConvertToLSPSnippetString(TextChange textChange, Immutable if (i == caretPosition - textChangeStart) { lspSnippetString.Append("$0"); - - // Special case for cursor position since they will occur between positions - // so we still wants to insert the character following the cursor position. - // Will not happen for placeholders since they have a direct mapping from position - // of the identifier to their position in the TextChange text. - if (i < textChangeText.Length) - { - lspSnippetString.Append(textChangeText[i]); - } - - i++; } //Tries to see if a value exists at that position in the map, and if so it diff --git a/src/Features/Core/Portable/Snippets/SnippetChange.cs b/src/Features/Core/Portable/Snippets/SnippetChange.cs index a0a3ca9814a2f..584d9a7aea2d8 100644 --- a/src/Features/Core/Portable/Snippets/SnippetChange.cs +++ b/src/Features/Core/Portable/Snippets/SnippetChange.cs @@ -37,7 +37,7 @@ public SnippetChange( { if (textChanges.IsEmpty) { - throw new ArgumentException($"{nameof(textChanges)} must not be empty"); + throw new ArgumentException($"{nameof(textChanges)} must not be empty."); } TextChanges = textChanges; diff --git a/src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs b/src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs index 051a5fc33f952..5932bdfeff17d 100644 --- a/src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs +++ b/src/Features/Core/Portable/Snippets/SnippetPlaceholder.cs @@ -35,6 +35,11 @@ internal readonly struct SnippetPlaceholder ///
public SnippetPlaceholder(string identifier, ImmutableArray placeholderPositions) { + if (identifier.Length == 0) + { + throw new ArgumentException($"{nameof(identifier)} must not be an empty string."); + } + Identifier = identifier; PlaceHolderPositions = placeholderPositions; } From f36a26264711b6f54e60d6a9925201ed0a3e2ac3 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Fri, 6 May 2022 13:37:41 -0700 Subject: [PATCH 54/58] comments --- .../Core/Portable/Snippets/RoslynLSPSnippetConverter.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs index d8ad830da51d7..695ff787e3432 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -93,6 +93,8 @@ private static void PopulateMapOfSpanStartsToLSPStringItem(Dictionary Date: Mon, 9 May 2022 10:18:45 -0700 Subject: [PATCH 55/58] tests --- .../Snippets/RoslynLSPSnippetConvertTests.cs | 20 +++++++++++++++++++ .../Snippets/RoslynLSPSnippetConverter.cs | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs index 41fb7e4d4d3de..78e98d814e17a 100644 --- a/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs +++ b/src/EditorFeatures/Test/Snippets/RoslynLSPSnippetConvertTests.cs @@ -396,6 +396,26 @@ public Task TestExtendSnippetTextChangeInMethodWithCodeBeforeAndAfterBackwardsFo return TestAsync(markup, expectedLSPSnippet); } + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public void TestExtendTextChangeInsertion() + { + var testString = "foo bar quux baz"; + using var workspace = CreateWorkspaceFromCode(testString); + var document = workspace.CurrentSolution.GetRequiredDocument(workspace.Documents.First().Id); + var lspSnippetString = RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document, 12, ImmutableArray.Empty, new TextChange(new TextSpan(8, 0), "quux"), CancellationToken.None).Result; + AssertEx.EqualOrDiff("quux$0", lspSnippetString); + } + + [Fact, Trait(Traits.Feature, Traits.Features.RoslynLSPSnippetConverter)] + public void TestExtendTextChangeReplacement() + { + var testString = "foo bar quux baz"; + using var workspace = CreateWorkspaceFromCode(testString); + var document = workspace.CurrentSolution.GetRequiredDocument(workspace.Documents.First().Id); + var lspSnippetString = RoslynLSPSnippetConverter.GenerateLSPSnippetAsync(document, 12, ImmutableArray.Empty, new TextChange(new TextSpan(4, 4), "bar quux"), CancellationToken.None).Result; + AssertEx.EqualOrDiff("bar quux$0", lspSnippetString); + } + #endregion #region LSP Snippet generation tests diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs index 695ff787e3432..1967c267ff4da 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -46,7 +46,7 @@ private static string ConvertToLSPSnippetString(TextChange textChange, Immutable // character position. // If there is a caret at the end of the line, then it's position // will be equivalent to the length of the TextChange. - for (var i = 0; i < textChangeText.Length + 1;) + for (var i = 0; i < textChange.Span.Length + 1;) { if (i == caretPosition - textChangeStart) { @@ -119,7 +119,7 @@ private static async Task ExtendSnippetTextChangeAsync(Document docu var documentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var newString = documentText.ToString(extendedSpan); - var newTextChange = new TextChange(new TextSpan(extendedSpan.Start, 0), newString); + var newTextChange = new TextChange(TextSpan.FromBounds(extendedSpan.Start, extendedSpan.End), newString); return newTextChange; } From 6e33b9db06924d102669b87db888d0308aebc286 Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 9 May 2022 10:22:03 -0700 Subject: [PATCH 56/58] pr feedback --- .../Core/IntelliSense/AsyncCompletion/CommitManager.cs | 2 +- .../Core/Snippets/RoslynLSPSnippetExpander.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs index a2e5f351f2f15..8318cc717c83d 100644 --- a/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs +++ b/src/EditorFeatures/Core/IntelliSense/AsyncCompletion/CommitManager.cs @@ -247,7 +247,7 @@ private AsyncCompletionData.CommitResult Commit( { var lspSnippetText = change.Properties[SnippetCompletionItem.LSPSnippetKey]; - _roslynLSPSnippetExpander.TryExpand(change.TextChange.Span, lspSnippetText, _textView, triggerSnapshot); + _roslynLSPSnippetExpander.Expand(change.TextChange.Span, lspSnippetText, _textView, triggerSnapshot); return new AsyncCompletionData.CommitResult(isHandled: true, AsyncCompletionData.CommitBehavior.None); } diff --git a/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs b/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs index 6b8560e91d373..49bc122b5ab37 100644 --- a/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs +++ b/src/EditorFeatures/Core/Snippets/RoslynLSPSnippetExpander.cs @@ -40,7 +40,7 @@ public RoslynLSPSnippetExpander( } } - public void TryExpand(TextSpan textSpan, string lspSnippetText, ITextView textView, ITextSnapshot textSnapshot) + public void Expand(TextSpan textSpan, string lspSnippetText, ITextView textView, ITextSnapshot textSnapshot) { Contract.ThrowIfFalse(CanExpandSnippet()); @@ -54,12 +54,12 @@ public void TryExpand(TextSpan textSpan, string lspSnippetText, ITextView textVi { // ExpanderMethodInfo should not be null at this point. var expandMethodResult = _expanderMethodInfo!.Invoke(_lspSnippetExpander, new object[] { textEdit, textView, textSnapshot }); - if (expandMethodResult is null) + if (expandMethodResult is not bool resultValue) { - throw new Exception("The result of the invoked LSP snippet expander is null."); + throw new Exception("The result of the invoked LSP snippet expander was not a boolean."); } - if (!(bool)expandMethodResult) + if (!resultValue) { throw new Exception("The invoked LSP snippet expander came back as false."); } From 2498eff9e7beac1b480abf3128ff7878677a61ec Mon Sep 17 00:00:00 2001 From: akhera99 Date: Mon, 9 May 2022 13:08:52 -0700 Subject: [PATCH 57/58] removed unnecessary check --- .../Core/Portable/Snippets/RoslynLSPSnippetConverter.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs index 1967c267ff4da..4a1e86330ec11 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -111,12 +111,6 @@ private static void PopulateMapOfSpanStartsToLSPStringItem(Dictionary ExtendSnippetTextChangeAsync(Document document, TextChange textChange, ImmutableArray placeholders, int caretPosition, CancellationToken cancellationToken) { var extendedSpan = GetUpdatedTextSpan(textChange, placeholders, caretPosition); - - if (extendedSpan.Length == 0) - { - return textChange; - } - var documentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var newString = documentText.ToString(extendedSpan); var newTextChange = new TextChange(TextSpan.FromBounds(extendedSpan.Start, extendedSpan.End), newString); From d02e1c2d2eac02a6338f82bd842f501b07ac2a6e Mon Sep 17 00:00:00 2001 From: akhera99 Date: Tue, 10 May 2022 08:42:43 -0700 Subject: [PATCH 58/58] fix --- .../Core/Portable/Snippets/RoslynLSPSnippetConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs index 4a1e86330ec11..3fe52bf86914f 100644 --- a/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs +++ b/src/Features/Core/Portable/Snippets/RoslynLSPSnippetConverter.cs @@ -113,7 +113,7 @@ private static async Task ExtendSnippetTextChangeAsync(Document docu var extendedSpan = GetUpdatedTextSpan(textChange, placeholders, caretPosition); var documentText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var newString = documentText.ToString(extendedSpan); - var newTextChange = new TextChange(TextSpan.FromBounds(extendedSpan.Start, extendedSpan.End), newString); + var newTextChange = new TextChange(extendedSpan, newString); return newTextChange; }