InlineHintsTag.cs 10.3 KB
Newer Older
A
Ankita Khera 已提交
1 2 3 4
// 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.

A
Ankita Khera 已提交
5
using System;
A
Ankita Khera 已提交
6
using System.Collections.Generic;
A
Ankita Khera 已提交
7
using System.Collections.Immutable;
8
using System.Linq;
A
Ankita Khera 已提交
9
using System.Threading;
A
Ankita Khera 已提交
10
using System.Threading.Tasks;
A
Ankita Khera 已提交
11
using System.Windows;
A
Ankita Khera 已提交
12
using System.Windows.Controls;
A
Ankita Khera 已提交
13
using System.Windows.Input;
A
Ankita Khera 已提交
14
using System.Windows.Media;
A
Ankita Khera 已提交
15 16 17 18 19
using Microsoft.CodeAnalysis.DocumentationComments;
using Microsoft.CodeAnalysis.Editor.Host;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
20
using Microsoft.CodeAnalysis.Text;
A
Ankita Khera 已提交
21
using Microsoft.VisualStudio.Text;
22
using Microsoft.VisualStudio.Text.Adornments;
A
Ankita Khera 已提交
23
using Microsoft.VisualStudio.Text.Editor;
24
using Microsoft.VisualStudio.Text.Formatting;
A
Ankita Khera 已提交
25

