From 2fa53c0e4e174ba883b6e0b7093835b86981c581 Mon Sep 17 00:00:00 2001 From: "gel@microsoft.com" Date: Thu, 3 Apr 2025 10:22:45 -0700 Subject: [PATCH] Move Copilot context provider to EA.Copilot and handler to LanguageServer --- .../Copilot/Completion/CodeSnippetItem.cs | 31 +++++++ .../ICSharpCopilotContextProviderService.cs | 13 +++ .../Copilot/Completion/IContextItem.cs | 13 +++ .../Copilot/Completion/IContextProvider.cs | 21 +++++ .../Copilot/Completion/Importance.cs | 13 +++ .../Copilot/Completion/TraitItem.cs | 26 ++++++ .../CSharpContextProviderService.cs | 85 +++++++++++++++++++ .../Copilot/InternalAPI.Unshipped.txt | 47 ++++++++++ ...CodeAnalysis.ExternalAccess.Copilot.csproj | 5 ++ .../ExportProviderBuilder.cs | 17 +++- .../CodeSnippetContextItem.cs | 22 +++++ .../CopilotCompletion/ContextResolveParam.cs | 62 ++++++++++++++ .../CopilotCompletionResolveContextHandler.cs | 54 ++++++++++++ .../CopilotCompletion/TraitContextItem.cs | 19 +++++ ...crosoft.CodeAnalysis.LanguageServer.csproj | 1 + 15 files changed, 427 insertions(+), 2 deletions(-) create mode 100644 src/Features/ExternalAccess/Copilot/Completion/CodeSnippetItem.cs create mode 100644 src/Features/ExternalAccess/Copilot/Completion/ICSharpCopilotContextProviderService.cs create mode 100644 src/Features/ExternalAccess/Copilot/Completion/IContextItem.cs create mode 100644 src/Features/ExternalAccess/Copilot/Completion/IContextProvider.cs create mode 100644 src/Features/ExternalAccess/Copilot/Completion/Importance.cs create mode 100644 src/Features/ExternalAccess/Copilot/Completion/TraitItem.cs create mode 100644 src/Features/ExternalAccess/Copilot/Internal/Completion/CSharpContextProviderService.cs create mode 100644 src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/CopilotCompletion/CodeSnippetContextItem.cs create mode 100644 src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/CopilotCompletion/ContextResolveParam.cs create mode 100644 src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/CopilotCompletion/CopilotCompletionResolveContextHandler.cs create mode 100644 src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/CopilotCompletion/TraitContextItem.cs diff --git a/src/Features/ExternalAccess/Copilot/Completion/CodeSnippetItem.cs b/src/Features/ExternalAccess/Copilot/Completion/CodeSnippetItem.cs new file mode 100644 index 0000000000000..54626ca36134c --- /dev/null +++ b/src/Features/ExternalAccess/Copilot/Completion/CodeSnippetItem.cs @@ -0,0 +1,31 @@ +// 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.Text.Json.Serialization; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion; + +internal record CodeSnippetItem : IContextItem +{ + public CodeSnippetItem(string uri, string value, string[]? additionalUris = null, int importance = Completion.Importance.Default) + { + this.Uri = uri; + this.Value = value; + this.AdditionalUris = additionalUris; + this.Importance = importance; + } + + [JsonPropertyName("uri")] + public string Uri { get; init; } + + [JsonPropertyName("value")] + public string Value { get; init; } + + [JsonPropertyName("additionalUris")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string[]? AdditionalUris { get; init; } + + [JsonPropertyName("importance")] + public int Importance { get; init; } +} diff --git a/src/Features/ExternalAccess/Copilot/Completion/ICSharpCopilotContextProviderService.cs b/src/Features/ExternalAccess/Copilot/Completion/ICSharpCopilotContextProviderService.cs new file mode 100644 index 0000000000000..c742cfdcea2b4 --- /dev/null +++ b/src/Features/ExternalAccess/Copilot/Completion/ICSharpCopilotContextProviderService.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion; + +internal interface ICSharpCopilotContextProviderService +{ + IAsyncEnumerable GetContextItemsAsync(Document document, int position, IReadOnlyDictionary activeExperiments, CancellationToken cancellationToken); +} diff --git a/src/Features/ExternalAccess/Copilot/Completion/IContextItem.cs b/src/Features/ExternalAccess/Copilot/Completion/IContextItem.cs new file mode 100644 index 0000000000000..65196a0227160 --- /dev/null +++ b/src/Features/ExternalAccess/Copilot/Completion/IContextItem.cs @@ -0,0 +1,13 @@ +// 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.Text.Json.Serialization; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion; + +[JsonDerivedType(typeof(CodeSnippetItem))] +[JsonDerivedType(typeof(TraitItem))] +internal interface IContextItem +{ +} diff --git a/src/Features/ExternalAccess/Copilot/Completion/IContextProvider.cs b/src/Features/ExternalAccess/Copilot/Completion/IContextProvider.cs new file mode 100644 index 0000000000000..b0abafeb8bffd --- /dev/null +++ b/src/Features/ExternalAccess/Copilot/Completion/IContextProvider.cs @@ -0,0 +1,21 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion; + +internal interface IContextProvider +{ + ValueTask ProvideContextItemsAsync( + Document document, + int position, + IReadOnlyDictionary activeExperiments, + Func, CancellationToken, ValueTask> callback, + CancellationToken cancellationToken); +} diff --git a/src/Features/ExternalAccess/Copilot/Completion/Importance.cs b/src/Features/ExternalAccess/Copilot/Completion/Importance.cs new file mode 100644 index 0000000000000..082ce0f4b6e2e --- /dev/null +++ b/src/Features/ExternalAccess/Copilot/Completion/Importance.cs @@ -0,0 +1,13 @@ +// 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. + +namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion; + +internal static class Importance +{ + public const int Lowest = 0; + public const int Highest = 100; + + public const int Default = Lowest; +} diff --git a/src/Features/ExternalAccess/Copilot/Completion/TraitItem.cs b/src/Features/ExternalAccess/Copilot/Completion/TraitItem.cs new file mode 100644 index 0000000000000..9eac7e80e3104 --- /dev/null +++ b/src/Features/ExternalAccess/Copilot/Completion/TraitItem.cs @@ -0,0 +1,26 @@ +// 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.Text.Json.Serialization; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion; + +internal record TraitItem : IContextItem +{ + public TraitItem(string name, string value, int importance = Completion.Importance.Default) + { + this.Name = name; + this.Value = value; + this.Importance = importance; + } + + [JsonPropertyName("name")] + public string Name { get; init; } + + [JsonPropertyName("value")] + public string Value { get; init; } + + [JsonPropertyName("importance")] + public int Importance { get; init; } +} diff --git a/src/Features/ExternalAccess/Copilot/Internal/Completion/CSharpContextProviderService.cs b/src/Features/ExternalAccess/Copilot/Internal/Completion/CSharpContextProviderService.cs new file mode 100644 index 0000000000000..f8acdc724c39f --- /dev/null +++ b/src/Features/ExternalAccess/Copilot/Internal/Completion/CSharpContextProviderService.cs @@ -0,0 +1,85 @@ +// 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.Composition; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.ErrorReporting; +using Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.VisualStudio.Threading; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.Completion; + +[Shared] +[Export(typeof(ICSharpCopilotContextProviderService))] +internal sealed class CSharpContextProviderService : ICSharpCopilotContextProviderService +{ + // Exposed for testing + public ImmutableArray Providers { get; } + + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public CSharpContextProviderService([ImportMany] IEnumerable providers) + { + Providers = providers.ToImmutableArray(); + } + + public async IAsyncEnumerable GetContextItemsAsync(Document document, int position, IReadOnlyDictionary activeExperiments, [EnumeratorCancellation] CancellationToken cancellationToken) + { + var queue = new AsyncQueue(); + var tasks = this.Providers.Select(provider => Task.Run(async () => + { + try + { + await provider.ProvideContextItemsAsync(document, position, activeExperiments, ProvideItemsAsync, cancellationToken).ConfigureAwait(false); + } + catch (Exception exception) when (FatalError.ReportAndCatchUnlessCanceled(exception, ErrorSeverity.General)) + { + } + }, + cancellationToken)); + + // Let all providers run in parallel in the background, so we can steam results as they come in. + // Complete the queue when all providers are done. + _ = Task.WhenAll(tasks) + .ContinueWith((_, __) => queue.Complete(), + null, + cancellationToken, + TaskContinuationOptions.ExecuteSynchronously, + TaskScheduler.Default); + + while (true) + { + IContextItem item; + try + { + item = await queue.DequeueAsync(cancellationToken).ConfigureAwait(false); + } + catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) + { + // Dequeue is cancelled because the queue is empty and completed, we can break out of the loop. + break; + } + + yield return item; + } + + ValueTask ProvideItemsAsync(ImmutableArray items, CancellationToken cancellationToken) + { + foreach (var item in items) + { + queue.Enqueue(item); + } + + return default; + } + } +} diff --git a/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt b/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt index e489cba08dda5..5203c1fd27fa9 100644 --- a/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt +++ b/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt @@ -1,6 +1,35 @@ #nullable enable +const Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.Importance.Default = 0 -> int +const Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.Importance.Highest = 100 -> int +const Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.Importance.Lowest = 0 -> int Microsoft.CodeAnalysis.ExternalAccess.Copilot.CodeMapper.ICSharpCopilotMapCodeService Microsoft.CodeAnalysis.ExternalAccess.Copilot.CodeMapper.ICSharpCopilotMapCodeService.MapCodeAsync(Microsoft.CodeAnalysis.Document! document, System.Collections.Immutable.ImmutableArray contents, System.Collections.Immutable.ImmutableArray<(Microsoft.CodeAnalysis.Document! document, Microsoft.CodeAnalysis.Text.TextSpan textSpan)> prioritizedFocusLocations, System.Collections.Generic.Dictionary! options, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task?>! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.AdditionalUris.get -> string![]? +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.AdditionalUris.init -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.CodeSnippetItem(Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem! original) -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.CodeSnippetItem(string! uri, string! value, string![]? additionalUris = null, int importance = 0) -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.Importance.get -> int +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.Importance.init -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.Uri.get -> string! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.Uri.init -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.Value.get -> string! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.Value.init -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.IContextItem +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.IContextProvider +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.IContextProvider.ProvideContextItemsAsync(Microsoft.CodeAnalysis.Document! document, int position, System.Collections.Generic.IReadOnlyDictionary! activeExperiments, System.Func, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask>! callback, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.ICSharpCopilotContextProviderService +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.ICSharpCopilotContextProviderService.GetContextItemsAsync(Microsoft.CodeAnalysis.Document! document, int position, System.Collections.Generic.IReadOnlyDictionary! activeExperiments, System.Threading.CancellationToken cancellationToken) -> System.Collections.Generic.IAsyncEnumerable! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.Importance +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.Importance.get -> int +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.Importance.init -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.Name.get -> string! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.Name.init -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.TraitItem(Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem! original) -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.TraitItem(string! name, string! value, int importance = 0) -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.Value.get -> string! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.Value.init -> void Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper.Equals(Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper? other) -> bool Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotDocumentationCommentProposalWrapper @@ -84,10 +113,28 @@ Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.SemanticSearchCopil Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.SemanticSearchCopilotGeneratedQueryImpl.Text.init -> void Microsoft.CodeAnalysis.SemanticSearch.SemanticSearchCopilotServiceWrapper Microsoft.CodeAnalysis.SemanticSearch.SemanticSearchCopilotServiceWrapper.SemanticSearchCopilotServiceWrapper(System.Lazy? impl) -> void +override Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.Equals(object? obj) -> bool +override Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.GetHashCode() -> int +override Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.ToString() -> string! +override Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.Equals(object? obj) -> bool +override Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.GetHashCode() -> int +override Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.ToString() -> string! override Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper.Equals(object? obj) -> bool override Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper.GetHashCode() -> int +static Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.operator !=(Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem? left, Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem? right) -> bool +static Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.operator ==(Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem? left, Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem? right) -> bool +static Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.operator !=(Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem? left, Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem? right) -> bool +static Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.operator ==(Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem? left, Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem? right) -> bool static Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper.Create(System.Collections.Immutable.ImmutableArray values) -> Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper! static Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotUtilities.GetContainingMethodDeclarationAsync(Microsoft.CodeAnalysis.Document! document, int position, bool useFullSpan, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! static Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotUtilities.GetCopilotSuggestionDiagnosticTag() -> string! static Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotUtilities.IsResultantVisibilityPublic(this Microsoft.CodeAnalysis.ISymbol! symbol) -> bool static Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotUtilities.IsValidIdentifier(string? name) -> bool +virtual Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.$() -> Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem! +virtual Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.EqualityContract.get -> System.Type! +virtual Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.Equals(Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem? other) -> bool +virtual Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.CodeSnippetItem.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.$() -> Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem! +virtual Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.EqualityContract.get -> System.Type! +virtual Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.Equals(Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem? other) -> bool +virtual Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion.TraitItem.PrintMembers(System.Text.StringBuilder! builder) -> bool diff --git a/src/Features/ExternalAccess/Copilot/Microsoft.CodeAnalysis.ExternalAccess.Copilot.csproj b/src/Features/ExternalAccess/Copilot/Microsoft.CodeAnalysis.ExternalAccess.Copilot.csproj index 69e66be254d12..1b3d12b9541e3 100644 --- a/src/Features/ExternalAccess/Copilot/Microsoft.CodeAnalysis.ExternalAccess.Copilot.csproj +++ b/src/Features/ExternalAccess/Copilot/Microsoft.CodeAnalysis.ExternalAccess.Copilot.csproj @@ -20,6 +20,7 @@ + @@ -29,6 +30,10 @@ + + + + diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/ExportProviderBuilder.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/ExportProviderBuilder.cs index 63cebc020eaff..6f8f7fbd154a7 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/ExportProviderBuilder.cs +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/ExportProviderBuilder.cs @@ -182,13 +182,26 @@ private static void ThrowOnUnexpectedErrors(CompositionConfiguration configurati // Verify that we have exactly the MEF errors that we expect. If we have less or more this needs to be updated to assert the expected behavior. // Currently we are expecting the following: // "----- CompositionError level 1 ------ + // Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.CodeMapper.CSharpMapCodeService.ctor(service): expected exactly 1 export matching constraints: + // Contract name: Microsoft.CodeAnalysis.ExternalAccess.Copilot.CodeMapper.ICSharpCopilotMapCodeService + // TypeIdentityName: Microsoft.CodeAnalysis.ExternalAccess.Copilot.CodeMapper.ICSharpCopilotMapCodeService + // but found 0. + // part definition Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.CodeMapper.CSharpMapCodeService + // // Microsoft.CodeAnalysis.ExternalAccess.Pythia.PythiaSignatureHelpProvider.ctor(implementation): expected exactly 1 export matching constraints: // Contract name: Microsoft.CodeAnalysis.ExternalAccess.Pythia.Api.IPythiaSignatureHelpProviderImplementation // TypeIdentityName: Microsoft.CodeAnalysis.ExternalAccess.Pythia.Api.IPythiaSignatureHelpProviderImplementation // but found 0. - // part definition Microsoft.CodeAnalysis.ExternalAccess.Pythia.PythiaSignatureHelpProvider + // part definition Microsoft.CodeAnalysis.ExternalAccess.Pythia.PythiaSignatureHelpProvider + // + // Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.SemanticSearch.CopilotSemanticSearchQueryExecutor.ctor(workspaceProvider): expected exactly 1 export matching constraints: + // Contract name: Microsoft.CodeAnalysis.Host.IHostWorkspaceProvider + // TypeIdentityName: Microsoft.CodeAnalysis.Host.IHostWorkspaceProvider + // but found 0. + // part definition Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.SemanticSearch.CopilotSemanticSearchQueryExecutor + var erroredParts = configuration.CompositionErrors.FirstOrDefault()?.SelectMany(error => error.Parts).Select(part => part.Definition.Type.Name) ?? []; - var expectedErroredParts = new string[] { "PythiaSignatureHelpProvider" }; + var expectedErroredParts = new string[] { "CSharpMapCodeService", "PythiaSignatureHelpProvider", "CopilotSemanticSearchQueryExecutor" }; var hasUnexpectedErroredParts = erroredParts.Any(part => !expectedErroredParts.Contains(part)); if (hasUnexpectedErroredParts || !catalog.DiscoveredParts.DiscoveryErrors.IsEmpty) diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/CopilotCompletion/CodeSnippetContextItem.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/CopilotCompletion/CodeSnippetContextItem.cs new file mode 100644 index 0000000000000..1770f16510284 --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/CopilotCompletion/CodeSnippetContextItem.cs @@ -0,0 +1,22 @@ +// 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.Text.Json.Serialization; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Copilot; + +internal sealed class CodeSnippetContextItem +{ + [JsonPropertyName("importance")] + public int Importance { get; set; } + + [JsonPropertyName("uri")] + public required string Uri { get; set; } + + [JsonPropertyName("value")] + public required string Value { get; set; } + + [JsonPropertyName("additionalUris")] + public string[]? AdditionalUris { get; set; } +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/CopilotCompletion/ContextResolveParam.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/CopilotCompletion/ContextResolveParam.cs new file mode 100644 index 0000000000000..d02d4168686db --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/CopilotCompletion/ContextResolveParam.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Text.Json.Serialization; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Copilot; + +internal sealed class ContextResolveParam +{ + [JsonPropertyName("documentContext")] + public required TextDocumentPositionParams DocumentContext { get; set; } + + [JsonPropertyName("completionId")] + public required string CompletionId { get; set; } + + [JsonPropertyName("timeBudget")] + public required int TimeBudget { get; set; } + + [JsonPropertyName("data")] + public object? Data { get; set; } + + [JsonPropertyName("activeExperiments")] + public Dictionary>? ActiveExperiments { get; set; } + + public IReadOnlyDictionary GetUnpackedActiveExperiments() + { + if (this.ActiveExperiments is null) + { + return ImmutableDictionary.Empty; + } + + var result = new Dictionary(this.ActiveExperiments.Count); + foreach (var kvp in this.ActiveExperiments) + { + result[kvp.Key] = UnpackSumType(kvp.Value); + } + return result; + } + + private static object UnpackSumType(SumType sumType) + { + if (sumType.TryGetFirst(out var first)) + { + return first; + } + else if (sumType.TryGetSecond(out var second)) + { + return second; + } + else if (sumType.TryGetThird(out var third)) + { + return third; + } + else + { + return sumType.Fourth; + } + } +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/CopilotCompletion/CopilotCompletionResolveContextHandler.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/CopilotCompletion/CopilotCompletionResolveContextHandler.cs new file mode 100644 index 0000000000000..e02101b77ebae --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/CopilotCompletion/CopilotCompletionResolveContextHandler.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis.ExternalAccess.Copilot.Completion; +using Microsoft.CodeAnalysis.Text; +using Roslyn.LanguageServer.Protocol; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Copilot; + +[Shared] +[Method(MethodName)] +[ExportCSharpVisualBasicStatelessLspService(typeof(CopilotCompletionResolveContextHandler), WellKnownLspServerKinds.Any)] +internal sealed class CopilotCompletionResolveContextHandler : ILspServiceDocumentRequestHandler +{ + // "@2" prefix to differentiate it from the implementation previously located in devkit extension. + private const string MethodName = "roslyn/resolveContext@2"; + + [ImportingConstructor] + [Obsolete("This exported object must be obtained through the MEF export provider.", error: true)] + public CopilotCompletionResolveContextHandler(ICSharpCopilotContextProviderService contextProviderService) + { + ContextProviderService = contextProviderService; + } + + public bool MutatesSolutionState => false; + + public bool RequiresLSPSolution => true; + + public ICSharpCopilotContextProviderService ContextProviderService { get; } + + public TextDocumentIdentifier GetTextDocumentIdentifier(ContextResolveParam request) + => request.DocumentContext.TextDocument; + + public async Task HandleRequestAsync(ContextResolveParam param, RequestContext context, CancellationToken cancellationToken) + { + var linePosition = new LinePosition(param.DocumentContext.Position.Line, param.DocumentContext.Position.Character); + var document = context.GetRequiredDocument(); + + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + var position = text.Lines.GetPosition(linePosition); + var builder = ImmutableArray.CreateBuilder(); + var activeExperiments = param.GetUnpackedActiveExperiments(); + + await foreach (var item in ContextProviderService.GetContextItemsAsync(document, position, activeExperiments, cancellationToken).ConfigureAwait(false)) + { + builder.Add(item); + } + + return builder.ToArray(); + } +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/CopilotCompletion/TraitContextItem.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/CopilotCompletion/TraitContextItem.cs new file mode 100644 index 0000000000000..e61fe07230710 --- /dev/null +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServer/Handler/CopilotCompletion/TraitContextItem.cs @@ -0,0 +1,19 @@ +// 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.Text.Json.Serialization; + +namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Copilot; + +internal sealed class TraitContextItem +{ + [JsonPropertyName("importance")] + public int Importance { get; set; } + + [JsonPropertyName("name")] + public required string Name { get; set; } + + [JsonPropertyName("value")] + public required string Value { get; set; } +} diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj index ae21a40d1d2da..8e87ef2c2a334 100644 --- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj +++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Microsoft.CodeAnalysis.LanguageServer.csproj @@ -115,6 +115,7 @@ +