/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/VisualStudio/Core/Def/PreviewPane/PreviewPane.xaml.cs
436 строк
15 KB
Tomáš Matoušek
Add Microsoft.CodeAnalysis.Contracts source package (#76997)
11 фев 2025, 21:45
Не верифицирован
11 фев 2025, 21:45
eeff8ea
Код
Авторство
О чём код?
// 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. #nullable disable using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Navigation; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics.Log; using Microsoft.CodeAnalysis.Editor.Implementation.Preview; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Roslyn.Utilities; using IVsUIShell = Microsoft.VisualStudio.Shell.Interop.IVsUIShell; using OLECMDEXECOPT = Microsoft.VisualStudio.OLE.Interop.OLECMDEXECOPT; namespace Microsoft.VisualStudio.LanguageServices.Implementation.PreviewPane; internal partial class PreviewPane : UserControl, IDisposable, IOleCommandTarget { private const double DefaultWidth = 400; private static readonly string s_dummyThreeLineTitle = "A" + Environment.NewLine + "A" + Environment.NewLine + "A"; private static readonly Size s_infiniteSize = new(double.PositiveInfinity, double.PositiveInfinity); private readonly string _id; private readonly bool _logIdVerbatimInTelemetry; private readonly IVsUIShell _uiShell; private bool _isExpanded; private readonly double _heightForThreeLineTitle; private DifferenceViewerPreview _differenceViewerPreview; private readonly IVsRegisterPriorityCommandTarget _registerPriorityCommandTarget; private readonly uint _commandTargetCookie = VSConstants.VSCOOKIE_NIL; public PreviewPane( Image severityIcon, string id, string title, string description, Uri helpLink, string helpLinkToolTipText, IReadOnlyList<object> previewContent, bool logIdVerbatimInTelemetry, IVsUIShell uiShell, Guid optionPageGuid = default) { InitializeComponent(); Loaded += PreviewPane_Loaded; _id = id; _logIdVerbatimInTelemetry = logIdVerbatimInTelemetry; _uiShell = uiShell; // Initialize header portion. if ((severityIcon != null) && !string.IsNullOrWhiteSpace(id) && !string.IsNullOrWhiteSpace(title)) { HeaderStackPanel.Visibility = Visibility.Visible; SeverityIconBorder.Child = severityIcon; // Set the initial title text to three lines worth so that we can measure the pixel height // that WPF requires to render three lines of text under the current font and DPI settings. TitleRun.Text = s_dummyThreeLineTitle; TitleTextBlock.Measure(availableSize: s_infiniteSize); _heightForThreeLineTitle = TitleTextBlock.DesiredSize.Height; // Now set the actual title text. TitleRun.Text = title; if (helpLink != null) { Contract.ThrowIfNull(helpLinkToolTipText); InitializeHyperlinks(helpLink, helpLinkToolTipText); } if (!string.IsNullOrWhiteSpace(description)) { DescriptionParagraph.Inlines.Add(description); } } _optionPageGuid = optionPageGuid; if (optionPageGuid == default) { OptionsButton.Visibility = Visibility.Collapsed; } // Initialize preview (i.e. diff view) portion. InitializePreviewElement(previewContent); _registerPriorityCommandTarget = Shell.Package.GetGlobalService(typeof(SVsRegisterPriorityCommandTarget)) as IVsRegisterPriorityCommandTarget; Debug.Assert(_registerPriorityCommandTarget != null); if (_registerPriorityCommandTarget != null) { ErrorHandler.ThrowOnFailure( _registerPriorityCommandTarget.RegisterPriorityCommandTarget( dwReserved: 0, pCmdTrgt: this, out _commandTargetCookie)); } } public FrameworkElement ParentElement { get { return (FrameworkElement)GetValue(ParentElementProperty); } set { SetValue(ParentElementProperty, value); } } public static readonly DependencyProperty ParentElementProperty = DependencyProperty.Register("ParentElement", typeof(FrameworkElement), typeof(PreviewPane), new PropertyMetadata(null)); private void PreviewPane_Loaded(object sender, RoutedEventArgs e) { Loaded -= PreviewPane_Loaded; ParentElement = Parent as FrameworkElement; } public string AutomationName => ServicesVSResources.Preview_pane; private void InitializePreviewElement(IReadOnlyList<object> previewItems) { var previewElement = CreatePreviewElement(previewItems); if (previewElement != null) { HeaderSeparator.Visibility = Visibility.Visible; PreviewDockPanel.Visibility = Visibility.Visible; PreviewScrollViewer.Content = previewElement; previewElement.VerticalAlignment = VerticalAlignment.Top; previewElement.HorizontalAlignment = HorizontalAlignment.Left; } // 1. Width of the header section should not exceed the width of the preview content. // In other words, the text we put in the header at the top of the preview pane // should not cause the preview pane itself to grow wider than the width required to // display the preview content at the bottom of the pane. // 2. Adjust the height of the header section so that it displays only three lines worth // by default. AdjustWidthAndHeight(previewElement); } private FrameworkElement CreatePreviewElement(IReadOnlyList<object> previewItems) { if (previewItems == null || previewItems.Count == 0) { return null; } const int MaxItems = 3; if (previewItems.Count > MaxItems) { previewItems = previewItems.Take(MaxItems).Concat("...").ToList(); } var grid = new Grid(); foreach (var previewItem in previewItems) { var previewElement = GetPreviewElement(previewItem); // no preview element if (previewElement == null) { continue; } // the very first preview if (grid.RowDefinitions.Count == 0) { grid.RowDefinitions.Add(new RowDefinition()); } else { grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto }); } // set row position of the element Grid.SetRow(previewElement, grid.RowDefinitions.Count - 1); // add the element to the grid grid.Children.Add(previewElement); // set width of the grid same as the first element if (grid.Children.Count == 1) { grid.Width = previewElement.Width; } } if (grid.Children.Count == 0) { // no preview return null; } // if there is only 1 item, just take preview element as it is without grid if (grid.Children.Count == 1) { var preview = grid.Children[0]; // we need to take it out from visual tree grid.Children.Clear(); return (FrameworkElement)preview; } return grid; } private FrameworkElement GetPreviewElement(object previewItem) { if (previewItem is DifferenceViewerPreview) { // Contract is there should be only 1 diff viewer, otherwise we leak. Contract.ThrowIfFalse(_differenceViewerPreview == null); // cache the diff viewer so that we can close it when panel goes away. // this is a bit weird since we are mutating state here. _differenceViewerPreview = (DifferenceViewerPreview)previewItem; var viewer = _differenceViewerPreview.Viewer; PreviewDockPanel.Background = viewer.InlineView.Background; var previewElement = viewer.VisualElement; return previewElement; } if (previewItem is string s) { return GetPreviewForString(s); } if (previewItem is FrameworkElement frameworkElement) { return frameworkElement; } // preview item we don't know how to show to users return null; } private void InitializeHyperlinks(Uri helpLink, string helpLinkToolTipText) { IdHyperlink.Inlines.Add(_id); IdHyperlink.NavigateUri = helpLink; IdHyperlink.IsEnabled = true; IdHyperlink.ToolTip = helpLinkToolTipText; LearnMoreHyperlink.Inlines.Add(string.Format(ServicesVSResources.More_about_0, _id)); LearnMoreHyperlink.NavigateUri = helpLink; LearnMoreHyperlink.ToolTip = helpLinkToolTipText; } private static FrameworkElement GetPreviewForString(string previewContent) { return new TextBlock() { Margin = new Thickness(5), VerticalAlignment = VerticalAlignment.Center, Text = previewContent, TextWrapping = TextWrapping.Wrap, }; } // This method adjusts the width of the header section to match that of the preview content // thereby ensuring that the preview pane itself is never wider than the preview content. // This method also adjusts the height of the header section so that it displays only three lines // worth by default. private void AdjustWidthAndHeight(FrameworkElement previewElement) { double headerStackPanelWidth; var titleTextBlockHeight = double.PositiveInfinity; if (previewElement == null) { HeaderStackPanel.Measure(availableSize: s_infiniteSize); headerStackPanelWidth = HeaderStackPanel.DesiredSize.Width; TitleTextBlock.Measure(availableSize: s_infiniteSize); titleTextBlockHeight = TitleTextBlock.DesiredSize.Height; } else { PreviewDockPanel.Measure(availableSize: new Size( double.IsNaN(previewElement.Width) ? DefaultWidth : previewElement.Width, double.PositiveInfinity)); headerStackPanelWidth = PreviewDockPanel.DesiredSize.Width; if (IsNormal(headerStackPanelWidth)) { TitleTextBlock.Measure(availableSize: new Size(headerStackPanelWidth, double.PositiveInfinity)); titleTextBlockHeight = TitleTextBlock.DesiredSize.Height; } } if (IsNormal(headerStackPanelWidth)) { HeaderStackPanel.Width = headerStackPanelWidth; } // If the pixel height required to render the complete title in the // TextBlock is larger than that required to render three lines worth, // then trim the contents of the TextBlock with an ellipsis at the end and // display the expander button that will allow users to view the full text. if (HasDescription || (IsNormal(titleTextBlockHeight) && (titleTextBlockHeight > _heightForThreeLineTitle))) { TitleTextBlock.MaxHeight = _heightForThreeLineTitle; TitleTextBlock.TextTrimming = TextTrimming.CharacterEllipsis; ExpanderToggleButton.Visibility = Visibility.Visible; if (_isExpanded) { ExpanderToggleButton.IsChecked = true; } } } private static bool IsNormal(double size) => size > 0 && !double.IsNaN(size) && !double.IsInfinity(size); private bool HasDescription { get { return DescriptionParagraph.Inlines.Count > 0; } } private readonly Guid _optionPageGuid; void IDisposable.Dispose() { // VS editor will call Dispose at which point we should Close() the embedded IWpfDifferenceViewer. _differenceViewerPreview?.Dispose(); _differenceViewerPreview = null; if (_registerPriorityCommandTarget != null && _commandTargetCookie != VSConstants.VSCOOKIE_NIL) { _registerPriorityCommandTarget.UnregisterPriorityCommandTarget(_commandTargetCookie); } } int IOleCommandTarget.QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) { ThreadHelper.ThrowIfNotOnUIThread(); if (_differenceViewerPreview != null) { return _differenceViewerPreview.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); } return VSConstants.S_OK; } int IOleCommandTarget.Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { ThreadHelper.ThrowIfNotOnUIThread(); if (_differenceViewerPreview != null) { return _differenceViewerPreview.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); } return (int)VisualStudio.OLE.Interop.Constants.OLECMDERR_E_UNKNOWNGROUP; } private void LearnMoreHyperlink_RequestNavigate(object sender, RequestNavigateEventArgs e) { if (e.Uri == null) { return; } VisualStudioNavigateToLinkService.StartBrowser(e.Uri); e.Handled = true; if (sender is not Hyperlink hyperlink) { return; } DiagnosticLogger.LogHyperlink(hyperlink.Name ?? "Preview", _id, HasDescription, _logIdVerbatimInTelemetry, e.Uri.AbsoluteUri); } private void ExpanderToggleButton_CheckedChanged(object sender, RoutedEventArgs e) { if (ExpanderToggleButton.IsChecked ?? false) { TitleTextBlock.MaxHeight = double.PositiveInfinity; TitleTextBlock.TextTrimming = TextTrimming.None; if (HasDescription) { DescriptionDockPanel.Visibility = Visibility.Visible; if (LearnMoreHyperlink.NavigateUri != null) { LearnMoreTextBlock.Visibility = Visibility.Visible; LearnMoreHyperlink.IsEnabled = true; } } _isExpanded = true; } else { TitleTextBlock.MaxHeight = _heightForThreeLineTitle; TitleTextBlock.TextTrimming = TextTrimming.CharacterEllipsis; DescriptionDockPanel.Visibility = Visibility.Collapsed; _isExpanded = false; } } private void OptionsButton_Click(object sender, RoutedEventArgs e) { if (_optionPageGuid != default) { ErrorHandler.ThrowOnFailure(_uiShell.PostExecCommand( VSConstants.GUID_VSStandardCommandSet97, (uint)VSConstants.VSStd97CmdID.ToolsOptions, (uint)OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT, _optionPageGuid.ToString())); } } }