diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests_NoInteractive.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests_NoInteractive.cs index 7d64074b8ce88863543206b5a6748507678171bb..17b1428e5578f7def07ef1a02e091c4ad0315d8c 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests_NoInteractive.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests_NoInteractive.cs @@ -1,10 +1,12 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.CSharp.Completion.Providers; using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Completion.CompletionProviders; using Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces; +using Microsoft.VisualStudio.Text; using Roslyn.Test.Utilities; using Xunit; @@ -320,5 +322,38 @@ class C { void M() { B.$$ } } await VerifyItemExistsAsync(code, "X"); await VerifyItemExistsAsync(code, "Y"); } + + [WorkItem(209299, "https://devdiv.visualstudio.com/DevDiv/_workitems?id=209299")] + [Fact, Trait(Traits.Feature, Traits.Features.Completion)] + public async Task TestDescriptionWhenDocumentLengthChanges() + { + var code = @"using System; + +class C +{ + string Property + { + get + { + Console.$$";//, @"Beep" + + using (var workspace = TestWorkspace.CreateCSharp(code)) + { + var testDocument = workspace.Documents.Single(); + var position = testDocument.CursorPosition.Value; + + var document = workspace.CurrentSolution.GetDocument(testDocument.Id); + var service = CompletionService.GetService(document); + var completions = await service.GetCompletionsAsync(document, position); + + var item = completions.Items.First(i => i.DisplayText == "Beep"); + var edit = testDocument.GetTextBuffer().CreateEdit(); + edit.Delete(Span.FromBounds(position - 10, position)); + edit.Apply(); + + document = workspace.CurrentSolution.GetDocument(testDocument.Id); + var description = service.GetDescriptionAsync(document, item); + } + } } } diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractCrefCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractCrefCompletionProvider.cs index 48019b726c55e7b8b49af1dc828dcf79e12acf52..3677632869f60a8c4366a3dc67aa51077c5573e5 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractCrefCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractCrefCompletionProvider.cs @@ -11,9 +11,10 @@ abstract class AbstractCrefCompletionProvider : CommonCompletionProvider { protected const string HideAdvancedMembers = nameof(HideAdvancedMembers); - protected override async Task GetDescriptionWorkerAsync(Document document, CompletionItem item, CancellationToken cancellationToken) + protected override async Task GetDescriptionWorkerAsync( + Document document, CompletionItem item, CancellationToken cancellationToken) { - var position = SymbolCompletionItem.GetContextPosition(item); + var position = await SymbolCompletionItem.GetContextPositionAsync(document, item, cancellationToken).ConfigureAwait(false); // What EditorBrowsable settings were we previously passed in (if it mattered)? bool hideAdvancedMembers = false; diff --git a/src/Features/Core/Portable/Completion/Providers/AbstractRecommendationServiceBasedCompletionProvider.cs b/src/Features/Core/Portable/Completion/Providers/AbstractRecommendationServiceBasedCompletionProvider.cs index e8c6f85b9cbd8fe5f1d3d9a43d3f1911df939327..f469e24a0d4c178ac7a728e0db645863b6d16f5a 100644 --- a/src/Features/Core/Portable/Completion/Providers/AbstractRecommendationServiceBasedCompletionProvider.cs +++ b/src/Features/Core/Portable/Completion/Providers/AbstractRecommendationServiceBasedCompletionProvider.cs @@ -108,7 +108,7 @@ private static int ComputeSymbolMatchPriority(ISymbol symbol) protected override async Task GetDescriptionWorkerAsync( Document document, CompletionItem item, CancellationToken cancellationToken) { - var position = SymbolCompletionItem.GetContextPosition(item); + var position = await SymbolCompletionItem.GetContextPositionAsync(document, item, cancellationToken).ConfigureAwait(false); var name = SymbolCompletionItem.GetSymbolName(item); var kind = SymbolCompletionItem.GetKind(item); var relatedDocumentIds = document.Project.Solution.GetRelatedDocumentIds(document.Id).Concat(document.Id); diff --git a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs index 7c724c7fd7fc2c775304e66160029554a4008ba7..025afc23f0a4071445d3c4826fd34e8829a0800a 100644 --- a/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs +++ b/src/Features/Core/Portable/Completion/Providers/SymbolCompletionItem.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; +using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Completion.Providers { @@ -144,11 +145,12 @@ private static ISymbol DecodeSymbol(string id, Compilation compilation) return SymbolKey.Resolve(id, compilation).GetAnySymbol(); } - public static async Task GetDescriptionAsync(CompletionItem item, Document document, CancellationToken cancellationToken) + public static async Task GetDescriptionAsync( + CompletionItem item, Document document, CancellationToken cancellationToken) { var workspace = document.Project.Solution.Workspace; - var position = GetDescriptionPosition(item); + var position = await GetDescriptionPositionAsync(document, item, cancellationToken).ConfigureAwait(false); if (position == -1) { position = item.Span.Start; @@ -214,11 +216,19 @@ public static SupportedPlatformData GetSupportedPlatforms(CompletionItem item, W return null; } - public static int GetContextPosition(CompletionItem item) + public static async Task GetContextPositionAsync( + Document document, CompletionItem item, CancellationToken cancellationToken) { - if (item.Properties.TryGetValue("ContextPosition", out var text) && int.TryParse(text, out var number)) + if (item.Properties.TryGetValue("ContextPosition", out var text) && + int.TryParse(text, out var number)) { - return number; + // We have no access to the editor at this layer. So it's not + // possible for us to map the original context position forward + // to the current position in the file. So we need to cap the + // positoin to make sure it's within the bounds of the current + // text. + var sourceText = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + return Math.Min(number, sourceText.Length); } else { @@ -226,10 +236,8 @@ public static int GetContextPosition(CompletionItem item) } } - public static int GetDescriptionPosition(CompletionItem item) - { - return GetContextPosition(item); - } + public static Task GetDescriptionPositionAsync(Document document, CompletionItem item, CancellationToken cancellationToken) + => GetContextPositionAsync(document, item, cancellationToken); public static string GetInsertionText(CompletionItem item) { @@ -293,11 +301,12 @@ internal static string GetSymbolName(CompletionItem item) return null; } - public static async Task GetDescriptionAsync(CompletionItem item, ImmutableArray symbols, Document document, SemanticModel semanticModel, CancellationToken cancellationToken) + public static async Task GetDescriptionAsync( + CompletionItem item, ImmutableArray symbols, Document document, SemanticModel semanticModel, CancellationToken cancellationToken) { var workspace = document.Project.Solution.Workspace; - var position = SymbolCompletionItem.GetDescriptionPosition(item); + var position = await SymbolCompletionItem.GetDescriptionPositionAsync(document, item, cancellationToken).ConfigureAwait(false); var supportedPlatforms = SymbolCompletionItem.GetSupportedPlatforms(item, workspace); var contextDocument = FindAppropriateDocumentForDescriptionContext(document, supportedPlatforms);