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
}
}