Inquiry Regarding Customizing Tooltip Presentation for Macro Expansions

Adrian P 20 Reputation points
2023-10-04T13:44:14.9933333+00:00

I am currently working on a development project that involves extensive use of macros, and I have found that the tooltips generated by the "Visualize Macro Expansion" feature do not always provide the desired level of readability and formatting for complex macro code. I would like to explore the option of customizing or improving the way these tooltips are displayed within the Visual Studio IDE to enhance the developer experience.

Specifically, I am interested in:

  1. Adjusting the formatting and layout of tooltips for macro expansions.
  2. Enhancing the readability of macro code displayed in tooltips. I understand that Visual Studio offers a wide range of extensibility options, and I wanted to inquire if there are any official APIs, extensions, or recommended approaches that would allow me to achieve the above-mentioned goals.

Additionally, I would like to know if it is possible to extract the tooltip text from the QuickInfo using a Visual Studio extension (VSIX) as an alternative solution, should the direct customization of tooltips not be feasible. Your guidance and recommendations in this matter would be greatly appreciated.

.NET
.NET
Microsoft Technologies based on the .NET software framework.
3,442 questions
Visual Studio
Visual Studio
A family of Microsoft suites of integrated development tools for building applications for Windows, the web and mobile devices.
4,676 questions
C++
C++
A high-level, general-purpose programming language, created as an extension of the C programming language, that has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.
3,554 questions
Visual Studio Extensions
Visual Studio Extensions
Visual Studio: A family of Microsoft suites of integrated development tools for building applications for Windows, the web and mobile devices.Extensions: A program or program module that adds functionality to or extends the effectiveness of a program.
178 questions
{count} votes

Accepted answer
  1. gekka 6,926 Reputation points MVP
    2023-10-06T10:30:19.0266667+00:00

    You can hack it using VSIX !

    Implementing IAsyncQuickInfoSourceProvider or IQuickInfoSourceProvider called from MEF will posible detect when a ToolTip to be displayed.

    QuickInfo popup is built with WPF, so it is possible to search for strings displayed with WPF technology.

    using System;
    using System.Linq;
    using System.ComponentModel.Composition;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using Microsoft.VisualStudio.Language.Intellisense;
    using Microsoft.VisualStudio.Shell;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Threading;
    using Microsoft.VisualStudio.Utilities;
    
    namespace Gekka.VisualStudio.MacroToolTipInfoHack
    {
        [Export(typeof(IAsyncQuickInfoSourceProvider))]
        [Name("ToolTip QuickInfo HackProvider")]
        [Order(Before = "Default Quick Info Presenter")]
        //[ContentType("text")]
        [ContentType("C/C++")]
        internal class HackInfoProvider : IAsyncQuickInfoSourceProvider
        {
            public HackInfoProvider()
            {
            }
    
            [Import] internal SVsServiceProvider ServiceProvider = null;
            [Import] internal JoinableTaskContext JoinableTaskContext = null;
            [Import] internal IContentTypeRegistryService ContentTypeRegistryService { get; set; }
    
            public IAsyncQuickInfoSource TryCreateQuickInfoSource(ITextBuffer textBuffer)
            {
                System.Diagnostics.Debug.WriteLine("Supprted ContentType's");
                foreach (var ct in ContentTypeRegistryService.ContentTypes)
                {
                    System.Diagnostics.Debug.WriteLine($"\t{ct.TypeName}");
                }
    
                return new HackInfoSource(this.JoinableTaskContext);
            }
        }
    
        internal class HackInfoSource : IAsyncQuickInfoSource
        {
            private JoinableTaskContext _JoinableTaskContext;
    
            public HackInfoSource(JoinableTaskContext context)
            {
                this._JoinableTaskContext = context;
            }
    
            public async Task<QuickInfoItem> GetQuickInfoItemAsync(IAsyncQuickInfoSession session, CancellationToken cancellationToken)
            {
                await _JoinableTaskContext.Factory.SwitchToMainThreadAsync(cancellationToken);
    
                cancellationToken.ThrowIfCancellationRequested();
    
                var textBlock = new System.Windows.Controls.TextBlock()
                {
                    FontSize = 30.0,
                    Foreground = Brushes.Red,
                    Text = "I'm Hacking Now!!"
                };
    
                var grid = new System.Windows.Controls.Grid();
                grid.Children.Add(textBlock);
                grid.Loaded += grid_Loaded;
    
                return new QuickInfoItem(session.ApplicableToSpan, grid);
            }
    
            private static void grid_Loaded(object sender, RoutedEventArgs e)
            {
                Grid grid = (System.Windows.Controls.Grid)sender;
    
                DependencyObject d = grid;
                while (d != null)
                {
                    var parent = System.Windows.Media.VisualTreeHelper.GetParent(d);
    
                    if (parent is ItemsControl items)
                    {
                        foreach (object item in items.ItemsSource)
                        {
                            if (!object.ReferenceEquals(item, grid))
                            {
                                if (item is DependencyObject element)
                                {
                                    DumpChild(element);
                                }
                                else
                                {
                                    System.Diagnostics.Debug.WriteLine(item?.ToString());
                                }
                            }
                        }
                        break;
                    }
    
                    d = parent;
                }
            }
    
            private static void DumpChild(DependencyObject d, int lv = 0)
            {
                string typeName = d.GetType().Name;
    
                string text = null;
                switch (d)
                {
                case TextBox txb: text = $"Text={txb.Text}"; break;
                case TextBlock tb:
    
                    System.Text.StringBuilder sb = new System.Text.StringBuilder();
                    if (tb.Inlines.Count > 0)
                    {
                        AddInlines(sb, tb.Inlines);
                        text = $"Text={sb.ToString()}";
                    }
                    else
                    {
                        text = $"Text={tb.Text}";
                    }
    
                    break;
                case FrameworkElement fe:
                    text = $"DataContext={fe.DataContext?.GetType().Name}";
                    break;
                }
    
                System.Diagnostics.Debug.WriteLine($"{new string('\t', lv)}{typeName};\t{text}");
    
                int childCount = VisualTreeHelper.GetChildrenCount(d);
                for (int i = 0; i < childCount; i++)
                {
                    var child = VisualTreeHelper.GetChild(d, i);
                    DumpChild(child, lv + 1);
                }
            }
    
            static void AddInlines(System.Text.StringBuilder sb, System.Windows.Documents.InlineCollection lines)
            {
                foreach (var inline in lines)
                {
                    switch (inline)
                    {
                    case System.Windows.Documents.LineBreak lb: sb.Append("\\r\\n"); break;
                    case System.Windows.Documents.Run run: sb.Append(run.Text); break;
                    case System.Windows.Documents.Hyperlink link: AddInlines(sb, link.Inlines); break;
                    case System.Windows.Documents.Span span: AddInlines(sb, span.Inlines); break;
                    }
                }
            }
            #region IDisposable
            private bool disposedValue;
    
            protected virtual void Dispose(bool disposing)
            {
                if (!disposedValue)
                {
                    disposedValue = true;
                }
            }
    
            public void Dispose()
            {
                Dispose(disposing: true);
                GC.SuppressFinalize(this);
            }
            #endregion
        }
    
    }
    

    Tutorial for MEF implement.

    0 comments No comments

0 additional answers

Sort by: Most helpful