C
Cyrus Najmabadi 已提交
26
namespace Microsoft.CodeAnalysis.Editor.InlineHints
A
Ankita Khera 已提交
27
{
A
Ankita Khera 已提交
28
    /// <summary>
29 30
    /// This is the tag which implements the IntraTextAdornmentTag and is meant to create the UIElements that get shown
    /// in the editor
A
Ankita Khera 已提交
31
    /// </summary>
C
Renames  
Cyrus Najmabadi 已提交
32
    internal class InlineHintsTag : IntraTextAdornmentTag
A
Ankita Khera 已提交
33
    {
C
Renames  
Cyrus Najmabadi 已提交
34 35
        public const string TagId = "inline hints";

A
Ankita Khera 已提交
36 37
        private readonly IToolTipService _toolTipService;
        private readonly ITextView _textView;
38
        private readonly SnapshotSpan _span;
C
Renames  
Cyrus Najmabadi 已提交
39
        private readonly SymbolKey? _key;
40
        private readonly IThreadingContext _threadingContext;
41
        private readonly Lazy<IStreamingFindUsagesPresenter> _streamingPresenter;
42

C
Renames  
Cyrus Najmabadi 已提交
43
        private InlineHintsTag(
C
Cyrus Najmabadi 已提交
44 45 46
            FrameworkElement adornment,
            ITextView textView,
            SnapshotSpan span,
C
Renames  
Cyrus Najmabadi 已提交
47 48
            SymbolKey? key,
            InlineHintsTaggerProvider taggerProvider)
49
            : base(adornment, removalCallback: null, PositionAffinity.Predecessor)
A
Ankita Khera 已提交
50 51 52
        {
            _textView = textView;
            _span = span;
A
Ankita Khera 已提交
53
            _key = key;
54
            _streamingPresenter = taggerProvider.StreamingFindUsagesPresenter;
A
Ankita Khera 已提交
55 56 57 58
            _threadingContext = taggerProvider.ThreadingContext;
            _toolTipService = taggerProvider.ToolTipService;

            // Sets the tooltip to a string so that the tool tip opening event can be triggered
59 60
            // Tooltip value does not matter at this point because it immediately gets overwritten by the correct
            // information in the Border_ToolTipOpening event handler
A
Ankita Khera 已提交
61
            adornment.ToolTip = "Quick info";
A
Ankita Khera 已提交
62 63
            adornment.ToolTipOpening += Border_ToolTipOpening;
        }
A
Ankita Khera 已提交
64

A
Ankita Khera 已提交
65 66
        /// <summary>
        /// Creates the UIElement on call
67
        /// Uses PositionAffinity.Predecessor because we want the tag to be associated with the preceding character
A
Ankita Khera 已提交
68
        /// </summary>
69 70 71
        /// <param name="textView">The view of the editor</param>
        /// <param name="span">The span that has the location of the hint</param>
        /// <param name="key">The symbolkey associated with each parameter</param>
C
Renames  
Cyrus Najmabadi 已提交
72 73 74 75 76 77 78
        public static InlineHintsTag Create(
            string text,
            TextFormattingRunProperties format,
            IWpfTextView textView,
            SnapshotSpan span,
            SymbolKey? key,
            InlineHintsTaggerProvider taggerProvider)
79
        {
C
Renames  
Cyrus Najmabadi 已提交
80
            return new InlineHintsTag(CreateElement(text, textView, format), textView, span, key, taggerProvider);
81 82
        }

A
Ankita Khera 已提交
83
        public async Task<IReadOnlyCollection<object>> CreateDescriptionAsync(CancellationToken cancellationToken)
A
Ankita Khera 已提交
84
        {
C
Renames  
Cyrus Najmabadi 已提交
85
            if (_key != null)
A
Ankita Khera 已提交
86
            {
C
Renames  
Cyrus Najmabadi 已提交
87
                var document = _span.Snapshot.TextBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges();
A
Ankita Khera 已提交
88

C
Renames  
Cyrus Najmabadi 已提交
89
                if (document != null)
A
Ankita Khera 已提交
90
                {
C
Renames  
Cyrus Najmabadi 已提交
91 92 93 94
                    var compilation = await document.Project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
                    var symbol = _key.Value.Resolve(compilation, cancellationToken: cancellationToken).Symbol;

                    if (symbol != null)
A
Ankita Khera 已提交
95
                    {
C
Renames  
Cyrus Najmabadi 已提交
96
                        var textContentBuilder = new List<TaggedText>();
97

C
Renames  
Cyrus Najmabadi 已提交
98 99 100 101 102 103 104
                        var workspace = document.Project.Solution.Workspace;
                        var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
                        var symbolDisplayService = document.GetRequiredLanguageService<ISymbolDisplayService>();
                        var formatter = document.GetRequiredLanguageService<IDocumentationCommentFormattingService>();
                        var sections = await symbolDisplayService.ToDescriptionGroupsAsync(workspace, semanticModel, _span.Start, ImmutableArray.Create(symbol), cancellationToken).ConfigureAwait(false);
                        textContentBuilder.AddRange(sections[SymbolDescriptionGroups.MainDescription]);
                        if (formatter != null)
105
                        {
C
Renames  
Cyrus Najmabadi 已提交
106 107 108 109 110 111 112
                            var documentation = symbol.GetDocumentationParts(semanticModel, _span.Start, formatter, cancellationToken);

                            if (documentation.Any())
                            {
                                textContentBuilder.AddLineBreak();
                                textContentBuilder.AddRange(documentation);
                            }
113 114
                        }

C
Renames  
Cyrus Najmabadi 已提交
115
                        if (sections.TryGetValue(SymbolDescriptionGroups.AnonymousTypes, out var parts))
A
Ankita Khera 已提交
116
                        {
C
Renames  
Cyrus Najmabadi 已提交
117 118 119 120 121 122
                            if (!parts.IsDefaultOrEmpty)
                            {
                                textContentBuilder.AddLineBreak();
                                textContentBuilder.AddLineBreak();
                                textContentBuilder.AddRange(parts);
                            }
A
Ankita Khera 已提交
123
                        }
C
Renames  
Cyrus Najmabadi 已提交
124 125 126 127

                        var uiCollection = Implementation.IntelliSense.Helpers.BuildInteractiveTextElements(textContentBuilder.ToImmutableArray<TaggedText>(),
                            document, _threadingContext, _streamingPresenter);
                        return uiCollection;
A
Ankita Khera 已提交
128
                    }
A
Ankita Khera 已提交
129 130 131
                }
            }

D
David Wengier 已提交
132
            return Array.Empty<object>();
A
Ankita Khera 已提交
133 134
        }

135
        private static FrameworkElement CreateElement(string text, IWpfTextView textView, TextFormattingRunProperties format)
A
Ankita Khera 已提交
136
        {
137 138
            // Constructs the hint block which gets assigned parameter name and fontstyles according to the options
            // page. Calculates a font size 1/4 smaller than the font size of the rest of the editor
139
            var block = new TextBlock
A
Ankita Khera 已提交
140
            {
A
Ankita Khera 已提交
141
                FontFamily = format.Typeface.FontFamily,
A
Ankita Khera 已提交
142 143 144
                FontSize = format.FontRenderingEmSize - (0.25 * format.FontRenderingEmSize),
                FontStyle = FontStyles.Normal,
                Foreground = format.ForegroundBrush,
A
Ankita Khera 已提交
145 146 147 148

                // Adds a little bit of padding to the left of the text relative to the border
                // to make the text seem more balanced in the border
                Padding = new Thickness(left: 1, top: 0, right: 0, bottom: 0),
A
Ankita Khera 已提交
149 150
                Text = text + ":",
                VerticalAlignment = VerticalAlignment.Center,
A
Ankita Khera 已提交
151
            };
A
Ankita Khera 已提交
152

153 154
            // Encapsulates the textblock within a border. Sets the height of the border to be 3/4 of the original 
            // height. Gets foreground/background colors from the options menu. The margin is the distance from the 
A
Ankita Khera 已提交
155
            // adornment to the text and pushing the adornment upwards to create a separation when on a specific line
A
Ankita Khera 已提交
156 157
            var border = new Border
            {
A
Ankita Khera 已提交
158
                Background = format.BackgroundBrush,
159
                Child = block,
A
Ankita Khera 已提交
160
                CornerRadius = new CornerRadius(2),
A
Ankita Khera 已提交
161
                Height = textView.LineHeight - (0.25 * textView.LineHeight),
162
                HorizontalAlignment = HorizontalAlignment.Center,
163
                Margin = new Thickness(left: 0, top: -0.20 * textView.LineHeight, right: 5, bottom: 0),
A
Ankita Khera 已提交
164
                Padding = new Thickness(1),
165 166

                // Need to set SnapsToDevicePixels and UseLayoutRounding to avoid unnecessary reformatting
A
Ankita Khera 已提交
167 168 169
                SnapsToDevicePixels = textView.VisualElement.SnapsToDevicePixels,
                UseLayoutRounding = textView.VisualElement.UseLayoutRounding,
                VerticalAlignment = VerticalAlignment.Center
A
Ankita Khera 已提交
170
            };
171

172
            // Need to set these properties to avoid unnecessary reformatting because some dependancy properties
A
Ankita Khera 已提交
173 174 175 176 177
            // affect layout
            TextOptions.SetTextFormattingMode(border, TextOptions.GetTextFormattingMode(textView.VisualElement));
            TextOptions.SetTextHintingMode(border, TextOptions.GetTextHintingMode(textView.VisualElement));
            TextOptions.SetTextRenderingMode(border, TextOptions.GetTextRenderingMode(textView.VisualElement));

A
Ankita Khera 已提交
178
            border.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
A
Ankita Khera 已提交
179
            return border;
A
Ankita Khera 已提交
180
        }
A
Ankita Khera 已提交
181

182 183 184
        /// <summary>
        /// Determines if the border is being moused over and shows the info accordingly
        /// </summary>
A
Ankita Khera 已提交
185
        private void Border_ToolTipOpening(object sender, ToolTipEventArgs e)
A
Ankita Khera 已提交
186
        {
A
Ankita Khera 已提交
187
            var border = (Border)sender;
188
            e.Handled = true;
189

190 191 192
            bool KeepOpen()
            {
                var mousePoint = Mouse.GetPosition(border);
A
Ankita Khera 已提交
193
                return !(mousePoint.X > border.ActualWidth || mousePoint.X < 0 || mousePoint.Y > border.ActualHeight || mousePoint.Y < 0);
194
            }
195

196
            var toolTipPresenter = _toolTipService.CreatePresenter(_textView, new ToolTipParameters(trackMouse: true, ignoreBufferChange: false, KeepOpen));
A
Ankita Khera 已提交
197
            _ = StartToolTipServiceAsync(toolTipPresenter);
A
Ankita Khera 已提交
198 199 200 201 202
        }

        /// <summary>
        /// Waits for the description to be created and updates the tooltip with the associated information
        /// </summary>
203
        private async Task StartToolTipServiceAsync(IToolTipPresenter toolTipPresenter)
A
Ankita Khera 已提交
204
        {
A
Ankita Khera 已提交
205
            var uiList = await Task.Run(() => CreateDescriptionAsync(_threadingContext.DisposalToken)).ConfigureAwait(false);
206 207
            await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(_threadingContext.DisposalToken);

A
Ankita Khera 已提交
208
            toolTipPresenter.StartOrUpdate(_textView.TextSnapshot.CreateTrackingSpan(_span.Start, _span.Length, SpanTrackingMode.EdgeInclusive), uiList);
A
Ankita Khera 已提交
209
        }
A
Ankita Khera 已提交
210 211
    }
}