Skip to content

Commit

Permalink
Merge pull request #73277 from dotnet/merges/release/dev17.11-to-main
Browse files Browse the repository at this point in the history
Merge release/dev17.11 to main
  • Loading branch information
dotnet-bot authored Apr 30, 2024
2 parents 5cf2e00 + 28985c0 commit acb75e2
Show file tree
Hide file tree
Showing 43 changed files with 1,100 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ private void ItemsPresenter_PreviewMouseUp(object sender, MouseButtonEventArgs e

private void InnerTextBox_GotFocus(object sender, RoutedEventArgs e)
{
e.Handled = true; // Prevent selecting all the text
if (!_smartRenameViewModel.IsUsingDropdown)
{
return;
Expand All @@ -185,8 +186,7 @@ private void InnerTextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
Assumes.NotNull(_dropDownPopup);
if ((e.Key is Key.Escape or Key.Space or Key.Enter)
&& (_dropDownPopup.IsOpen // Handle these keystrokes when dropdown is present
|| _smartRenameViewModel.IsUsingResultPanel && this.TextSelectionLength < this.Text.Length)) // Or when panel is present and text is not yet selected
&& _dropDownPopup.IsOpen) // Handle these keystrokes when dropdown is present
{
_dropDownPopup.IsOpen = false;
SelectAllText();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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.ComponentModel.Composition;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.VisualStudio.Language.StandardClassification;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Utilities;

namespace Microsoft.CodeAnalysis.Editor.QuickInfo;

internal sealed class ClassificationFormatDefinitions
{
[Export(typeof(EditorFormatDefinition))]
[ClassificationType(ClassificationTypeNames = ClassificationTypeDefinitions.ReducedEmphasisText)]
[Name(ClassificationTypeDefinitions.ReducedEmphasisText)]
[Order(After = Priority.High)]
[UserVisible(false)]
private class ReducedEmphasisTextFormat : ClassificationFormatDefinition
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public ReducedEmphasisTextFormat()
{
this.ForegroundOpacity = 0.65f;
}
}
}

internal sealed class ClassificationTypeDefinitions
{
// Only used for theming, does not need localized
public const string ReducedEmphasisText = "Reduced Emphasis Text";

[Export]
[Name(ReducedEmphasisText)]
[BaseDefinition(PredefinedClassificationTypeNames.Text)]
internal readonly ClassificationTypeDefinition? ReducedEmphasisTextTypeDefinition;
}
26 changes: 26 additions & 0 deletions src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsState.cs
Original file line number Diff line number Diff line change
@@ -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.

namespace Microsoft.CodeAnalysis.Editor.QuickInfo;

/// <summary>
/// Represents the potential states of the view.
/// </summary>
internal enum OnTheFlyDocsState
{
/// <summary>
/// The view is displaying the on-demand hyperlink.
/// </summary>
OnDemandLink,

/// <summary>
/// The view is in the loading state.
/// </summary>
Loading,

/// <summary>
/// The view is displaying computed results.
/// </summary>
Finished,
}
32 changes: 32 additions & 0 deletions src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<UserControl x:Class="Microsoft.CodeAnalysis.Editor.QuickInfo.OnTheFlyDocsView"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Microsoft.CodeAnalysis.Editor.QuickInfo"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
AutomationProperties.Name="{Binding OnTheFlyDocumentation}">
<UserControl.Resources>
<local:OnTheFlyDocsViewStateVisibilityConverter x:Key="VisibilityConverter"></local:OnTheFlyDocsViewStateVisibilityConverter>
</UserControl.Resources>

<Grid>
<ContentControl
Visibility="{Binding Path=CurrentState,
Converter={StaticResource VisibilityConverter},
ConverterParameter={x:Static local:OnTheFlyDocsState.OnDemandLink}}"
Content="{Binding Path=OnDemandLinkContent}" />
<ContentControl
Visibility="{Binding Path=CurrentState,
Converter={StaticResource VisibilityConverter},
ConverterParameter={x:Static local:OnTheFlyDocsState.Loading}}"
Content="{Binding Path=LoadingContent}" />
<ContentControl
Visibility="{Binding Path=CurrentState,
Converter={StaticResource VisibilityConverter},
ConverterParameter={x:Static local:OnTheFlyDocsState.Finished}}"
Content="{Binding Path=ResultsContent}" />

