|
// 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.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
namespace Microsoft.CodeAnalysis.Editor.Implementation.InlineRename
{
/// <summary>
/// Interaction logic for InlineRenameAdornment.xaml
/// </summary>
internal partial class RenameFlyout : InlineRenameAdornment
{
private readonly RenameFlyoutViewModel _viewModel;
private readonly IWpfTextView _textView;
private readonly IAsyncQuickInfoBroker _asyncQuickInfoBroker;
private readonly IAsynchronousOperationListener _listener;
public RenameFlyout(
RenameFlyoutViewModel viewModel,
IWpfTextView textView,
IWpfThemeService? themeService,
IAsyncQuickInfoBroker asyncQuickInfoBroker,
IAsynchronousOperationListenerProvider listenerProvider)
{
DataContext = _viewModel = viewModel;
_textView = textView;
_asyncQuickInfoBroker = asyncQuickInfoBroker;
_textView.LayoutChanged += TextView_LayoutChanged;
_textView.ViewportHeightChanged += TextView_ViewPortChanged;
_textView.ViewportWidthChanged += TextView_ViewPortChanged;
_listener = listenerProvider.GetListener(FeatureAttribute.InlineRenameFlyout);
// 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;
};
InitializeComponent();
if (themeService is not null)
{
Outline.BorderBrush = new SolidColorBrush(themeService.GetThemeColor(EnvironmentColors.AccentBorderColorKey));
Background = new SolidColorBrush(themeService.GetThemeColor(EnvironmentColors.ToolWindowBackgroundColorKey));
}
// Dismiss any current tooltips. Note that this does not disable tooltips
// from showing up again, so if a user has the mouse unmoved another
// tooltip will pop up. https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1611398
// tracks when we can handle this with IFeaturesService in VS
var token = _listener.BeginAsyncOperation(nameof(DismissToolTipsAsync));
_ = DismissToolTipsAsync().CompletesAsyncOperation(token);
}
private async Task DismissToolTipsAsync()
{
var infoSession = _asyncQuickInfoBroker.GetSession(_textView);
if (infoSession is null)
{
return;
}
await infoSession.DismissAsync().ConfigureAwait(false);
}
#pragma warning disable CA1822 // Mark members as static - used in xaml
public string RenameOverloads => EditorFeaturesResources.Include_overload_s;
public string SearchInComments => EditorFeaturesResources.Include_comments;
public string SearchInStrings => EditorFeaturesResources.Include_strings;
public string ApplyRename => EditorFeaturesResources.Apply1;
public string CancelRename => EditorFeaturesResources.Cancel;
public string PreviewChanges => EditorFeaturesResources.Preview_changes1;
public string SubmitText => EditorFeaturesWpfResources.Enter_to_rename_shift_enter_to_preview;
#pragma warning restore CA1822 // Mark members as static
private void TextView_ViewPortChanged(object sender, EventArgs e)
=> PositionAdornment();
private void TextView_LayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
{
// Since the textview will update for the buffer being updated, we only want to reposition
// in cases where there was an actual view translation instead of EVERY time it updates. Otherwise
// the user will see the flyout jumping as they type
if (e.VerticalTranslation || e.HorizontalTranslation)
{
PositionAdornment();
}
}
private void PositionAdornment()
{
var span = _viewModel.InitialTrackingSpan.GetSpan(_textView.TextSnapshot);
var line = _textView.GetTextViewLineContainingBufferPosition(span.Start);
var charBounds = line.GetCharacterBounds(span.Start);
var height = DesiredSize.Height;
var width = DesiredSize.Width;
var desiredTop = charBounds.TextBottom + 5;
var desiredLeft = charBounds.Left;
var top = (desiredTop + height) > _textView.ViewportBottom
? _textView.ViewportBottom - height
: desiredTop;
var left = (desiredLeft + width) > _textView.ViewportRight
? _textView.ViewportRight - width
: desiredLeft;
Canvas.SetTop(this, top);
Canvas.SetLeft(this, left);
}
public override void Dispose()
{
_viewModel.Dispose();
_textView.LayoutChanged -= TextView_LayoutChanged;
_textView.ViewportHeightChanged -= TextView_ViewPortChanged;
_textView.ViewportWidthChanged -= TextView_ViewPortChanged;
// Restore focus back to the textview
_textView.VisualElement.Focus();
}
private void Submit_Click(object sender, RoutedEventArgs e)
{
_viewModel.Submit();
}
private void Adornment_KeyDown(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Enter:
e.Handled = true;
_viewModel.PreviewChangesFlag = Keyboard.Modifiers.HasFlag(ModifierKeys.Shift);
_viewModel.Submit();
break;
case Key.Escape:
e.Handled = true;
_viewModel.Cancel();
break;
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
? FileRenameCheckbox
: IdentifierTextBox;
if (lastItem.IsFocused)
{
e.Handled = true;
MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
}
break;
}
}
private void IdentifierTextBox_GotFocus(object sender, RoutedEventArgs e)
{
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)
{
return;
}
IdentifierTextBox.Focus();
e.Handled = true;
}
private void ToggleExpand(object sender, RoutedEventArgs e)
{
_viewModel.IsExpanded = !_viewModel.IsExpanded;
}
/// <summary>
/// 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)
{
var start = IdentifierTextBox.SelectionStart;
var length = IdentifierTextBox.SelectionLength;
var buffer = _viewModel.InitialTrackingSpan.TextBuffer;
var startPoint = _viewModel.InitialTrackingSpan.GetStartPoint(buffer.CurrentSnapshot);
_textView.SetSelection(new SnapshotSpan(startPoint + start, length));
}
}
}
|