diff --git a/Roslyn.sln b/Roslyn.sln index 34d550842ddc4..e78ca2d58f566 100644 --- a/Roslyn.sln +++ b/Roslyn.sln @@ -579,6 +579,8 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.CodeAnalysis.Cont EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CodeAnalysis.Contracts.Package", "src\Dependencies\Contracts\Microsoft.CodeAnalysis.Contracts.Package.csproj", "{A8D5CFFA-7F9E-C35B-4F19-D63F6EC1D5CA}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.VisualStudio.LanguageServices.ExternalAccess.Copilot", "src\VisualStudio\ExternalAccess\Copilot\Microsoft.VisualStudio.LanguageServices.ExternalAccess.Copilot.csproj", "{9EB058F3-10C9-8F3F-AD9E-72CB362A0928}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1365,6 +1367,10 @@ Global {B1481D94-682E-46EC-ADBE-A16EB46FEEE9}.Debug|Any CPU.Build.0 = Debug|Any CPU {B1481D94-682E-46EC-ADBE-A16EB46FEEE9}.Release|Any CPU.ActiveCfg = Release|Any CPU {B1481D94-682E-46EC-ADBE-A16EB46FEEE9}.Release|Any CPU.Build.0 = Release|Any CPU + {5D60CF30-28A9-9F0F-7610-D90E4A69AE73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5D60CF30-28A9-9F0F-7610-D90E4A69AE73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5D60CF30-28A9-9F0F-7610-D90E4A69AE73}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5D60CF30-28A9-9F0F-7610-D90E4A69AE73}.Release|Any CPU.Build.0 = Release|Any CPU {09AEDEE4-6358-47C9-8022-3BD37A518070}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {09AEDEE4-6358-47C9-8022-3BD37A518070}.Debug|Any CPU.Build.0 = Debug|Any CPU {09AEDEE4-6358-47C9-8022-3BD37A518070}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1409,10 +1415,6 @@ Global {900168D7-D982-86CE-5097-C5F173BA4D8B}.Debug|Any CPU.Build.0 = Debug|Any CPU {900168D7-D982-86CE-5097-C5F173BA4D8B}.Release|Any CPU.ActiveCfg = Release|Any CPU {900168D7-D982-86CE-5097-C5F173BA4D8B}.Release|Any CPU.Build.0 = Release|Any CPU - {5D60CF30-28A9-9F0F-7610-D90E4A69AE73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5D60CF30-28A9-9F0F-7610-D90E4A69AE73}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5D60CF30-28A9-9F0F-7610-D90E4A69AE73}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5D60CF30-28A9-9F0F-7610-D90E4A69AE73}.Release|Any CPU.Build.0 = Release|Any CPU {2559DAF9-784E-4C29-E0E1-70204B1FD56E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2559DAF9-784E-4C29-E0E1-70204B1FD56E}.Debug|Any CPU.Build.0 = Debug|Any CPU {2559DAF9-784E-4C29-E0E1-70204B1FD56E}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1421,6 +1423,10 @@ Global {A8D5CFFA-7F9E-C35B-4F19-D63F6EC1D5CA}.Debug|Any CPU.Build.0 = Debug|Any CPU {A8D5CFFA-7F9E-C35B-4F19-D63F6EC1D5CA}.Release|Any CPU.ActiveCfg = Release|Any CPU {A8D5CFFA-7F9E-C35B-4F19-D63F6EC1D5CA}.Release|Any CPU.Build.0 = Release|Any CPU + {9EB058F3-10C9-8F3F-AD9E-72CB362A0928}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9EB058F3-10C9-8F3F-AD9E-72CB362A0928}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9EB058F3-10C9-8F3F-AD9E-72CB362A0928}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9EB058F3-10C9-8F3F-AD9E-72CB362A0928}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1666,6 +1672,7 @@ Global {A833B11C-5072-4A1F-A32B-2700433B0D3D} = {806F0C6F-3640-4C92-8D55-6767B1535467} {8988270E-393A-4B92-AC1A-534F903CFD34} = {8977A560-45C2-4EC2-A849-97335B382C74} {B1481D94-682E-46EC-ADBE-A16EB46FEEE9} = {55A62CFA-1155-46F1-ADF3-BEEE51B58AB5} + {5D60CF30-28A9-9F0F-7610-D90E4A69AE73} = {58A2876A-618D-4AE6-A136-E44B42BBDE11} {09AEDEE4-6358-47C9-8022-3BD37A518070} = {5880FECB-91F1-4AB8-8726-75EAFA8A918E} {5BABC440-4F1B-46E8-9068-DD7F02ED25D3} = {3E5FE3DB-45F7-4D83-9097-8F05D3B3AEC6} {5762E483-75CE-4328-A410-511F30737712} = {3E5FE3DB-45F7-4D83-9097-8F05D3B3AEC6} @@ -1683,11 +1690,11 @@ Global {806F0C6F-3640-4C92-8D55-6767B1535467} = {D449D505-CC6A-4E0B-AF1B-976E2D0AE67A} {7465CE63-A7B7-475F-8C57-48D2F8BC665A} = {D449D505-CC6A-4E0B-AF1B-976E2D0AE67A} {900168D7-D982-86CE-5097-C5F173BA4D8B} = {806F0C6F-3640-4C92-8D55-6767B1535467} - {5D60CF30-28A9-9F0F-7610-D90E4A69AE73} = {58A2876A-618D-4AE6-A136-E44B42BBDE11} {967723E8-4FDD-447B-99F6-4F8C47CB5433} = {C2D1346B-9665-4150-B644-075CF1636BAA} {2559DAF9-784E-4C29-E0E1-70204B1FD56E} = {C2D1346B-9665-4150-B644-075CF1636BAA} {BD974609-C68B-4BE6-9682-EB132462B50D} = {C2D1346B-9665-4150-B644-075CF1636BAA} {A8D5CFFA-7F9E-C35B-4F19-D63F6EC1D5CA} = {C2D1346B-9665-4150-B644-075CF1636BAA} + {9EB058F3-10C9-8F3F-AD9E-72CB362A0928} = {5880FECB-91F1-4AB8-8726-75EAFA8A918E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {604E6B91-7BC0-4126-AE07-D4D2FEFC3D29} diff --git a/eng/config/PublishData.json b/eng/config/PublishData.json index 8972709bd7985..fd1663b39f141 100644 --- a/eng/config/PublishData.json +++ b/eng/config/PublishData.json @@ -83,7 +83,7 @@ "Microsoft.CodeAnalysis.ExternalAccess.UnitTesting": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.VisualDiagnostics": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.Xamarin.Remote": "vs-impl", - "Microsoft.CodeAnalysis.ExternalAccess.Xaml": "vs-impl", + "Microsoft.CodeAnalysis.ExternalAccess.Xaml": "vs-impl", "Microsoft.CodeAnalysis.ExternalAccess.DotNetWatch": "vs-impl", "Microsoft.CodeAnalysis.LanguageServer.ExternalAccess.Copilot": "vs-impl", "Microsoft.CodeAnalysis.Remote.Razor.ServiceHub": "vs-impl", @@ -91,6 +91,7 @@ "Microsoft.CodeAnalysis.Remote.Workspaces": "vs-impl", "Microsoft.VisualStudio.LanguageServices.LiveShare": "vs-impl", "Microsoft.VisualStudio.LanguageServices.Razor.RemoteClient": "vs-impl", + "Microsoft.VisualStudio.LanguageServices.ExternalAccess.Copilot": "vs-impl", "Microsoft.CommonLanguageServerProtocol.Framework": "vs-impl", "Microsoft.CommonLanguageServerProtocol.Framework.Binary": "vs-impl" } diff --git a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj index ae74faa7622ea..e92b4dd72771f 100644 --- a/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj +++ b/src/EditorFeatures/Core/Microsoft.CodeAnalysis.EditorFeatures.csproj @@ -78,6 +78,7 @@ + diff --git a/src/Features/CSharp/Portable/SemanticSearch/CSharpSemanticSearchUtilities.cs b/src/Features/CSharp/Portable/SemanticSearch/CSharpSemanticSearchUtilities.cs index 77c6da34af215..bb4e2d8d84df3 100644 --- a/src/Features/CSharp/Portable/SemanticSearch/CSharpSemanticSearchUtilities.cs +++ b/src/Features/CSharp/Portable/SemanticSearch/CSharpSemanticSearchUtilities.cs @@ -17,7 +17,7 @@ internal sealed class CSharpSemanticSearchUtilities Query = """ static IEnumerable Find(Compilation compilation) { - return compilation.GlobalNamespace.GetMembers("C"); + return compilation.Assembly.GlobalNamespace.GetMembers("C"); } """, GlobalUsings = """ diff --git a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj index c3729dd724429..1424adb97aa32 100644 --- a/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj +++ b/src/Features/Core/Portable/Microsoft.CodeAnalysis.Features.csproj @@ -96,6 +96,7 @@ + diff --git a/src/Features/Core/Portable/SemanticSearch/ISemanticSearchCopilotService.cs b/src/Features/Core/Portable/SemanticSearch/ISemanticSearchCopilotService.cs new file mode 100644 index 0000000000000..895018ce8d485 --- /dev/null +++ b/src/Features/Core/Portable/SemanticSearch/ISemanticSearchCopilotService.cs @@ -0,0 +1,18 @@ +// 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.SemanticSearch; + +internal interface ISemanticSearchCopilotService +{ + bool IsAvailable { get; } + + /// + /// Translates natural language to C# query. + /// + ValueTask TryGetQueryAsync(string text, SemanticSearchCopilotContext context, CancellationToken cancellationToken); +} diff --git a/src/Features/Core/Portable/SemanticSearch/SemanticSearchCopilotContext.cs b/src/Features/Core/Portable/SemanticSearch/SemanticSearchCopilotContext.cs new file mode 100644 index 0000000000000..dcdfd21810a39 --- /dev/null +++ b/src/Features/Core/Portable/SemanticSearch/SemanticSearchCopilotContext.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; + +namespace Microsoft.CodeAnalysis.SemanticSearch; + +/// +/// Context necessary to generate Copilot prompt for semantic search query. +/// +internal sealed class SemanticSearchCopilotContext +{ + public required string ModelName { get; init; } + + /// + /// List of package names and versions that to include in the prompt. + /// + public required IEnumerable<(string name, Version version)> AvailablePackages { get; init; } +} + +internal readonly struct SemanticSearchCopilotGeneratedQuery +{ + /// + /// The generated code or an error message. + /// + public required string Text { get; init; } + + /// + /// True if is an error message. + /// + public required bool IsError { get; init; } +} + diff --git a/src/Features/ExternalAccess/Copilot/Internal/SemanticSearch/SemanticSearchCopilotServiceWrapper.cs b/src/Features/ExternalAccess/Copilot/Internal/SemanticSearch/SemanticSearchCopilotServiceWrapper.cs new file mode 100644 index 0000000000000..accb6ead3824b --- /dev/null +++ b/src/Features/ExternalAccess/Copilot/Internal/SemanticSearch/SemanticSearchCopilotServiceWrapper.cs @@ -0,0 +1,43 @@ +// 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.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch; +using Microsoft.CodeAnalysis.SemanticSearch; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.SemanticSearch; + +[Export(typeof(ISemanticSearchCopilotService)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class SemanticSearchCopilotServiceWrapper( + [Import(AllowDefault = true)] Lazy? impl) : ISemanticSearchCopilotService +{ + bool ISemanticSearchCopilotService.IsAvailable + => impl != null; + + async ValueTask ISemanticSearchCopilotService.TryGetQueryAsync(string text, SemanticSearchCopilotContext context, CancellationToken cancellationToken) + { + Contract.ThrowIfNull(impl); + + var result = await impl.Value.TryGetQueryAsync( + text, + new SemanticSearchCopilotContextImpl() + { + ModelName = context.ModelName, + AvailablePackages = context.AvailablePackages, + }, + cancellationToken).ConfigureAwait(false); + + return new SemanticSearchCopilotGeneratedQuery() + { + IsError = result.IsError, + Text = result.Text, + }; + } +} diff --git a/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt b/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt index aca2e87cc9e53..22b9cd3805bd8 100644 --- a/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt +++ b/src/Features/ExternalAccess/Copilot/InternalAPI.Unshipped.txt @@ -31,6 +31,22 @@ Microsoft.CodeAnalysis.ExternalAccess.Copilot.IExternalCSharpCopilotGenerateDocu 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.RelatedDocuments.ICopilotRelatedDocumentsService Microsoft.CodeAnalysis.ExternalAccess.Copilot.RelatedDocuments.ICopilotRelatedDocumentsService.GetRelatedDocumentIdsAsync(Microsoft.CodeAnalysis.Document! document, int position, System.Func, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask>! callbackAsync, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.ISemanticSearchCopilotServiceImpl +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.ISemanticSearchCopilotServiceImpl.TryGetQueryAsync(string! text, Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.SemanticSearchCopilotContextImpl! context, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.SemanticSearchCopilotContextImpl +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.SemanticSearchCopilotContextImpl.AvailablePackages.get -> System.Collections.Generic.IEnumerable<(string! name, System.Version! version)>! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.SemanticSearchCopilotContextImpl.AvailablePackages.init -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.SemanticSearchCopilotContextImpl.ModelName.get -> string! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.SemanticSearchCopilotContextImpl.ModelName.init -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.SemanticSearchCopilotContextImpl.SemanticSearchCopilotContextImpl() -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.SemanticSearchCopilotGeneratedQueryImpl +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.SemanticSearchCopilotGeneratedQueryImpl.IsError.get -> bool +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.SemanticSearchCopilotGeneratedQueryImpl.IsError.init -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.SemanticSearchCopilotGeneratedQueryImpl.SemanticSearchCopilotGeneratedQueryImpl() -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.SemanticSearchCopilotGeneratedQueryImpl.Text.get -> string! +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.CopilotChecksumWrapper.Equals(object? obj) -> bool override Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper.GetHashCode() -> int static Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper.Create(System.Collections.Immutable.ImmutableArray values) -> Microsoft.CodeAnalysis.ExternalAccess.Copilot.CopilotChecksumWrapper! diff --git a/src/Features/ExternalAccess/Copilot/SemanticSearch/ISemanticSearchCopilotServiceImpl.cs b/src/Features/ExternalAccess/Copilot/SemanticSearch/ISemanticSearchCopilotServiceImpl.cs new file mode 100644 index 0000000000000..4b3a34a53bb6b --- /dev/null +++ b/src/Features/ExternalAccess/Copilot/SemanticSearch/ISemanticSearchCopilotServiceImpl.cs @@ -0,0 +1,38 @@ +// 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch; + +internal interface ISemanticSearchCopilotServiceImpl +{ + ValueTask TryGetQueryAsync(string text, SemanticSearchCopilotContextImpl context, CancellationToken cancellationToken); +} + +internal sealed class SemanticSearchCopilotContextImpl +{ + public required string ModelName { get; init; } + + /// + /// List of package names and versions that to include in the prompt. + /// + public required IEnumerable<(string name, Version version)> AvailablePackages { get; init; } +} + +internal readonly struct SemanticSearchCopilotGeneratedQueryImpl +{ + /// + /// True if is an error message. + /// + public required bool IsError { get; init; } + + /// + /// The generated code or an error message. + /// + public required string Text { get; init; } +} diff --git a/src/VisualStudio/CSharp/Impl/SemanticSearch/CSharpSemanticSearchContentType.cs b/src/VisualStudio/CSharp/Impl/SemanticSearch/CSharpSemanticSearchContentType.cs new file mode 100644 index 0000000000000..2a503d3dd90d5 --- /dev/null +++ b/src/VisualStudio/CSharp/Impl/SemanticSearch/CSharpSemanticSearchContentType.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.ComponentModel.Composition; +using Microsoft.CodeAnalysis.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.LanguageServices.CSharp; + +internal static class CSharpSemanticSearchContentType +{ + public const string Name = "SemanticSearch-CSharp"; + + [Export] + [Name(Name)] + [BaseDefinition(ContentTypeNames.CSharpContentType)] + public static readonly ContentTypeDefinition Definition = null!; +} diff --git a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindowImpl.cs b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindowImpl.cs index d51c7b44a8e87..ad9cfd908e738 100644 --- a/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindowImpl.cs +++ b/src/VisualStudio/CSharp/Impl/SemanticSearch/SemanticSearchToolWindowImpl.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Immutable; using System.Composition; +using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -36,6 +37,7 @@ using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.TextManager.Interop; using Microsoft.VisualStudio.Threading; using Microsoft.VisualStudio.Utilities; @@ -61,6 +63,9 @@ internal sealed class SemanticSearchToolWindowImpl( IGlobalOptionService globalOptions, VisualStudioWorkspace workspace, IStreamingFindUsagesPresenter resultsPresenter, + ITextUndoHistoryRegistry undoHistoryRegistry, + ISemanticSearchCopilotService copilotService, + ISemanticSearchCopilotUIProvider copilotUIProvider, IVsService vsUIShellProvider) : ISemanticSearchWorkspaceHost, OptionsProvider { private const int ToolBarHeight = 26; @@ -68,7 +73,7 @@ internal sealed class SemanticSearchToolWindowImpl( private static readonly Lazy s_buttonTemplate = new(CreateButtonTemplate); - private readonly IContentType _contentType = contentTypeRegistry.GetContentType(ContentTypeNames.CSharpContentType); + private readonly IContentType _contentType = contentTypeRegistry.GetContentType(CSharpSemanticSearchContentType.Name); private readonly IAsynchronousOperationListener _asyncListener = listenerProvider.GetListener(FeatureAttribute.SemanticSearch); private readonly Lazy _semanticSearchWorkspace @@ -100,7 +105,9 @@ public async Task InitializeAsync(CancellationToken cancellati var vsUIShell = await vsUIShellProvider.GetValueAsync(cancellationToken).ConfigureAwait(false); - var textViewHost = CreateTextViewHost(vsUIShell); + var copilotUI = CreateCopilotUI(); + + var textViewHost = CreateTextViewHost(vsUIShell, copilotUI); var textViewControl = textViewHost.HostControl; _textView = textViewHost.TextView; _textBuffer = textViewHost.TextView.TextBuffer; @@ -112,7 +119,8 @@ public async Task InitializeAsync(CancellationToken cancellati var toolWindowGrid = new Grid(); toolWindowGrid.ColumnDefinitions.Add(new ColumnDefinition()); toolWindowGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(ToolBarHeight, GridUnitType.Pixel) }); - toolWindowGrid.RowDefinitions.Add(new RowDefinition()); + toolWindowGrid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto }); + toolWindowGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(1, GridUnitType.Star) }); var toolbarGrid = new Grid(); @@ -143,18 +151,30 @@ public async Task InitializeAsync(CancellationToken cancellati _cancelButton = cancelButton; toolWindowGrid.Children.Add(toolbarGrid); + + if (copilotUI != null) + { + toolWindowGrid.Children.Add(copilotUI.Control); + } + toolWindowGrid.Children.Add(textViewControl); toolbarGrid.Children.Add(executeButton); toolbarGrid.Children.Add(cancelButton); // placement within the tool window grid: - Grid.SetRow(textViewControl, 1); - Grid.SetColumn(textViewControl, 0); - Grid.SetRow(toolbarGrid, 0); Grid.SetColumn(toolbarGrid, 0); + if (copilotUI != null) + { + Grid.SetRow(copilotUI.Control, 1); + Grid.SetColumn(copilotUI.Control, 0); + } + + Grid.SetRow(textViewControl, 2); + Grid.SetColumn(textViewControl, 0); + // placement within the toolbar grid: Grid.SetRow(executeButton, 0); @@ -172,6 +192,101 @@ public async Task InitializeAsync(CancellationToken cancellati SemanticSearchWorkspace ISemanticSearchWorkspaceHost.Workspace => _semanticSearchWorkspace.Value; + private CopilotUI? CreateCopilotUI() + { + if (!copilotUIProvider.IsAvailable || !copilotService.IsAvailable) + { + return null; + } + + var outerGrid = new Grid() + { + Background = (Brush)Application.Current.FindResource(CommonControlsColors.TextBoxBackgroundBrushKey), + }; + + ImageThemingUtilities.SetImageBackgroundColor(outerGrid, (Color)Application.Current.Resources[CommonDocumentColors.PageBackgroundColorKey]); + ThemedDialogStyleLoader.SetUseDefaultThemedDialogStyles(outerGrid, true); + + // [ prompt border | empty ] + outerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + outerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + + var promptGrid = new Grid(); + + // [ input | panel ] + promptGrid.ColumnDefinitions.Add(new ColumnDefinition { MaxWidth = 600, Width = GridLength.Auto }); + promptGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); + + var promptTextBox = copilotUIProvider.GetTextBox(); + + var panel = new StackPanel() + { + Orientation = Orientation.Horizontal, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Bottom, + Margin = new Thickness(8, 8, 0, 8), + }; + + Grid.SetColumn(promptTextBox.Control, 0); + promptGrid.Children.Add(promptTextBox.Control); + + Grid.SetColumn(panel, 1); + promptGrid.Children.Add(panel); + + var promptGridBorder = new Border + { + Name = "PromptBorder", + BorderBrush = (Brush)Application.Current.Resources[EnvironmentColors.SystemHighlightBrushKey], + BorderThickness = new Thickness(1), + Child = promptGrid + }; + + Grid.SetColumn(promptGridBorder, 0); + outerGrid.Children.Add(promptGridBorder); + + // ComboBox for model selection + var modelPicker = new ComboBox + { + SelectedIndex = 0, + HorizontalAlignment = HorizontalAlignment.Right, + VerticalAlignment = VerticalAlignment.Top, + Margin = new Thickness(4, 0, 4, 0), + Height = 24, + IsEditable = false, + IsReadOnly = true, + BorderThickness = new Thickness(0), + MinHeight = 24, + VerticalContentAlignment = VerticalAlignment.Top, + TabIndex = 1, + Style = (Style)Application.Current.FindResource(VsResourceKeys.ComboBoxStyleKey) + }; + + modelPicker.Items.Add("gpt-4o"); + modelPicker.Items.Add("gpt-4o-mini"); + modelPicker.Items.Add("o1"); + modelPicker.Items.Add("o1-ga"); + modelPicker.Items.Add("o1-mini"); + + panel.Children.Add(modelPicker); + + var submitButton = CreateButton( + KnownMonikers.Send, + automationName: "Generate query", + acceleratorKey: "Ctrl+Enter", + toolTip: "Generate query"); + + panel.Children.Add(submitButton); + + submitButton.Click += (_, _) => SubmitCopilotQuery(promptTextBox.Text, modelPicker.Text); + + return new CopilotUI() + { + Control = outerGrid, + Input = promptTextBox, + ModelPicker = modelPicker, + }; + } + private static Button CreateButton( Imaging.Interop.ImageMoniker moniker, string automationName, @@ -250,7 +365,7 @@ private static ControlTemplate CreateButtonTemplate() """, context); } - private IWpfTextViewHost CreateTextViewHost(IVsUIShell vsUIShell) + private IWpfTextViewHost CreateTextViewHost(IVsUIShell vsUIShell, CopilotUI? copilotUI) { Contract.ThrowIfFalse(threadingContext.JoinableTaskContext.IsOnMainThread); @@ -289,7 +404,7 @@ private IWpfTextViewHost CreateTextViewHost(IVsUIShell vsUIShell) ErrorHandler.ThrowOnFailure(windowFrame.SetProperty((int)__VSFPROPID.VSFPROPID_ViewHelper, textViewAdapter)); - _ = new CommandFilter(this, textViewAdapter); + _ = new CommandFilter(this, textViewAdapter, copilotUI); return textViewHost; } @@ -315,6 +430,74 @@ private void UpdateUIState() _cancelButton.IsEnabled = isExecuting; } + private void SubmitCopilotQuery(string input, string model) + { + Contract.ThrowIfFalse(threadingContext.JoinableTaskContext.IsOnMainThread); + Contract.ThrowIfNull(_textBuffer); + Contract.ThrowIfNull(copilotService); + + // TODO: hook up cancel button for copilot queries + var cancellationSource = new CancellationTokenSource(); + + // TODO: fade out current content and show overlay spinner + + var completionToken = _asyncListener.BeginAsyncOperation(nameof(SemanticSearchToolWindow) + "." + nameof(SubmitCopilotQuery)); + _ = ExecuteAsync(cancellationSource.Token).ReportNonFatalErrorAsync().CompletesAsyncOperation(completionToken); + + async Task ExecuteAsync(CancellationToken cancellationToken) + { + await TaskScheduler.Default; + + SemanticSearchCopilotGeneratedQuery query; + + // TODO: generate list from SemanticSearch.ReferenceAssemblies: + var codeAnalysisVersion = new Version(4, 14, 0); + var sdkVersion = new Version(9, 0, 0); + + var context = new SemanticSearchCopilotContext() + { + ModelName = model, + AvailablePackages = + [ + ("Microsoft.CodeAnalysis", codeAnalysisVersion), + ("Microsoft.CodeAnalysis.CSharp", codeAnalysisVersion), + ("System.Collections.Immutable", sdkVersion), + ("System.Collections", sdkVersion), + ("System.Linq", sdkVersion), + ("System.Runtime", sdkVersion), + ] + }; + + try + { + query = await copilotService.TryGetQueryAsync(input, context, cancellationToken).ConfigureAwait(false); + } + catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken, ErrorSeverity.Critical)) + { + return; + } + catch (OperationCanceledException) + { + return; + } + + // Replace text buffer content. Allow using Ctrl+Z to revert to the previous content. + + await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None); + + Contract.ThrowIfFalse(undoHistoryRegistry.TryGetHistory(_textBuffer, out var undoHistory)); + using var undoTransaction = undoHistory.CreateTransaction(FeaturesResources.SemanticSearch); + + using (var edit = _textBuffer.CreateEdit()) + { + edit.Replace(0, _textBuffer.CurrentSnapshot.Length, query.Text); + edit.Apply(); + } + + undoTransaction.Complete(); + } + } + private void CancelQuery() { Contract.ThrowIfFalse(threadingContext.JoinableTaskContext.IsOnMainThread); @@ -485,7 +668,14 @@ public NavigableLocation GetNavigableLocation(TextSpan textSpan) public ValueTask GetOptionsAsync(Microsoft.CodeAnalysis.Host.LanguageServices languageServices, CancellationToken cancellationToken) => new(globalOptions.GetClassificationOptions(languageServices.Language)); - internal sealed class ResultsObserver(Document queryDocument, IFindUsagesContext presenterContext) : ISemanticSearchResultsObserver + private sealed class CopilotUI + { + public required FrameworkElement Control { get; init; } + public required ITextBoxControl Input { get; init; } + public required ComboBox ModelPicker { get; init; } + } + + private sealed class ResultsObserver(Document queryDocument, IFindUsagesContext presenterContext) : ISemanticSearchResultsObserver { public ValueTask OnDefinitionFoundAsync(DefinitionItem definition, CancellationToken cancellationToken) => presenterContext.OnDefinitionFoundAsync(definition, cancellationToken); @@ -509,19 +699,29 @@ public async ValueTask OnCompilationFailureAsync(ImmutableArray _copilotUI?.Input.View.HasAggregateFocus == true; + public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) - => _editorCommandTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); + { + var target = HasCopilotInputFocus ? _copilotUI.Input.CommandTarget : _editorCommandTarget; + + return target.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); + } public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { @@ -530,6 +730,12 @@ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pv switch ((VSConstants.VSStd2KCmdID)nCmdID) { case VSConstants.VSStd2KCmdID.OPENLINEABOVE: + if (HasCopilotInputFocus) + { + _window.SubmitCopilotQuery(_copilotUI.Input.Text, _copilotUI.ModelPicker.Text); + return VSConstants.S_OK; + } + if (!_window.IsExecutingUIState()) { _window.RunQuery(); @@ -549,7 +755,8 @@ public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pv } } - return _editorCommandTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); + var target = HasCopilotInputFocus ? _copilotUI.Input.CommandTarget : _editorCommandTarget; + return target.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); } } } diff --git a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj index 6bb7c7aca2d1f..7ecc7bdaa2455 100644 --- a/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj +++ b/src/VisualStudio/Core/Def/Microsoft.VisualStudio.LanguageServices.csproj @@ -91,6 +91,7 @@ + diff --git a/src/VisualStudio/Core/Def/SemanticSearch/ISemanticSearchCopilotUIProvider.cs b/src/VisualStudio/Core/Def/SemanticSearch/ISemanticSearchCopilotUIProvider.cs new file mode 100644 index 0000000000000..2c29f61682813 --- /dev/null +++ b/src/VisualStudio/Core/Def/SemanticSearch/ISemanticSearchCopilotUIProvider.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.Windows.Controls; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.CodeAnalysis.SemanticSearch; + +/// +/// Abstraction over Copilot prompt text box UI. +/// +internal interface ITextBoxControl +{ + Control Control { get; } + string Text { get; set; } + IOleCommandTarget CommandTarget { get; } + IWpfTextView View { get; } +} + +/// +/// Abstraction over Copilot prompt UI. +/// +internal interface ISemanticSearchCopilotUIProvider +{ + bool IsAvailable { get; } + ITextBoxControl GetTextBox(); +} diff --git a/src/VisualStudio/ExternalAccess/Copilot/.editorconfig b/src/VisualStudio/ExternalAccess/Copilot/.editorconfig new file mode 100644 index 0000000000000..8b8e284ec0a39 --- /dev/null +++ b/src/VisualStudio/ExternalAccess/Copilot/.editorconfig @@ -0,0 +1,3 @@ +[**] +dotnet_public_api_analyzer.skip_namespaces = Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal + diff --git a/src/VisualStudio/ExternalAccess/Copilot/Internal/SemanticSearch/SemanticSearchCopilotUIProviderWrapper.cs b/src/VisualStudio/ExternalAccess/Copilot/Internal/SemanticSearch/SemanticSearchCopilotUIProviderWrapper.cs new file mode 100644 index 0000000000000..12966f1629140 --- /dev/null +++ b/src/VisualStudio/ExternalAccess/Copilot/Internal/SemanticSearch/SemanticSearchCopilotUIProviderWrapper.cs @@ -0,0 +1,38 @@ +// 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.Composition; +using System.Windows.Controls; +using Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.SemanticSearch; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.SemanticSearch; + +[Export(typeof(ISemanticSearchCopilotUIProvider)), Shared] +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class SemanticSearchCopilotUIProviderWrapper( + [Import(AllowDefault = true)] Lazy? impl) : ISemanticSearchCopilotUIProvider +{ + private sealed class TextBoxWrapper(ITextBoxControlImpl impl) : ITextBoxControl + { + Control ITextBoxControl.Control => impl.Control; + string ITextBoxControl.Text { get => impl.Text; set => impl.Text = value; } + IOleCommandTarget ITextBoxControl.CommandTarget => impl.CommandTarget; + IWpfTextView ITextBoxControl.View => impl.View; + } + + bool ISemanticSearchCopilotUIProvider.IsAvailable + => impl != null; + + ITextBoxControl ISemanticSearchCopilotUIProvider.GetTextBox() + { + Contract.ThrowIfNull(impl); + return new TextBoxWrapper(impl.Value.GetTextBox()); + } +} diff --git a/src/VisualStudio/ExternalAccess/Copilot/InternalAPI.Shipped.txt b/src/VisualStudio/ExternalAccess/Copilot/InternalAPI.Shipped.txt new file mode 100644 index 0000000000000..7dc5c58110bfa --- /dev/null +++ b/src/VisualStudio/ExternalAccess/Copilot/InternalAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/VisualStudio/ExternalAccess/Copilot/InternalAPI.Unshipped.txt b/src/VisualStudio/ExternalAccess/Copilot/InternalAPI.Unshipped.txt new file mode 100644 index 0000000000000..d14e794951821 --- /dev/null +++ b/src/VisualStudio/ExternalAccess/Copilot/InternalAPI.Unshipped.txt @@ -0,0 +1,13 @@ +#nullable enable +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.ISemanticSearchCopilotUIProviderImpl +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.ISemanticSearchCopilotUIProviderImpl.GetTextBox() -> Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.ITextBoxControlImpl! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.ITextBoxControlImpl +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.ITextBoxControlImpl.CommandTarget.get -> Microsoft.VisualStudio.OLE.Interop.IOleCommandTarget! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.ITextBoxControlImpl.Control.get -> System.Windows.Controls.Control! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.ITextBoxControlImpl.Text.get -> string! +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.ITextBoxControlImpl.Text.set -> void +Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch.ITextBoxControlImpl.View.get -> Microsoft.VisualStudio.Text.Editor.IWpfTextView! +Microsoft.CodeAnalysis.SemanticSearch.ISemanticSearchCopilotUIProviderWrapper +Microsoft.CodeAnalysis.SemanticSearch.ISemanticSearchCopilotUIProviderWrapper.IsAvailable.get -> bool +Microsoft.CodeAnalysis.SemanticSearch.ISemanticSearchCopilotUIProviderWrapper.ISemanticSearchCopilotUIProviderWrapper(System.Lazy? impl) -> void +Microsoft.VisualStudio.LanguageServices.ExternalAccess.Copilot.Internal.ISemanticSearchCopilotUIProviderWrapper diff --git a/src/VisualStudio/ExternalAccess/Copilot/Microsoft.VisualStudio.LanguageServices.ExternalAccess.Copilot.csproj b/src/VisualStudio/ExternalAccess/Copilot/Microsoft.VisualStudio.LanguageServices.ExternalAccess.Copilot.csproj new file mode 100644 index 0000000000000..b3e5e06c4d54b --- /dev/null +++ b/src/VisualStudio/ExternalAccess/Copilot/Microsoft.VisualStudio.LanguageServices.ExternalAccess.Copilot.csproj @@ -0,0 +1,37 @@ + + + + + Library + Microsoft.CodeAnalysis.ExternalAccess.Copilot + net472 + true + + + true + Microsoft.VisualStudio.LanguageServices.ExternalAccess.Copilot + + A supporting package for Copilot features: + https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_git/VisualStudio.Conversations + + + + + + + + + + + + + + + + + + + + diff --git a/src/VisualStudio/ExternalAccess/Copilot/PublicAPI.Shipped.txt b/src/VisualStudio/ExternalAccess/Copilot/PublicAPI.Shipped.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/VisualStudio/ExternalAccess/Copilot/PublicAPI.Unshipped.txt b/src/VisualStudio/ExternalAccess/Copilot/PublicAPI.Unshipped.txt new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/src/VisualStudio/ExternalAccess/Copilot/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ + diff --git a/src/VisualStudio/ExternalAccess/Copilot/SemanticSearch/ISemanticSearchCopilotUIProviderImpl.cs b/src/VisualStudio/ExternalAccess/Copilot/SemanticSearch/ISemanticSearchCopilotUIProviderImpl.cs new file mode 100644 index 0000000000000..be63658590193 --- /dev/null +++ b/src/VisualStudio/ExternalAccess/Copilot/SemanticSearch/ISemanticSearchCopilotUIProviderImpl.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.Windows.Controls; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.CodeAnalysis.ExternalAccess.Copilot.SemanticSearch; + +internal interface ITextBoxControlImpl +{ + Control Control { get; } + string Text { get; set; } + IOleCommandTarget CommandTarget { get; } + IWpfTextView View { get; } +} + +internal interface ISemanticSearchCopilotUIProviderImpl +{ + ITextBoxControlImpl GetTextBox(); +} diff --git a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj index b95e19e642ebc..d54d721a87fa1 100644 --- a/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj +++ b/src/VisualStudio/Setup/Roslyn.VisualStudio.Setup.csproj @@ -235,6 +235,12 @@ BuiltProjectOutputGroup;SatelliteDllsProjectOutputGroup BindingRedirect + + Microsoft.VisualStudio.LanguageServices.ExternalAccess.Copilot + BuiltProjectOutputGroup + true + BindingRedirect + RemoteWorkspaces BuiltProjectOutputGroup;SatelliteDllsProjectOutputGroup diff --git a/src/VisualStudio/Setup/source.extension.vsixmanifest b/src/VisualStudio/Setup/source.extension.vsixmanifest index 545f5f5674c87..add0ea3be74b0 100644 --- a/src/VisualStudio/Setup/source.extension.vsixmanifest +++ b/src/VisualStudio/Setup/source.extension.vsixmanifest @@ -67,6 +67,7 @@ + diff --git a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj index 4196dfa0a75af..dcbbdeb8f2597 100644 --- a/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj +++ b/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj @@ -123,6 +123,7 @@ +