</Grid>
</UserControl>
232 changes: 232 additions & 0 deletions src/EditorFeatures/Core.Wpf/QuickInfo/OnTheFlyDocsView.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
// 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.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Copilot;
using Microsoft.CodeAnalysis.Editor.Implementation.IntelliSense.QuickInfo;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.QuickInfo;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Text.Adornments;
using Microsoft.VisualStudio.Text.Editor;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Editor.QuickInfo;

/// <summary>
/// Interaction logic for OnTheFlyDocsView.xaml.
/// </summary>
internal sealed partial class OnTheFlyDocsView : UserControl, INotifyPropertyChanged
{
private readonly ITextView _textView;
private readonly IViewElementFactoryService _viewElementFactoryService;
private readonly IAsynchronousOperationListener _asyncListener;
private readonly IAsyncQuickInfoSession _asyncQuickInfoSession;
private readonly IThreadingContext _threadingContext;
private readonly Document _document;
private readonly OnTheFlyDocsElement _onTheFlyDocsElement;
private readonly ContentControl _responseControl = new();
private readonly CancellationTokenSource _cancellationTokenSource = new();

private OnTheFlyDocsState _currentState = OnTheFlyDocsState.OnDemandLink;

/// <inheritdoc/>
public event PropertyChangedEventHandler? PropertyChanged;

/// <summary>
/// Event that fires when the user requests results.
/// </summary>
public event EventHandler ResultsRequested;

#pragma warning disable CA1822 // Mark members as static
/// <summary>
/// Used to display the "On the fly documentation" directly in the associated XAML file.
/// </summary>
public string OnTheFlyDocumentation => EditorFeaturesResources.On_the_fly_documentation;
#pragma warning restore CA1822 // Mark members as static

public OnTheFlyDocsView(ITextView textView, IViewElementFactoryService viewElementFactoryService, IAsynchronousOperationListenerProvider listenerProvider, IAsyncQuickInfoSession asyncQuickInfoSession, IThreadingContext threadingContext, EditorFeaturesOnTheFlyDocsElement editorFeaturesOnTheFlyDocsElement)
{
_textView = textView;
_viewElementFactoryService = viewElementFactoryService;
_asyncListener = listenerProvider.GetListener(FeatureAttribute.OnTheFlyDocs);
_asyncQuickInfoSession = asyncQuickInfoSession;
_threadingContext = threadingContext;
_onTheFlyDocsElement = editorFeaturesOnTheFlyDocsElement.OnTheFlyDocsElement;
_document = editorFeaturesOnTheFlyDocsElement.Document;

var sparkle = new ImageElement(new VisualStudio.Core.Imaging.ImageId(CopilotConstants.CopilotIconMonikerGuid, CopilotConstants.CopilotIconSparkleId));

OnDemandLinkContent = ToUIElement(
new ContainerElement(
ContainerElementStyle.Wrapped,
new object[]
{
sparkle,
ClassifiedTextElement.CreateHyperlink(EditorFeaturesResources.Tell_me_more, EditorFeaturesResources.Show_an_AI_generated_summary_of_this_code, () =>
RequestResults()),
}));

LoadingContent = ToUIElement(
new ContainerElement(
ContainerElementStyle.Stacked,
new object[]
{
new ClassifiedTextElement(new ClassifiedTextRun(
ClassificationTypeDefinitions.ReducedEmphasisText, EditorFeaturesResources.GitHub_Copilot_thinking)),
new SmoothProgressBar { IsIndeterminate = true, Height = 2, Margin = new Thickness { Top = 2 } },
}));

// Ensure the loading content stretches so that the progress bar
// takes the entire width of the quick info tooltip.
if (LoadingContent is FrameworkElement element)
{
element.HorizontalAlignment = HorizontalAlignment.Stretch;
}

ResultsContent = ToUIElement(
new ContainerElement(
ContainerElementStyle.Stacked,
new object[]
{
new ContainerElement(
ContainerElementStyle.Wrapped,
new object[]
{
sparkle,
ClassifiedTextElement.CreatePlainText(EditorFeaturesResources.GitHub_Copilot),
}),
new ThematicBreakElement(),
_responseControl,
new ThematicBreakElement(),
new ClassifiedTextElement(new ClassifiedTextRun(
ClassificationTypeDefinitions.ReducedEmphasisText, EditorFeaturesResources.AI_generated_content_may_be_inaccurate)),
}));

ResultsRequested += (_, _) => PopulateAIDocumentationElements(_cancellationTokenSource.Token);
_asyncQuickInfoSession.StateChanged += (_, _) => OnQuickInfoSessionChanged();
InitializeComponent();
}

/// <summary>
/// Retrieves the documentation for the given symbol from the Copilot service and displays it in the view.
/// </summary>
private void PopulateAIDocumentationElements(CancellationToken cancellationToken)
{
var token = _asyncListener.BeginAsyncOperation(nameof(SetResultTextAsync));
var copilotService = _document.GetLanguageService<ICopilotCodeAnalysisService>();
if (copilotService is not null)
{
_ = SetResultTextAsync(copilotService, cancellationToken).CompletesAsyncOperation(token);
}
}

private async Task SetResultTextAsync(ICopilotCodeAnalysisService copilotService, CancellationToken cancellationToken)
{
var stopwatch = SharedStopwatch.StartNew();

try
{
var response = await copilotService.GetOnTheFlyDocsAsync(_onTheFlyDocsElement.SymbolSignature, _onTheFlyDocsElement.DeclarationCode, _onTheFlyDocsElement.Language, cancellationToken).ConfigureAwait(false);
var copilotRequestTime = stopwatch.Elapsed;

await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);

cancellationToken.ThrowIfCancellationRequested();

if (response is null || response.Length == 0)
{
SetResultText(EditorFeaturesResources.An_error_occurred_while_generating_documentation_for_this_code);
CurrentState = OnTheFlyDocsState.Finished;
Logger.Log(FunctionId.Copilot_On_The_Fly_Docs_Error_Displayed, KeyValueLogMessage.Create(m =>
{
m["ElapsedTime"] = copilotRequestTime;
}, LogLevel.Information));
}
else
{
SetResultText(response);
CurrentState = OnTheFlyDocsState.Finished;

Logger.Log(FunctionId.Copilot_On_The_Fly_Docs_Results_Displayed, KeyValueLogMessage.Create(m =>
{
m["ElapsedTime"] = copilotRequestTime;
m["ResponseLength"] = response.Length;
}, LogLevel.Information));
}
}
catch (OperationCanceledException)
{
Logger.Log(FunctionId.Copilot_On_The_Fly_Docs_Results_Canceled, KeyValueLogMessage.Create(m =>
{
m["ElapsedTime"] = stopwatch.Elapsed;
}, LogLevel.Information));
}
catch (Exception e) when (FatalError.ReportAndCatch(e))
{
}
}

