Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Inline Rename] Update the UI related to Copilot #71332

Merged
merged 14 commits into from
Dec 30, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,10 @@
<ColumnDefinition Width="22" />
</Grid.ColumnDefinitions>

<TextBox
<rename:RenameUserInputPresenter
Grid.Column="0"
x:Name="IdentifierTextBox"
Text="{Binding IdentifierText, UpdateSourceTrigger=PropertyChanged}"
GotFocus="IdentifierTextBox_GotFocus"
HorizontalAlignment="Stretch"
PreviewKeyDown="IdentifierTextBox_KeyDown"/>
x:Name="RenameUserInputPresenter"
HorizontalAlignment="Stretch"/>

<!-- Expand/Collapse button and glyph -->
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Media;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
Expand All @@ -32,6 +33,10 @@
private readonly IAsyncQuickInfoBroker _asyncQuickInfoBroker;
private readonly IAsynchronousOperationListener _listener;
private readonly IThreadingContext _threadingContext;
private readonly Lazy<RenameUserInputTextBox> _identifierTextBox;
veler marked this conversation as resolved.
Show resolved Hide resolved
private readonly Lazy<SmartRenameUserInputComboBox> _smartRenameUserInputComboBox;

private IRenameUserInput RenameUserInput => _viewModel.SmartRenameViewModel is null ? _identifierTextBox.Value : _smartRenameUserInputComboBox.Value;

public RenameFlyout(
RenameFlyoutViewModel viewModel,
Expand All @@ -51,20 +56,25 @@
_listener = listenerProvider.GetListener(FeatureAttribute.InlineRenameFlyout);
_threadingContext = threadingContext;
_wpfThemeService = themeService;
_identifierTextBox = new(() => new RenameUserInputTextBox(_viewModel));
_smartRenameUserInputComboBox = new(() => new SmartRenameUserInputComboBox(_viewModel));

// On load focus the first tab target
Loaded += (s, e) =>
{
// Wait until load to position adornment for space negotiation
PositionAdornment();

IdentifierTextBox.Focus();
IdentifierTextBox.Select(_viewModel.StartingSelection.Start, _viewModel.StartingSelection.Length);
IdentifierTextBox.SelectionChanged += IdentifierTextBox_SelectionChanged;
RenameUserInput.Focus();
RenameUserInput.SelectText(_viewModel.StartingSelection.Start, _viewModel.StartingSelection.Length);
RenameUserInput.TextSelectionChanged += RenameUserInput_TextSelectionChanged;
};

InitializeComponent();

RenameUserInputPresenter.Content = RenameUserInput;
RenameUserInput.PreviewKeyDown += RenameUserInput_PreviewKeyDown;

// If smart rename is available, insert the control after the identifier text box.
if (viewModel.SmartRenameViewModel is not null)
veler marked this conversation as resolved.
Show resolved Hide resolved
{
Expand Down Expand Up @@ -195,9 +205,9 @@
case Key.Tab:
// We don't want tab to lose focus for the adornment, so manually
// loop focus back to the first item that is focusable.
FrameworkElement lastItem = _viewModel.IsExpanded

Check failure on line 208 in src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml.cs

View check run for this annotation

Azure Pipelines / roslyn-CI (Correctness Correctness_Analyzers)

src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml.cs#L208

src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml.cs(208,21): error IDE0007: (NETCORE_ENGINEERING_TELEMETRY=Build) use 'var' instead of explicit type (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0007)

Check failure on line 208 in src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml.cs

View check run for this annotation

Azure Pipelines / roslyn-CI

src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml.cs#L208

src/EditorFeatures/Core.Wpf/InlineRename/UI/Adornment/RenameFlyout.xaml.cs(208,21): error IDE0007: (NETCORE_ENGINEERING_TELEMETRY=Build) use 'var' instead of explicit type (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0007)
veler marked this conversation as resolved.
Show resolved Hide resolved
? FileRenameCheckbox
: IdentifierTextBox;
: (FrameworkElement)RenameUserInput;

if (lastItem.IsFocused)
{
Expand All @@ -209,24 +219,19 @@
}
}

private void IdentifierTextBox_GotFocus(object sender, RoutedEventArgs e)
veler marked this conversation as resolved.
Show resolved Hide resolved
{
IdentifierTextBox.SelectAll();
}

private void Adornment_ConsumeMouseEvent(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
}

private void Adornment_GotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
if (e.OldFocus == this)
if (e.OldFocus == this || e.OldFocus == RenameUserInput)
{
return;
}

IdentifierTextBox.Focus();
RenameUserInput.Focus();
e.Handled = true;
}

