diff --git a/.github/workflows/main-merge.yml b/.github/workflows/main-merge.yml index f00b43ac01a49..73424ad35fad6 100644 --- a/.github/workflows/main-merge.yml +++ b/.github/workflows/main-merge.yml @@ -5,6 +5,9 @@ on: schedule: # Create a merge every 3 hours (works only for merges from `main`, others would need a `push` trigger). - cron: '0 */3 * * *' + push: + branches: + - main-vs-deps workflow_dispatch: inputs: configuration_file_branch: @@ -17,17 +20,9 @@ permissions: pull-requests: write jobs: - # The config does not support multiple flows from the same source branch, - # so we need to run separately for each duplicate source branch (https://github.com/dotnet/arcade/issues/15586). merge: if: github.repository == 'dotnet/roslyn' uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@main with: configuration_file_path: 'eng/config/branch-merge.jsonc' configuration_file_branch: ${{ inputs.configuration_file_branch || 'main' }} - merge-2: - if: github.repository == 'dotnet/roslyn' - uses: dotnet/arcade/.github/workflows/inter-branch-merge-base.yml@main - with: - configuration_file_path: 'eng/config/branch-merge-2.jsonc' - configuration_file_branch: ${{ inputs.configuration_file_branch || 'main' }} diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index d86c5d196dcac..5d1b41f1f115a 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -8,9 +8,9 @@ - + https://github.com/dotnet/source-build-reference-packages - c3d4c372a15c2de79a2f26fe2b6b3644996d8550 + 12b07db19093502e1530df6058699bb43bb9b4ba diff --git a/eng/config/branch-merge-2.jsonc b/eng/config/branch-merge-2.jsonc deleted file mode 100644 index cd979074b54a4..0000000000000 --- a/eng/config/branch-merge-2.jsonc +++ /dev/null @@ -1,10 +0,0 @@ -// Used by .github/workflows/main-merge.yml -{ - "merge-flow-configurations": { - // Merge any main changes to main-vs-deps. - "main": { - "MergeToBranch": "main-vs-deps", - "ExtraSwitches": "-QuietComments" - } - } -} diff --git a/eng/config/branch-merge.jsonc b/eng/config/branch-merge.jsonc index bd198290691b3..7cdbfe60c8de2 100644 --- a/eng/config/branch-merge.jsonc +++ b/eng/config/branch-merge.jsonc @@ -1,8 +1,13 @@ // Used by .github/workflows/main-merge.yml { "merge-flow-configurations": { - // Merge any main changes to release/dev18.0. + // Merge any main changes to main-vs-deps. "main": { + "MergeToBranch": "main-vs-deps", + "ExtraSwitches": "-QuietComments" + }, + // Merge any main-vs-deps changes to release/dev18.0. + "main-vs-deps": { "MergeToBranch": "release/dev18.0", "ExtraSwitches": "-QuietComments" } diff --git a/src/Compilers/Test/Core/Traits/Traits.cs b/src/Compilers/Test/Core/Traits/Traits.cs index 61c3f93529acf..90799128f9b1f 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 NetCore = nameof(NetCore); public const string NormalizeModifiersOrOperators = nameof(NormalizeModifiersOrOperators); public const string ObjectBrowser = nameof(ObjectBrowser); + public const string OnTheFlyDocs = nameof(OnTheFlyDocs); public const string Options = nameof(Options); public const string Organizing = nameof(Organizing); public const string Outlining = nameof(Outlining); diff --git a/src/EditorFeatures/CSharpTest/OnTheFlyDocs/OnTheFlyDocsUtilitiesTests.cs b/src/EditorFeatures/CSharpTest/OnTheFlyDocs/OnTheFlyDocsUtilitiesTests.cs new file mode 100644 index 0000000000000..d52c79cb9d1c3 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/OnTheFlyDocs/OnTheFlyDocsUtilitiesTests.cs @@ -0,0 +1,130 @@ +// 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.Tasks; +using Microsoft.CodeAnalysis.CSharp.QuickInfo; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.OnTheFlyDocs; + +[UseExportProvider] +[Trait(Traits.Feature, Traits.Features.OnTheFlyDocs)] +public sealed class OnTheFlyDocsUtilitiesTests +{ + [Fact] + public async Task TestAdditionalContextNoContext() + { + var testCode = """ + class C + { + void AddMethod(int a, int b) + { + return a + b; + } + } + """; + + using var workspace = EditorTestWorkspace.CreateCSharp(testCode); + var solution = workspace.CurrentSolution; + var document = solution.Projects.First().Documents.First(); + + var syntaxTree = await document.GetSyntaxTreeAsync(); + var semanticModel = await document.GetSemanticModelAsync(); + + var methodDeclaration = syntaxTree!.GetRoot() + .DescendantNodes() + .OfType() + .First(); + + var methodSymbol = semanticModel!.GetDeclaredSymbol(methodDeclaration); + + var result = OnTheFlyDocsUtilities.GetAdditionalOnTheFlyDocsContext(solution, methodSymbol!); + Assert.True(result.All(item => item == null)); + } + + [Fact] + public async Task TestAdditionalContextWithTypeParameters() + { + var testCode = """ + class C + { + int AddMethod(A a, int b) + { + return a.x + b; + } + } + + class A + { + public int x; + } + """; + + using var workspace = EditorTestWorkspace.CreateCSharp(testCode); + var solution = workspace.CurrentSolution; + var document = solution.Projects.First().Documents.First(); + + var syntaxTree = await document.GetSyntaxTreeAsync(); + var semanticModel = await document.GetSemanticModelAsync(); + + var methodDeclaration = syntaxTree!.GetRoot() + .DescendantNodes() + .OfType() + .First(); + + var methodSymbol = semanticModel!.GetDeclaredSymbol(methodDeclaration); + + var result = OnTheFlyDocsUtilities.GetAdditionalOnTheFlyDocsContext(solution, methodSymbol!); + Assert.NotNull(result.First()); + Assert.Null(result.Last()); + } + + [Fact] + public async Task TestAdditionalContextWithTypeArguments() + { + var testCode = """ + class C + { + void Method(T t, U u) where T : class where U : struct + { + } + + void CallMethod() + { + Method(new CustomClass(), new CustomStruct()); + } + } + + class CustomClass + { + public string Name { get; set; } + } + + struct CustomStruct + { + public int Value { get; set; } + } + """; + + using var workspace = EditorTestWorkspace.CreateCSharp(testCode); + var solution = workspace.CurrentSolution; + var document = solution.Projects.First().Documents.First(); + + var syntaxTree = await document.GetSyntaxTreeAsync(); + var semanticModel = await document.GetSemanticModelAsync(); + + var methodInvocation = syntaxTree!.GetRoot() + .DescendantNodes() + .OfType() + .First(); + + var methodSymbol = semanticModel!.GetSymbolInfo(methodInvocation).Symbol; + + var result = OnTheFlyDocsUtilities.GetAdditionalOnTheFlyDocsContext(solution, methodSymbol!); + Assert.True(result.All(item => item is not null)); + } +} diff --git a/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml.cs b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml.cs index 424d1c8c775d2..b9908ef2dbf80 100644 --- a/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml.cs +++ b/src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml.cs @@ -179,7 +179,8 @@ private async Task SetResultTextAsync(ICopilotCodeAnalysisService copilotService try { - var (responseString, isQuotaExceeded) = await copilotService.GetOnTheFlyDocsAsync(_onTheFlyDocsInfo.SymbolSignature, _onTheFlyDocsInfo.DeclarationCode, _onTheFlyDocsInfo.Language, cancellationToken).ConfigureAwait(false); + var prompt = await copilotService.GetOnTheFlyDocsPromptAsync(_onTheFlyDocsInfo, cancellationToken).ConfigureAwait(false); + var (responseString, isQuotaExceeded) = await copilotService.GetOnTheFlyDocsResponseAsync(prompt, cancellationToken).ConfigureAwait(false); var copilotRequestTime = stopwatch.Elapsed; await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); diff --git a/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb b/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb index 254c213025a31..cab76f41eba63 100644 --- a/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb +++ b/src/EditorFeatures/Test2/CodeFixes/CodeFixServiceTests.vb @@ -19,6 +19,7 @@ Imports Microsoft.CodeAnalysis.ErrorLogger Imports Microsoft.CodeAnalysis.FindSymbols Imports Microsoft.CodeAnalysis.Host Imports Microsoft.CodeAnalysis.Host.Mef +Imports Microsoft.CodeAnalysis.QuickInfo Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.UnitTests Imports Roslyn.Utilities @@ -359,10 +360,6 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests Return Task.CompletedTask End Function - Public Function GetOnTheFlyDocsAsync(symbolSignature As String, declarationCode As ImmutableArray(Of String), language As String, cancellationToken As CancellationToken) As Task(Of (responseString As String, isQuotaExceeded As Boolean)) Implements ICopilotCodeAnalysisService.GetOnTheFlyDocsAsync - Return Task.FromResult(("", False)) - End Function - Public Function IsFileExcludedAsync(filePath As String, cancellationToken As CancellationToken) As Task(Of Boolean) Implements ICopilotCodeAnalysisService.IsFileExcludedAsync Return Task.FromResult(False) End Function @@ -371,6 +368,14 @@ Namespace Microsoft.CodeAnalysis.Editor.Implementation.CodeFixes.UnitTests Return Task.FromResult((New Dictionary(Of String, String), False)) End Function + Public Function GetOnTheFlyDocsPromptAsync(onTheFlyDocsInfo As OnTheFlyDocsInfo, cancellationToken As CancellationToken) As Task(Of String) Implements ICopilotCodeAnalysisService.GetOnTheFlyDocsPromptAsync + Return Task.FromResult(String.Empty) + End Function + + Public Function GetOnTheFlyDocsResponseAsync(prompt As String, cancellationToken As CancellationToken) As Task(Of (responseString As String, isQuotaExceeded As Boolean)) Implements ICopilotCodeAnalysisService.GetOnTheFlyDocsResponseAsync + Return Task.FromResult((String.Empty, False)) + End Function + Public Function IsImplementNotImplementedExceptionsAvailableAsync(cancellationToken As CancellationToken) As Task(Of Boolean) Implements ICopilotCodeAnalysisService.IsImplementNotImplementedExceptionsAvailableAsync Return Task.FromResult(False) End Function diff --git a/src/Features/CSharp/Portable/QuickInfo/CSharpSemanticQuickInfoProvider.cs b/src/Features/CSharp/Portable/QuickInfo/CSharpSemanticQuickInfoProvider.cs index 81fd490ab6f3e..96b38b78d4825 100644 --- a/src/Features/CSharp/Portable/QuickInfo/CSharpSemanticQuickInfoProvider.cs +++ b/src/Features/CSharp/Portable/QuickInfo/CSharpSemanticQuickInfoProvider.cs @@ -193,14 +193,21 @@ protected override NullableFlowState GetNullabilityAnalysis(SemanticModel semant } } - var maxLength = 1000; - var symbolStrings = symbol.DeclaringSyntaxReferences.Select(reference => + var solution = document.Project.Solution; + var declarationCode = symbol.DeclaringSyntaxReferences.Select(reference => { var span = reference.Span; - var sourceText = reference.SyntaxTree.GetText(cancellationToken); - return sourceText.GetSubText(new Text.TextSpan(span.Start, Math.Min(maxLength, span.Length))).ToString(); + var syntaxReferenceDocument = solution.GetDocument(reference.SyntaxTree); + if (syntaxReferenceDocument is not null) + { + return new OnTheFlyDocsRelevantFileInfo(syntaxReferenceDocument, span); + } + + return null; }).ToImmutableArray(); - return new OnTheFlyDocsInfo(symbol.ToDisplayString(), symbolStrings, symbol.Language, hasContentExcluded); + var additionalContext = OnTheFlyDocsUtilities.GetAdditionalOnTheFlyDocsContext(solution, symbol); + + return new OnTheFlyDocsInfo(symbol.ToDisplayString(), declarationCode, symbol.Language, hasContentExcluded, additionalContext); } } diff --git a/src/Features/CSharp/Portable/QuickInfo/OnTheFlyDocsUtilities.cs b/src/Features/CSharp/Portable/QuickInfo/OnTheFlyDocsUtilities.cs new file mode 100644 index 0000000000000..74779aebf1546 --- /dev/null +++ b/src/Features/CSharp/Portable/QuickInfo/OnTheFlyDocsUtilities.cs @@ -0,0 +1,50 @@ +// 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.Linq; +using Microsoft.CodeAnalysis.QuickInfo; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.CSharp.QuickInfo; + +internal static class OnTheFlyDocsUtilities +{ + public static ImmutableArray GetAdditionalOnTheFlyDocsContext(Solution solution, ISymbol symbol) + { + var parameters = symbol.GetParameters(); + var typeArguments = symbol.GetTypeArguments(); + + var parameterStrings = parameters.Select(parameter => + { + var typeSymbol = parameter.Type; + return GetOnTheFlyDocsRelevantFileInfo(typeSymbol); + + }).ToImmutableArray(); + + var typeArgumentStrings = typeArguments.Select(typeArgument => + { + return GetOnTheFlyDocsRelevantFileInfo(typeArgument); + + }).ToImmutableArray(); + + return parameterStrings.AddRange(typeArgumentStrings); + + OnTheFlyDocsRelevantFileInfo? GetOnTheFlyDocsRelevantFileInfo(ITypeSymbol typeSymbol) + { + var typeSyntaxReference = typeSymbol.DeclaringSyntaxReferences.FirstOrDefault(); + if (typeSyntaxReference is not null) + { + var typeSpan = typeSyntaxReference.Span; + var syntaxReferenceDocument = solution.GetDocument(typeSyntaxReference.SyntaxTree); + if (syntaxReferenceDocument is not null) + { + return new OnTheFlyDocsRelevantFileInfo(syntaxReferenceDocument, typeSpan); + } + } + + return null; + } + } +} diff --git a/src/Features/CSharpTest/Copilot/CSharpImplementNotImplementedExceptionFixProviderTests.cs b/src/Features/CSharpTest/Copilot/CSharpImplementNotImplementedExceptionFixProviderTests.cs index 31c40582322a0..1bd47a290ce1f 100644 --- a/src/Features/CSharpTest/Copilot/CSharpImplementNotImplementedExceptionFixProviderTests.cs +++ b/src/Features/CSharpTest/Copilot/CSharpImplementNotImplementedExceptionFixProviderTests.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.QuickInfo; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; using Microsoft.CodeAnalysis.Text; @@ -696,5 +697,15 @@ public Task IsImplementNotImplementedExceptionsAvailableAsync(Cancellation { return Task.FromResult(true); } + + public Task GetOnTheFlyDocsPromptAsync(OnTheFlyDocsInfo onTheFlyDocsInfo, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsResponseAsync(string prompt, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } } } diff --git a/src/Features/Core/Portable/Copilot/ICopilotCodeAnalysisService.cs b/src/Features/Core/Portable/Copilot/ICopilotCodeAnalysisService.cs index 9b294d5a002b6..0df44b14c6463 100644 --- a/src/Features/Core/Portable/Copilot/ICopilotCodeAnalysisService.cs +++ b/src/Features/Core/Portable/Copilot/ICopilotCodeAnalysisService.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.QuickInfo; using Microsoft.CodeAnalysis.Text; namespace Microsoft.CodeAnalysis.Copilot; @@ -65,15 +66,17 @@ internal interface ICopilotCodeAnalysisService : ILanguageService Task StartRefinementSessionAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken); /// - /// Method to fetch the on-the-fly documentation based on a a symbols - /// and the code for the symbols in . - /// - /// is a formatted string representation of an .
- /// is a list of a code definitions from an . - /// is the language of the originating . - ///
+ /// Retrieves the prompt ///
- Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken); + /// Type containing code and other context about the symbol being examined. + /// + Task GetOnTheFlyDocsPromptAsync(OnTheFlyDocsInfo onTheFlyDocsInfo, CancellationToken cancellationToken); + + /// + /// Retrieves the response from Copilot summarizing what a symbol is being used for and whether or not the quota has exceeded. + /// + /// The input text used to generate the response. + Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsResponseAsync(string prompt, CancellationToken cancellationToken); /// /// Determines if the given is excluded in the workspace. diff --git a/src/Features/Core/Portable/QuickInfo/OnTheFlyDocsInfo.cs b/src/Features/Core/Portable/QuickInfo/OnTheFlyDocsInfo.cs index c1a69a7d374ff..2e46a28525140 100644 --- a/src/Features/Core/Portable/QuickInfo/OnTheFlyDocsInfo.cs +++ b/src/Features/Core/Portable/QuickInfo/OnTheFlyDocsInfo.cs @@ -13,12 +13,13 @@ namespace Microsoft.CodeAnalysis.QuickInfo; /// the symbol's declaration code /// the language of the symbol /// whether the symbol has comments -internal sealed class OnTheFlyDocsInfo(string symbolSignature, ImmutableArray declarationCode, string language, bool isContentExcluded, bool hasComments = false) +internal sealed class OnTheFlyDocsInfo(string symbolSignature, ImmutableArray declarationCode, string language, bool isContentExcluded, ImmutableArray additionalContext, bool hasComments = false) { public string SymbolSignature { get; } = symbolSignature; - public ImmutableArray DeclarationCode { get; } = declarationCode; + public ImmutableArray DeclarationCode { get; } = declarationCode; public string Language { get; } = language; public bool IsContentExcluded { get; set; } = isContentExcluded; + public ImmutableArray AdditionalContext { get; } = additionalContext; // Added for telemetry collection purposes. public bool HasComments { get; set; } = hasComments; diff --git a/src/Features/Core/Portable/QuickInfo/OnTheFlyDocsRelevantFileInfo.cs b/src/Features/Core/Portable/QuickInfo/OnTheFlyDocsRelevantFileInfo.cs new file mode 100644 index 0000000000000..aa5390dac3e35 --- /dev/null +++ b/src/Features/Core/Portable/QuickInfo/OnTheFlyDocsRelevantFileInfo.cs @@ -0,0 +1,20 @@ +// 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 Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.QuickInfo; + +internal sealed record OnTheFlyDocsRelevantFileInfo +{ + public Document Document { get; } + public TextSpan TextSpan { get; } + + public OnTheFlyDocsRelevantFileInfo(Document document, TextSpan textSpan) + { + Document = document; + TextSpan = textSpan; + } +} + diff --git a/src/Features/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs b/src/Features/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs index 8bd3c115522e8..397b31031be87 100644 --- a/src/Features/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs +++ b/src/Features/ExternalAccess/Copilot/Analyzer/IExternalCSharpCopilotCodeAnalysisService.cs @@ -17,6 +17,5 @@ internal interface IExternalCSharpCopilotCodeAnalysisService Task> AnalyzeDocumentAsync(Document document, TextSpan? span, string promptTitle, CancellationToken cancellationToken); Task> GetCachedDiagnosticsAsync(Document document, string promptTitle, CancellationToken cancellationToken); Task StartRefinementSessionAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken); - Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken); Task IsFileExcludedAsync(string filePath, CancellationToken cancellationToken); } diff --git a/src/Features/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs b/src/Features/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs index a783f1bf7eae6..e5ae13688b414 100644 --- a/src/Features/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs +++ b/src/Features/ExternalAccess/Copilot/Internal/Analyzer/AbstractCopilotCodeAnalysisService.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.DocumentationComments; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.QuickInfo; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -41,7 +42,8 @@ internal abstract class AbstractCopilotCodeAnalysisService(IDiagnosticsRefresher protected abstract Task> AnalyzeDocumentCoreAsync(Document document, TextSpan? span, string promptTitle, CancellationToken cancellationToken); protected abstract Task> GetCachedDiagnosticsCoreAsync(Document document, string promptTitle, CancellationToken cancellationToken); protected abstract Task StartRefinementSessionCoreAsync(Document oldDocument, Document newDocument, Diagnostic? primaryDiagnostic, CancellationToken cancellationToken); - protected abstract Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsCoreAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken); + protected abstract Task GetOnTheFlyDocsPromptCoreAsync(OnTheFlyDocsInfo onTheFlyDocsInfo, CancellationToken cancellationToken); + protected abstract Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsResponseCoreAsync(string prompt, CancellationToken cancellationToken); protected abstract Task IsFileExcludedCoreAsync(string filePath, CancellationToken cancellationToken); protected abstract Task<(Dictionary? responseDictionary, bool isQuotaExceeded)> GetDocumentationCommentCoreAsync(DocumentationCommentProposal proposal, CancellationToken cancellationToken); protected abstract Task> ImplementNotImplementedExceptionsCoreAsync(Document document, ImmutableDictionary> methodOrProperties, CancellationToken cancellationToken); @@ -178,12 +180,16 @@ public async Task StartRefinementSessionAsync(Document oldDocument, Document new await StartRefinementSessionCoreAsync(oldDocument, newDocument, primaryDiagnostic, cancellationToken).ConfigureAwait(false); } - public async Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken) + public async Task GetOnTheFlyDocsPromptAsync(OnTheFlyDocsInfo onTheFlyDocsInfo, CancellationToken cancellationToken) + { + return await GetOnTheFlyDocsPromptCoreAsync(onTheFlyDocsInfo, cancellationToken).ConfigureAwait(false); + } + public async Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsResponseAsync(string prompt, CancellationToken cancellationToken) { if (!await IsAvailableAsync(cancellationToken).ConfigureAwait(false)) return (string.Empty, false); - return await GetOnTheFlyDocsCoreAsync(symbolSignature, declarationCode, language, cancellationToken).ConfigureAwait(false); + return await GetOnTheFlyDocsResponseCoreAsync(prompt, cancellationToken).ConfigureAwait(false); } public async Task IsFileExcludedAsync(string filePath, CancellationToken cancellationToken) diff --git a/src/Features/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs b/src/Features/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs index eb00e458c3742..7b64e019e5f80 100644 --- a/src/Features/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs +++ b/src/Features/ExternalAccess/Copilot/Internal/Analyzer/CSharp/CSharpCopilotCodeAnalysisService.cs @@ -18,6 +18,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.PooledObjects; +using Microsoft.CodeAnalysis.QuickInfo; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; @@ -28,6 +29,7 @@ internal sealed class CSharpCopilotCodeAnalysisService : AbstractCopilotCodeAnal { private IExternalCSharpCopilotCodeAnalysisService? AnalysisService { get; } private IExternalCSharpCopilotGenerateDocumentationService? GenerateDocumentationService { get; } + private IExternalCSharpOnTheFlyDocsService? OnTheFlyDocsService { get; } private IExternalCSharpCopilotGenerateImplementationService? GenerateImplementationService { get; } [ImportingConstructor] @@ -35,22 +37,26 @@ internal sealed class CSharpCopilotCodeAnalysisService : AbstractCopilotCodeAnal public CSharpCopilotCodeAnalysisService( [Import(AllowDefault = true)] IExternalCSharpCopilotCodeAnalysisService? externalCopilotService, [Import(AllowDefault = true)] IExternalCSharpCopilotGenerateDocumentationService? externalCSharpCopilotGenerateDocumentationService, + [Import(AllowDefault = true)] IExternalCSharpOnTheFlyDocsService? externalCSharpOnTheFlyDocsService, [Import(AllowDefault = true)] IExternalCSharpCopilotGenerateImplementationService? externalCSharpCopilotGenerateImplementationService, IDiagnosticsRefresher diagnosticsRefresher ) : base(diagnosticsRefresher) { if (externalCopilotService is null) - FatalError.ReportAndCatch(new ArgumentNullException(nameof(externalCSharpCopilotGenerateDocumentationService)), ErrorSeverity.Diagnostic); + FatalError.ReportAndCatch(new ArgumentNullException(nameof(externalCopilotService)), ErrorSeverity.Diagnostic); if (externalCSharpCopilotGenerateDocumentationService is null) FatalError.ReportAndCatch(new ArgumentNullException(nameof(externalCSharpCopilotGenerateDocumentationService)), ErrorSeverity.Diagnostic); - if (externalCSharpCopilotGenerateDocumentationService is null) + if (externalCSharpOnTheFlyDocsService is null) + FatalError.ReportAndCatch(new ArgumentNullException(nameof(externalCSharpOnTheFlyDocsService)), ErrorSeverity.Diagnostic); + + if (externalCSharpCopilotGenerateImplementationService is null) FatalError.ReportAndCatch(new ArgumentNullException(nameof(externalCSharpCopilotGenerateImplementationService)), ErrorSeverity.Diagnostic); AnalysisService = externalCopilotService; GenerateDocumentationService = externalCSharpCopilotGenerateDocumentationService; - GenerateImplementationService = externalCSharpCopilotGenerateImplementationService; + OnTheFlyDocsService = externalCSharpOnTheFlyDocsService; } protected override Task> AnalyzeDocumentCoreAsync(Document document, TextSpan? span, string promptTitle, CancellationToken cancellationToken) @@ -93,10 +99,18 @@ protected override Task StartRefinementSessionCoreAsync(Document oldDocument, Do return Task.CompletedTask; } - protected override Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsCoreAsync(string symbolSignature, ImmutableArray declarationCode, string language, CancellationToken cancellationToken) + protected override Task GetOnTheFlyDocsPromptCoreAsync(OnTheFlyDocsInfo onTheFlyDocsInfo, CancellationToken cancellationToken) { - if (AnalysisService is not null) - return AnalysisService.GetOnTheFlyDocsAsync(symbolSignature, declarationCode, language, cancellationToken); + if (OnTheFlyDocsService is not null) + return OnTheFlyDocsService.GetOnTheFlyDocsPromptAsync(new CopilotOnTheFlyDocsInfoWrapper(onTheFlyDocsInfo), cancellationToken); + + return Task.FromResult(string.Empty); + } + + protected override Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsResponseCoreAsync(string prompt, CancellationToken cancellationToken) + { + if (OnTheFlyDocsService is not null) + return OnTheFlyDocsService.GetOnTheFlyDocsResponseAsync(prompt, cancellationToken); return Task.FromResult((string.Empty, false)); } diff --git a/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt b/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt index 3f5b1f9b5612a..52c76edce89ff 100644 --- a/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt +++ b/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt @@ -18,6 +18,18 @@ Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotDocumentationCommentTagType Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotDocumentationCommentTagType.Returns = 3 -> Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotDocumentationCommentTagType Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotDocumentationCommentTagType.Summary = 0 -> Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotDocumentationCommentTagType Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotDocumentationCommentTagType.TypeParam = 1 -> Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotDocumentationCommentTagType +Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotOnTheFlyDocsInfoWrapper +Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotOnTheFlyDocsInfoWrapper.AdditionalContext.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotOnTheFlyDocsInfoWrapper.CopilotOnTheFlyDocsInfoWrapper(Microsoft.CodeAnalysis.QuickInfo.OnTheFlyDocsInfo! onTheFlyDocsInfo) -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotOnTheFlyDocsInfoWrapper.DeclarationCode.get -> System.Collections.Immutable.ImmutableArray +Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotOnTheFlyDocsInfoWrapper.HasComments.get -> bool +Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotOnTheFlyDocsInfoWrapper.IsContentExcluded.get -> bool +Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotOnTheFlyDocsInfoWrapper.Language.get -> string! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotOnTheFlyDocsInfoWrapper.SymbolSignature.get -> string! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotOnTheFlyDocsRelevantFileInfoWrapper +Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotOnTheFlyDocsRelevantFileInfoWrapper.CopilotOnTheFlyDocsRelevantFileInfoWrapper(Microsoft.CodeAnalysis.QuickInfo.OnTheFlyDocsRelevantFileInfo! onTheFlyDocsRelevantFileInfo) -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotOnTheFlyDocsRelevantFileInfoWrapper.Document.get -> Microsoft.CodeAnalysis.Document! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotOnTheFlyDocsRelevantFileInfoWrapper.TextSpan.get -> Microsoft.CodeAnalysis.Text.TextSpan Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotUtilities Microsoft.CodeAnalysis.ExternalAccess.Copilot.GenerateImplementation.ImplementationDetailsWrapper Microsoft.CodeAnalysis.ExternalAccess.Copilot.GenerateImplementation.ImplementationDetailsWrapper.ImplementationDetailsWrapper() -> void @@ -35,6 +47,9 @@ Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotCodeAnalysis Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotCodeAnalysisService.StartRefinementSessionAsync(Microsoft.CodeAnalysis.Document! oldDocument, Microsoft.CodeAnalysis.Document! newDocument, Microsoft.CodeAnalysis.Diagnostic? primaryDiagnostic, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotGenerateDocumentationService Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotGenerateDocumentationService.GetDocumentationCommentAsync(Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotDocumentationCommentProposalWrapper! proposal, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<(System.Collections.Generic.Dictionary? responseDictionary, bool isQuotaExceeded)>! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpOnTheFlyDocsService +Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpOnTheFlyDocsService.GetOnTheFlyDocsPromptAsync(Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotOnTheFlyDocsInfoWrapper! onTheFlyDocsInfo, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpOnTheFlyDocsService.GetOnTheFlyDocsResponseAsync(string! prompt, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task<(string! responseString, bool isQuotaExceeded)>! Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotGenerateImplementationService Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotGenerateImplementationService.ImplementNotImplementedExceptionsAsync(Microsoft.CodeAnalysis.Document! document, System.Collections.Immutable.ImmutableDictionary>! methodOrProperties, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task!>! Microsoft.CodeAnalysis.ExternalAccess.Copilot.RelatedDocuments.ICopilotRelatedDocumentsService diff --git a/src/Features/ExternalAccess/Copilot/OnTheFlyDocs/CopilotOnTheFlyDocsInfoWrapper.cs b/src/Features/ExternalAccess/Copilot/OnTheFlyDocs/CopilotOnTheFlyDocsInfoWrapper.cs new file mode 100644 index 0000000000000..c7e1cb312b447 --- /dev/null +++ b/src/Features/ExternalAccess/Copilot/OnTheFlyDocs/CopilotOnTheFlyDocsInfoWrapper.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.Collections.Immutable; +using Microsoft.CodeAnalysis.QuickInfo; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot; + +internal sealed class CopilotOnTheFlyDocsInfoWrapper +{ + private readonly OnTheFlyDocsInfo _onTheFlyDocsInfo; + private readonly ImmutableArray _wrappedDeclarationCode; + private readonly ImmutableArray _wrappedAdditionalContext; + + public CopilotOnTheFlyDocsInfoWrapper(OnTheFlyDocsInfo onTheFlyDocsInfo) + { + _onTheFlyDocsInfo = onTheFlyDocsInfo; + _wrappedDeclarationCode = _onTheFlyDocsInfo.DeclarationCode.SelectAsArray(c => c is not null ? new CopilotOnTheFlyDocsRelevantFileInfoWrapper(c) : null); + _wrappedAdditionalContext = _onTheFlyDocsInfo.AdditionalContext.SelectAsArray(c => c is not null ? new CopilotOnTheFlyDocsRelevantFileInfoWrapper(c) : null); + + } + + public string SymbolSignature => _onTheFlyDocsInfo.SymbolSignature; + public ImmutableArray DeclarationCode => _wrappedDeclarationCode; + public string Language => _onTheFlyDocsInfo.Language; + public bool IsContentExcluded => _onTheFlyDocsInfo.IsContentExcluded; + public ImmutableArray AdditionalContext => _wrappedAdditionalContext; + public bool HasComments => _onTheFlyDocsInfo.HasComments; +} + diff --git a/src/Features/ExternalAccess/Copilot/OnTheFlyDocs/CopilotOnTheFlyDocsRelevantFileInfoWrapper.cs b/src/Features/ExternalAccess/Copilot/OnTheFlyDocs/CopilotOnTheFlyDocsRelevantFileInfoWrapper.cs new file mode 100644 index 0000000000000..c56903c86cd82 --- /dev/null +++ b/src/Features/ExternalAccess/Copilot/OnTheFlyDocs/CopilotOnTheFlyDocsRelevantFileInfoWrapper.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 Microsoft.CodeAnalysis.QuickInfo; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot; + +internal sealed class CopilotOnTheFlyDocsRelevantFileInfoWrapper(OnTheFlyDocsRelevantFileInfo onTheFlyDocsRelevantFileInfo) +{ + private readonly OnTheFlyDocsRelevantFileInfo _onTheFlyDocsRelevantFileInfo = onTheFlyDocsRelevantFileInfo; + + public Document Document => _onTheFlyDocsRelevantFileInfo.Document; + public TextSpan TextSpan => _onTheFlyDocsRelevantFileInfo.TextSpan; +} diff --git a/src/Features/ExternalAccess/Copilot/OnTheFlyDocs/IExternalCSharpOnTheFlyDocsService.cs b/src/Features/ExternalAccess/Copilot/OnTheFlyDocs/IExternalCSharpOnTheFlyDocsService.cs new file mode 100644 index 0000000000000..ba83f33f96137 --- /dev/null +++ b/src/Features/ExternalAccess/Copilot/OnTheFlyDocs/IExternalCSharpOnTheFlyDocsService.cs @@ -0,0 +1,15 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot +{ + internal interface IExternalCSharpOnTheFlyDocsService + { + Task GetOnTheFlyDocsPromptAsync(CopilotOnTheFlyDocsInfoWrapper onTheFlyDocsInfo, CancellationToken cancellationToken); + Task<(string responseString, bool isQuotaExceeded)> GetOnTheFlyDocsResponseAsync(string prompt, CancellationToken cancellationToken); + } +}