private void OnQuickInfoSessionChanged()
{
if (_asyncQuickInfoSession.State == QuickInfoSessionState.Dismissed)
{
_cancellationTokenSource.Cancel();
}
}

public OnTheFlyDocsState CurrentState
{
get => _currentState;
set => OnPropertyChanged(ref _currentState, value);
}

public UIElement OnDemandLinkContent { get; }

public UIElement LoadingContent { get; }

public UIElement ResultsContent { get; }

public void RequestResults()
{
CurrentState = OnTheFlyDocsState.Loading;
Logger.Log(FunctionId.Copilot_On_The_Fly_Docs_Loading_State_Entered, KeyValueLogMessage.Create(m =>
{
m["SymbolHeaderText"] = _onTheFlyDocsElement.SymbolSignature;
}, LogLevel.Information));

ResultsRequested?.Invoke(this, EventArgs.Empty);
}

/// <summary>
/// Sets the text of the AI-generated response.
/// </summary>
/// <param name="text">Response text to display.</param>
public void SetResultText(string text)
{
_responseControl.Content = ToUIElement(
new ContainerElement(ContainerElementStyle.Wrapped, new ClassifiedTextElement([new ClassifiedTextRun(ClassificationTypeNames.Text, text)])));
}

private void OnPropertyChanged<T>(ref T member, T value, [CallerMemberName] string? name = null)
{
member = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}

private UIElement ToUIElement(object model)
=> _viewElementFactoryService.CreateViewElement<UIElement>(_textView, model);
}
Loading

0 comments on commit acb75e2

Please sign in to comment.