Expand All @@ -239,38 +244,38 @@
/// Respond to selection/cursor changes in the textbox the user is editing by
/// applying the same selection to the textview that initiated the command
/// </summary>
private void IdentifierTextBox_SelectionChanged(object sender, RoutedEventArgs e)
private void RenameUserInput_TextSelectionChanged(object sender, RoutedEventArgs e)
veler marked this conversation as resolved.
Show resolved Hide resolved
{
// When user is editing the text or make selection change in the text box, sync the selection with text view
if (!this.IdentifierTextBox.IsFocused)
if (!this.RenameUserInput.IsFocused)
{
return;
}

var start = IdentifierTextBox.SelectionStart;
var length = IdentifierTextBox.SelectionLength;
var start = RenameUserInput.TextSelectionStart;
var length = RenameUserInput.TextSelectionLength;

var buffer = _viewModel.InitialTrackingSpan.TextBuffer;
var startPoint = _viewModel.InitialTrackingSpan.GetStartPoint(buffer.CurrentSnapshot);
_textView.SetSelection(new SnapshotSpan(startPoint + start, length));
}

private void IdentifierTextBox_KeyDown(object sender, KeyEventArgs e)
private void RenameUserInput_PreviewKeyDown(object sender, KeyEventArgs e)
{
// When smart rename is available, allow the user choose the suggestions using the up/down keys.
_threadingContext.ThrowIfNotOnUIThread();
var smartRenameViewModel = _viewModel.SmartRenameViewModel;
if (smartRenameViewModel is not null)
{
var currentIdentifier = IdentifierTextBox.Text;
var currentIdentifier = RenameUserInput.Text;
if (e.Key is Key.Down or Key.Up)
{
var newIdentifier = smartRenameViewModel.ScrollSuggestions(currentIdentifier, down: e.Key == Key.Down);
if (newIdentifier is not null)
{
_viewModel.IdentifierText = newIdentifier;
// Place the cursor at the end of the input text box.
IdentifierTextBox.Select(newIdentifier.Length, 0);
RenameUserInput.SelectText(newIdentifier.Length, 0);
e.Handled = true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,22 @@ public RenameFlyoutViewModel(
var smartRenameSession = smartRenameSessionFactory?.Value.CreateSmartRenameSession(_session.TriggerSpan);
if (smartRenameSession is not null)
{
SmartRenameViewModel = new SmartRenameViewModel(threadingContext, listenerProvider, smartRenameSession.Value);
SmartRenameViewModel = new SmartRenameViewModel(threadingContext, listenerProvider, smartRenameSession.Value, this);
SmartRenameViewModel.OnSelectedSuggestedNameChanged += OnSuggestedNameSelected;
SmartRenameViewModel.PropertyChanged += SmartRenameViewModel_PropertyChanged;
veler marked this conversation as resolved.
Show resolved Hide resolved
}

RegisterOleComponent();
}

private void SmartRenameViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
veler marked this conversation as resolved.
Show resolved Hide resolved
{
if (e.PropertyName == nameof(SmartRenameViewModel.CurrentIdentifierText) && SmartRenameViewModel != null)
{
this.IdentifierText = SmartRenameViewModel.CurrentIdentifierText;
}
}

private void OnSuggestedNameSelected(object sender, string? selectedName)
{
// When user clicks one of the suggestions, update the IdentifierTextBox content to it.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<TextBox x:Class="Microsoft.CodeAnalysis.Editor.Implementation.InlineRename.RenameUserInputTextBox"
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:vsfx="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0"
mc:Ignorable="d"
Style="{StaticResource {x:Static vsfx:VsResourceKeys.TextBoxStyleKey}}"
Text="{Binding IdentifierText, UpdateSourceTrigger=PropertyChanged}"/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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;
using System.Windows.Controls;
using System.Windows.Input;

namespace Microsoft.CodeAnalysis.Editor.Implementation.InlineRename;

/// <summary>
/// Interaction logic for RenameUserInputTextBox.xaml
/// </summary>
public partial class RenameUserInputTextBox : TextBox, IRenameUserInput
{
internal RenameUserInputTextBox(RenameFlyoutViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}

public int TextSelectionStart
{
get => SelectionStart;
set => SelectionStart = value;
}

public int TextSelectionLength
{
get => SelectionLength;
set => SelectionLength = value;
}

public event RoutedEventHandler? TextSelectionChanged
{
add
{
AddHandler(SelectionChangedEvent, value, handledEventsToo: false);
}
remove
{
RemoveHandler(SelectionChangedEvent, value);
}
}

event KeyEventHandler? IRenameUserInput.PreviewKeyDown
{
add
{
AddHandler(PreviewKeyDownEvent, value, handledEventsToo: false);
}
remove
{
RemoveHandler(PreviewKeyDownEvent, value);
}
}

public void SelectText(int start, int length)
{
Select(start, length);
}

void IRenameUserInput.Focus()
{
this.Focus();
}
}
29 changes: 29 additions & 0 deletions src/EditorFeatures/Core.Wpf/InlineRename/UI/IRenameUserInput.cs
Original file line number Diff line number Diff line change
@@ -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;
using System.Windows.Input;

namespace Microsoft.CodeAnalysis.Editor.Implementation.InlineRename;

internal interface IRenameUserInput
{
string Text { get; set; }

bool IsFocused { get; }

int TextSelectionStart { get; set; }

int TextSelectionLength { get; set; }

event RoutedEventHandler? TextSelectionChanged;

event RoutedEventHandler? GotFocus;

event KeyEventHandler? PreviewKeyDown;

void Focus();

void SelectText(int start, int length);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// 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;

namespace Microsoft.CodeAnalysis.Editor.Implementation.InlineRename;

public sealed class RenameUserInputPresenter : ContentPresenter
{
internal IRenameUserInput? RenameUserInput => Content as IRenameUserInput;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,82 +10,15 @@
Margin="0 0 0 5">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<SolidColorBrush x:Key="copilotBranding" Color="#6F66E3"/>

<Style x:Key="CopilotListViewStyle" TargetType="ListView">
<Setter Property="ItemsSource" Value="{Binding SuggestedNames}"/>
<Setter Property="Visibility" Value="{Binding HasSuggestions, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="BorderBrush" Value="{StaticResource copilotBranding}"/>

<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" Width="238" HorizontalAlignment="Left"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>

<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border Margin="0 0 4 4"
CornerRadius="5"
BorderThickness="1"
BorderBrush="{StaticResource copilotBranding}"
Padding="2"
Background="{DynamicResource {x:Static vsfx:VsBrushes.CommandBarMenuBackgroundGradientBeginKey}}"
Cursor="Hand"
MouseDown="Suggestion_MouseDown"
Tag="{Binding}">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>

<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"
Padding="2 0 2 0"
Foreground="{DynamicResource {x:Static vsfx:VsBrushes.NewProjectProviderHoverForegroundKey}}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>

<StackPanel Orientation="Horizontal" Grid.Row="0" Visibility="{Binding Path=IsInProgress, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}">
<platformUI:ProgressControl/>
<TextBlock Text="{Binding GeneratingSuggestions}" Margin="2 0 2 0" Foreground="{DynamicResource {x:Static vsfx:VsBrushes.NewProjectProviderHoverForegroundKey}}"/>
</StackPanel>

<StackPanel Orientation="Vertical">
<TextBlock
x:Uid="ErrorTextBlock"
x:Name="ErrorTextBlock"
MaxWidth="400"
TextWrapping="Wrap"
Text="{Binding StatusMessage}"
Visibility="{Binding Path=StatusMessageVisibility, Converter={StaticResource BooleanToVisibilityConverter}, Mode=OneWay}"
Foreground="{DynamicResource {x:Static vsfx:VsBrushes.NewProjectProviderHoverForegroundKey}}"
Grid.Row="1"/>

<ListView Style="{StaticResource CopilotListViewStyle}" Grid.Row="2" />
</Grid>
Foreground="{DynamicResource {x:Static vsfx:VsBrushes.NewProjectProviderHoverForegroundKey}}"/>
</StackPanel>
</UserControl>
Loading
Loading