Deleted MemoryStreamDebugVisualizer

Deleted    NrxDebugVisualizer
This commit is contained in:
Matthias Heil
2026-03-31 13:16:22 +02:00
parent 9c0c548c50
commit 15fa583c56
29 changed files with 0 additions and 1380 deletions

View File

@@ -1,14 +0,0 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<RepoRootPath>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)'))</RepoRootPath>
<BaseIntermediateOutputPath>$(RepoRootPath)obj\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
<BaseOutputPath Condition=" '$(BaseOutputPath)' == '' ">$(RepoRootPath)bin\$(MSBuildProjectName)\</BaseOutputPath>
<SignAssembly>false</SignAssembly>
<IsPackable>false</IsPackable>
<DisableImplicitNamespaceImports>true</DisableImplicitNamespaceImports>
<EnforceCodeStyleInBuild>false</EnforceCodeStyleInBuild>
<EnableNETAnalyzers>false</EnableNETAnalyzers>
<AnalysisLevel>latest</AnalysisLevel>
</PropertyGroup>
</Project>

View File

@@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Remove="C:\Users\heilm\source\repos\DebugVisualizer\stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.DebuggerVisualizers" Version="17.6.1032901" />
</ItemGroup>
</Project>

View File

@@ -1,64 +0,0 @@
// <copyright file="MemoryStreamVisualizerObjectSource.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
namespace MemoryStreamObjectSource;
using Microsoft.VisualStudio.DebuggerVisualizers;
using System.IO;
/// <summary>
/// Object source class for the MemoryStreamVisualizer.
/// </summary>
public class MemoryStreamVisualizerObjectSource : VisualizerObjectSource
{
/// <summary>
/// How many rows will be transfered, at most, responding to a single request.
/// </summary>
public const int RowsCountPerRequest = 1024;
/// <summary>
/// How many bytes will be transfered for each row.
/// </summary>
public const int RowLength = 16;
/// <inheritdoc/>
public override void TransferData(object target, Stream incomingData, Stream outgoingData)
{
if (target is not MemoryStream memoryStream)
{
return;
}
using BinaryReader binaryReader = new(incomingData);
var index = binaryReader.ReadInt32(); // The extension will send the offset (Int32) to start reading from
using BinaryWriter binaryWriter = new(outgoingData);
var backupPosition = memoryStream.Position;
// Will reply with the current MemoryStream.Position (Int64),
// followed by MemoryStream.Length (Int64),
// followed by up to 16KB of data retrieved from the MemoryStream
binaryWriter.Write(backupPosition);
binaryWriter.Write(memoryStream.Length);
if (index < memoryStream.Length)
{
try
{
var data = new byte[RowsCountPerRequest * RowLength];
memoryStream.Seek(index, SeekOrigin.Begin);
var count = memoryStream.Read(data, 0, data.Length);
binaryWriter.Write(data, 0, count);
}
finally
{
// Make sure to restore the MemoryStream to its original position
memoryStream.Seek(backupPosition, SeekOrigin.Begin);
}
}
binaryWriter.Flush();
}
}

View File

@@ -1,5 +0,0 @@
<Solution>
<Project Path="MemoryStreamObjectSource/MemoryStreamObjectSource.csproj" />
<Project Path="MemoryStreamVisualizer/MemoryStreamVisualizer.csproj" />
<Project Path="TestApp/TestApp.csproj" />
</Solution>

View File

@@ -1,33 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace MemoryStreamVisualizer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.Extensibility;
/// <summary>
/// Extension entrypoint for the VisualStudio.Extensibility extension.
/// </summary>
[VisualStudioContribution]
internal class ExtensionEntrypoint : Extension
{
/// <inheritdoc/>
public override ExtensionConfiguration ExtensionConfiguration => new()
{
Metadata = new(
id: "MemoryStreamVisualizer.97a0a2fb-f163-4fa3-91f0-48a2d4ad9f57",
version: this.ExtensionAssemblyVersion,
publisherName: "Microsoft",
displayName: "MemoryStream Debugger Visualizer",
description: "A debugger visualizer for MemoryStream"),
};
/// <inheritdoc />
protected override void InitializeServices(IServiceCollection serviceCollection)
{
base.InitializeServices(serviceCollection);
// You can configure dependency injection here by adding services to the serviceCollection.
}
}

View File

@@ -1,50 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace MemoryStreamVisualizer;
using System.Runtime.Serialization;
/// <summary>
/// ViewModel class representing a row of binary data.
/// </summary>
[DataContract]
public class HexEditorRow
{
/// <summary>
/// Initializes a new instance of the <see cref="HexEditorRow"/> class.
/// </summary>
/// <param name="index">The index of this row.</param>
/// <param name="data">The bytes making up this row of data, in their hex representation.</param>
/// <param name="ascii">The bytes making up this row of data, in their Ascii representation.</param>
public HexEditorRow(int index, string data, string ascii)
{
this.Index = index;
this.Data = data;
this.Ascii = ascii;
}
/// <summary>
/// Gets the index of this row.
/// </summary>
[DataMember]
public int Index { get; }
/// <summary>
/// Gets the index of this row in hex format.
/// </summary>
[DataMember]
public string HexIndex => $"{this.Index:X8}h";
/// <summary>
/// Gets the bytes making up this row of data, in their hex representation.
/// </summary>
[DataMember]
public string Data { get; }
/// <summary>
/// Gets the bytes making up this row of data, in their Ascii representation.
/// </summary>
[DataMember]
public string Ascii { get; }
}

View File

@@ -1,80 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace MemoryStreamVisualizer;
using System.IO;
using System.Runtime.Serialization;
using System.Windows;
using Microsoft.VisualStudio.Extensibility.UI;
/// <summary>
/// ViewModel class representing the data contained by a <see cref="MemoryStream"/>.
/// </summary>
[DataContract]
public class MemoryStreamData : NotifyPropertyChangedObject
{
private long length;
private long position;
private Visibility loadingVisibility = Visibility.Visible;
/// <summary>
/// Gets or sets the length of the <see cref="MemoryStream"/>.
/// </summary>
[DataMember]
public long Length
{
get => this.length;
set
{
if (this.SetProperty(ref this.length, value))
{
this.RaiseNotifyPropertyChangedEvent(nameof(this.HexLength));
}
}
}
/// <summary>
/// Gets the length of the <see cref="MemoryStream"/> in hex format.
/// </summary>
[DataMember]
public string HexLength => $"{this.length:X}h";
/// <summary>
/// Gets or sets the current position of the <see cref="MemoryStream"/>.
/// </summary>
[DataMember]
public long Position
{
get => this.position;
set
{
if (this.SetProperty(ref this.position, value))
{
this.RaiseNotifyPropertyChangedEvent(nameof(this.HexPosition));
}
}
}
/// <summary>
/// Gets the current position of the <see cref="MemoryStream"/> in hex format.
/// </summary>
[DataMember]
public string HexPosition => $"{this.position:X}h";
/// <summary>
/// Gets the data currently contained in the <see cref="MemoryStream"/>.
/// </summary>
[DataMember]
public ObservableList<HexEditorRow> Data { get; } = new();
/// <summary>
/// Gets or sets whether the loading bar should be visible.
/// </summary>
[DataMember]
public Visibility LoadingVisibility
{
get => this.loadingVisibility;
set => this.SetProperty(ref this.loadingVisibility, value);
}
}

View File

@@ -1,36 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace MemoryStreamVisualizer;
using MemoryStreamObjectSource;
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.DebuggerVisualizers;
using Microsoft.VisualStudio.RpcContracts.RemoteUI;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// Debugger visualizer provider for <see cref="MemoryStream"/>.
/// </summary>
[VisualStudioContribution]
internal class MemoryStreamDebuggerVisualizerProvider : DebuggerVisualizerProvider
{
/// <inheritdoc/>
public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new(
[
new VisualizerTargetType("%MemoryStreamVisualizer.MemoryStreamDebuggerVisualizerProvider.Name%", typeof(MemoryStream)),
])
{
VisualizerObjectSourceType = new VisualizerObjectSourceType(typeof(MemoryStreamVisualizerObjectSource)),
Style = VisualizerStyle.ToolWindow,
};
/// <inheritdoc/>
public override Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
{
return Task.FromResult<IRemoteUserControl>(new MemoryStreamVisualizerUserControl(visualizerTarget));
}
}

View File

@@ -1,29 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows8.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
<NeutralLanguage>en-US</NeutralLanguage>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Remove="C:\Users\heilm\source\repos\DebugVisualizer\stylecop.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Sdk" Version="17.14.40608" PrivateAssets="all" />
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Build" Version="17.14.40608" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<None Remove="MemoryStreamVisualizerUserControl.xaml" />
<EmbeddedResource Include="MemoryStreamVisualizerUserControl.xaml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MemoryStreamObjectSource\MemoryStreamObjectSource.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="..\bin\MemoryStreamObjectSource\$(Configuration)\netstandard2.0\MemoryStreamObjectSource.dll" Link="netstandard2.0\MemoryStreamObjectSource.dll" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
</Project>

View File

@@ -1,150 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using MemoryStreamObjectSource;
using Microsoft.VisualStudio.Extensibility.DebuggerVisualizers;
using Microsoft.VisualStudio.Extensibility.UI;
using Microsoft.VisualStudio.RpcContracts.DebuggerVisualizers;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace MemoryStreamVisualizer;
/// <summary>
/// Remote UI user control for the MemoryStreamVisualizer.
/// </summary>
internal class MemoryStreamVisualizerUserControl : RemoteUserControl
{
private readonly VisualizerTarget visualizerTarget;
private readonly MemoryStreamData dataContext;
/// <summary>
/// Initializes a new instance of the <see cref="MemoryStreamVisualizerUserControl"/> class.
/// </summary>
/// <param name="visualizerTarget">The visualizer target to be used to retrieve data from.</param>
public MemoryStreamVisualizerUserControl(VisualizerTarget visualizerTarget)
: base(new MemoryStreamData())
{
visualizerTarget.Changed += this.VisualizerTargetStateChangedAsync;
this.dataContext = (MemoryStreamData)this.DataContext!;
this.visualizerTarget = visualizerTarget;
}
private Task VisualizerTargetStateChangedAsync(VisualizerTargetStateNotification args)
{
if (args == VisualizerTargetStateNotification.Available || args == VisualizerTargetStateNotification.ValueUpdated)
{
this.dataContext.Data.Clear();
this.dataContext.Position = 0;
this.dataContext.Length = 0;
this.dataContext.LoadingVisibility = Visibility.Visible;
return this.RetrieveDataAsync();
}
return Task.CompletedTask;
}
private async Task RetrieveDataAsync()
{
ReadOnlySequence<byte> data;
do
{
using MemoryStream memoryStream = new(sizeof(int));
using BinaryWriter binaryWriter = new(memoryStream);
int index = this.dataContext.Data.Count * MemoryStreamVisualizerObjectSource.RowLength;
if (index >= 1024 * 1024)
{
break; // Let's not retrieve more than 1MB of data
}
binaryWriter.Write(index);
binaryWriter.Flush();
try
{
data = (await this.visualizerTarget.ObjectSource.RequestDataAsync(new ReadOnlySequence<byte>(memoryStream.ToArray()), CancellationToken.None)).Value;
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception)
#pragma warning restore CA1031 // Do not catch general exception types
{
// I can get an exception if the debug session is unpaused, so I need to handle it gracefully
break;
}
}
while (data.Length > 0 && this.Read(data));
this.dataContext.LoadingVisibility = Visibility.Hidden;
}
private bool Read(ReadOnlySequence<byte> data)
{
int byteInRowCount = 0;
StringBuilder binaryText = new();
StringBuilder asciiText = new();
SequenceReader<byte> reader = new(data);
if (!reader.TryReadLittleEndian(out long position) || !reader.TryReadLittleEndian(out long length))
{
return false;
}
this.dataContext.Position = position;
this.dataContext.Length = length;
if (reader.UnreadSpan.Length == 0)
{
return false; // We always receive data unless we are at the end of the MemoryStream
}
List<HexEditorRow> rows = new(MemoryStreamVisualizerObjectSource.RowsCountPerRequest);
byte[] tmp = new byte[1];
while (reader.TryRead(out byte b))
{
byteInRowCount++;
if (byteInRowCount > 1)
{
binaryText.Append(' ');
}
binaryText.Append(b.ToString("X2", CultureInfo.InvariantCulture));
tmp[0] = b;
asciiText.Append(char.IsControl((char)b) || b == 0xAD ? '•' : Encoding.Latin1.GetChars(tmp)[0]);
if (byteInRowCount == MemoryStreamVisualizerObjectSource.RowLength)
{
CompleteRow();
}
}
if (byteInRowCount > 0)
{
CompleteRow();
this.dataContext.Data.AddRange(rows);
return false; // We only receive partial rows at the end of the MemoryStream
}
this.dataContext.Data.AddRange(rows);
return true;
void CompleteRow()
{
rows.Add(new HexEditorRow(
index: (this.dataContext.Data.Count + rows.Count) * MemoryStreamVisualizerObjectSource.RowLength,
data: binaryText.ToString(),
ascii: asciiText.ToString()));
byteInRowCount = 0;
binaryText.Clear();
asciiText.Clear();
}
}
}

View File

@@ -1,115 +0,0 @@
<DataTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vs="http://schemas.microsoft.com/visualstudio/extensibility/2022/xaml"
xmlns:styles="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0"
xmlns:colors="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ProgressBar IsIndeterminate="True" VerticalAlignment="Top" Visibility="{Binding LoadingVisibility}" Style="{StaticResource {x:Static styles:VsResourceKeys.ProgressBarStyleKey}}" />
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Label VerticalAlignment="Center" Style="{StaticResource {x:Static styles:VsResourceKeys.ThemedDialogLabelStyleKey}}">Position: </Label>
<TextBox IsReadOnly="True" Width="75" VerticalAlignment="Center">
<TextBox.Style>
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Static styles:VsResourceKeys.TextBoxStyleKey}}">
<Setter Property="Text" Value="{Binding Position, Mode=OneWay}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=HexCheck, FallbackValue=True}" Value="True">
<Setter Property="Text" Value="{Binding HexPosition, Mode=OneWay}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<Label Margin="2,0,0,0" VerticalAlignment="Center" Style="{StaticResource {x:Static styles:VsResourceKeys.ThemedDialogLabelStyleKey}}">Length:</Label>
<TextBox IsReadOnly="True" Width="100" VerticalAlignment="Center">
<TextBox.Style>
<Style TargetType="TextBox" BasedOn="{StaticResource {x:Static styles:VsResourceKeys.TextBoxStyleKey}}">
<Setter Property="Text" Value="{Binding Length, Mode=OneWay}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=HexCheck, FallbackValue=True}" Value="True">
<Setter Property="Text" Value="{Binding HexLength, Mode=OneWay}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
<CheckBox x:Name="HexCheck" Margin="5,0,0,0" VerticalAlignment="Center" Style="{StaticResource {x:Static styles:VsResourceKeys.CheckBoxStyleKey}}">Hex</CheckBox>
</StackPanel>
<DataGrid ItemsSource="{Binding Data}"
EnableRowVirtualization="True"
AutoGenerateColumns="False"
SelectionMode="Extended"
SelectionUnit="Cell"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
IsReadOnly="True"
Grid.Row="2">
<DataGrid.Resources>
<Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type DataGrid}, ResourceId=DataGridSelectAllButtonStyle}" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Rectangle Fill="{DynamicResource {x:Static colors:ThemedDialogColors.GridHeadingBackgroundBrushKey}}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
<DataGrid.Style>
<Style TargetType="{x:Type DataGrid}">
<Setter Property="Background" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridHeadingBackgroundBrushKey}}"/>
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridLineBrushKey}}"/>
<Setter Property="BorderThickness" Value="1"/>
</Style>
</DataGrid.Style>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridHeadingBackgroundBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static colors:ThemedDialogColors.WindowPanelTextBrushKey}}"/>
</Style>
</DataGrid.RowStyle>
<DataGrid.RowHeaderStyle>
<Style TargetType="{x:Type DataGridRowHeader}">
<Setter Property="Background" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridHeadingBackgroundBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridHeadingTextBrushKey}}"/>
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridLineBrushKey}}"/>
<Setter Property="Content" Value="{Binding Index, Mode=OneWay}" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=HexCheck, FallbackValue=True}" Value="True">
<Setter Property="Content" Value="{Binding HexIndex, Mode=OneWay}" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowHeaderStyle>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridHeadingBackgroundBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridHeadingTextBrushKey}}"/>
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static colors:ThemedDialogColors.GridLineBrushKey}}"/>
<Setter Property="Padding" Value="2,0,0,0"/>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.CellStyle>
<Style TargetType="{x:Type DataGridCell}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="{DynamicResource {x:Static colors:ThemedDialogColors.ActionButtonStrokeHoverBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static colors:ThemedDialogColors.ActionButtonTextActiveBrushKey}}"/>
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static colors:ThemedDialogColors.ActionButtonStrokeHoverBrushKey}}"/>
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Hex value" Binding="{Binding Data}" FontFamily="Consolas" />
<DataGridTextColumn Header="Ascii" Binding="{Binding Ascii}" FontFamily="Consolas" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</DataTemplate>

View File

@@ -1,23 +0,0 @@
// <copyright file="Program.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>
using System.Text;
// Initialize a MemoryStream to store user input temporarily
using MemoryStream userInputStream = new();
// Convert user input string to byte array
string userInput = "Sample user input data";
byte[] inputData = Encoding.UTF8.GetBytes(userInput);
// Write user input data to the MemoryStream
userInputStream.Write(inputData, 0, inputData.Length);
var buffer = new byte[inputData.Length];
userInputStream.Position = 0;
userInputStream.Read(buffer);
var text = Encoding.UTF8.GetString(buffer, 0, buffer.Length);
// Perform further processing with the stored user input
Thread.Sleep(1000);

View File

@@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<AdditionalFiles Remove="C:\Users\heilm\source\repos\DebugVisualizer\stylecop.json" />
</ItemGroup>
</Project>

View File

@@ -1,51 +0,0 @@
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<RepoRootPath>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\..\'))</RepoRootPath>
<BaseIntermediateOutputPath>$(RepoRootPath)obj\samples\$(MSBuildProjectName)\</BaseIntermediateOutputPath>
<BaseOutputPath Condition=" '$(BaseOutputPath)' == '' ">$(RepoRootPath)bin\samples\$(MSBuildProjectName)\</BaseOutputPath>
<SignAssembly>false</SignAssembly>
<IsPackable>false</IsPackable>
<DisableImplicitNamespaceImports>true</DisableImplicitNamespaceImports>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)shipping.ruleset</CodeAnalysisRuleSet>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>latest</AnalysisLevel>
</PropertyGroup>
<PropertyGroup>
<!--
Make sure any documentation comments which are included in code get checked for syntax during the build, but do
not report warnings for missing comments.
SA1600: Elements must be documented
SA1602: Enumeration items must be documented
CS1591: Missing XML comment
-->
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);SA1600;SA1602;CS1591</NoWarn>
<!--
StyleCop.Analyzers warnings that are not updated to C# 12 yet.
SA1010: Opening square brackets should not be preceded by a space
-->
<NoWarn>$(NoWarn);SA1010</NoWarn>
<!--
Microsoft.CodeAnalysis.NetAnalyzers warnings.
CA1812: Avoid uninstantiated internal classes
CA1303: Do not pass literals as localized parameters
-->
<NoWarn>$(NoWarn);CA1812;CA1303</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)stylecop.json" Link="stylecop.json" />
</ItemGroup>
</Project>

View File

@@ -1,32 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.4.11620.152
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{070F0AEA-C0A0-4B5D-9286-55574A37BE7A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Vector3DebugVisualizer", "Vector3DebugVisualizer", "{8554ABE3-9105-4AF7-9318-81414CC190C6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vector3DebugVisualizer", "Vector3DebugVisualizer\Vector3DebugVisualizer\Vector3DebugVisualizer.csproj", "{D8310D46-26AC-C7D7-1668-BFEBB7072467}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D8310D46-26AC-C7D7-1668-BFEBB7072467}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D8310D46-26AC-C7D7-1668-BFEBB7072467}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D8310D46-26AC-C7D7-1668-BFEBB7072467}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D8310D46-26AC-C7D7-1668-BFEBB7072467}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D8310D46-26AC-C7D7-1668-BFEBB7072467} = {8554ABE3-9105-4AF7-9318-81414CC190C6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {40A38C8A-61B7-427B-A430-DEB75BE34F22}
EndGlobalSection
EndGlobal

View File

@@ -1,196 +0,0 @@
# RegEx Match Debug Visualizer
This VisualStudio.Extensibility extension adds two new debugger visualizers supporting the .NET [`Match`](https://learn.microsoft.com/dotnet/api/system.text.regularexpressions.match) and [`MatchCollection`](https://learn.microsoft.com/dotnet/api/system.text.regularexpressions.matchcollection) classes.
![RegEx Match visualizer](RegexMatchVisualizer.png "RegEx Match visualizer")
The extension is composed of two projects: `RegexMatchDebugVisualizer`, the actual extension, and `RegexMatchObjectSource`, the visualizer object source library.
## Creating the extension
The extension project is created as described in the [tutorial document](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/get-started/create-your-first-extension). You can also reference the [debugger visualizers guide](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/debugger-visualizer/debugger-visualizers) for additional information.
## The `Match` visualizer
The next step is to create a `DebuggerVisualizerProvider` class to visualize instances of [`Match`](https://learn.microsoft.com/dotnet/api/system.text.regularexpressions.match):
```csharp
[VisualStudioContribution]
internal class RegexMatchDebuggerVisualizerProvider : DebuggerVisualizerProvider
{
...
```
If the `Match` type were serializable by Newtonsoft.Json, the visualizer implementation would be extremely simple:
```csharp
public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => ew("Regex Match visualizer", typeof(Match));
public override async Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget isualizerTarget, CancellationToken cancellationToken)
{
var regexMatch = await visualizerTarget.ObjectSource.RequestDataAsync<Match>(jsonSerializer: null, cancellationToken);
return new RegexMatchVisualizerUserControl(regexMatch);
}
```
Unfortunately `Match` is not serializable as-is, so we need a new serializable class [`RegexMatch`](RegexMatchObjectSource/RegexMatch.cs). And we will need to create a visualizer object source library to convert the `Match` into a serializable `RegexMatch`, more about this in [a later paragraph](#the-visualizer-object-source). For now, let's just update the `RequestDataAsync` call to use `RegexMatch`:
```csharp
var regexMatch = await visualizerTarget.ObjectSource.RequestDataAsync<RegexMatch>(jsonSerializer: null, cancellationToken);
```
### Adding the remote user control
We now have to create the `RegexMatchVisualizerUserControl` [class](./RegexMatchDebugVisualizer/RegexMatch/RegexMatchVisualizerUserControl.cs) and its associated [XAML file](./RegexMatchDebugVisualizer/RegexMatch/RegexMatchVisualizerUserControl.xaml). This process is described in the [Remote UI documentation](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/inside-the-sdk/remote-ui).
Every time we create a remote user control like `RegexMatchVisualizerUserControl` we need to configure the corresponding XAML file as embedded resource. In this case, since the XAML file is in a subfolder, we also need to use `LogicalName` to make sure the name of the resource matches the full name of the remote user control class. This is all done in the `.csproj` file:
```xml
<ItemGroup>
<Page Remove="RegexMatchVisualizerUserControl.xaml" />
<EmbeddedResource Include="RegexMatch\RegexMatchVisualizerUserControl.xaml" LogicalName="$(RootNamespace).RegexMatchVisualizerUserControl.xaml" />
</ItemGroup>
```
## The visualizer object source
The visualizer object source assembly will be loaded by the debugger into the process being debugged and will take care of converting the `Match` object into a serializable `RegexMatch`.
I will start creating a `RegexMatchObjectSource` class library targeting `netstandard2.0` and adding a project reference to [Microsoft.VisualStudio.DebuggerVisualizers](https://www.nuget.org/packages/Microsoft.VisualStudio.DebuggerVisualizers) version 17.6 or newer. Targeting `netstandard2.0` will allow the debugger visualizer to easily work with a large variety of .NET versions.
I will then create a `RegexMatchObjectSource` class extending `VisualizerObjectSource` and will override the `GetData` method to convert the `target` from a `Match` to a `RegexMatch` and use the `VisualizerObjectSource.SerializeAsJson` method to write the value to the `outgoingData` stream.
```csharp
public class RegexMatchObjectSource : VisualizerObjectSource
{
public override void GetData(object target, Stream outgoingData)
{
if (target is Match match)
{
RegexMatch result = Convert(match);
SerializeAsJson(outgoingData, result);
}
}
...
```
The `GetData` method is invoked by the debugger when the `RegexMatchDebuggerVisualizerProvider` calls `RequestDataAsync`.
`SerializeAsJson` will serialize the `RegexMatch` object using Newtonsoft.Json, which is loaded by the debugger in the process being debugged via reflection. Since my visualizer object source doesn't need to refence Newtonsoft.Json directly, I didn't include a `PackageReference` to it, which is better since we should minimize the dependencies of the visualizer object source assembly. Because this code doesn't reference Newtonsoft.Json, the `RegexMatch` class uses `DataContract` and `DataMember` attributes to control serialization instead of the Newtonsoft.Json-specific types.
My [`RegexMatchObjectSource`](./RegexMatchObjectSource/RegexMatchObjectSource.cs) implementation contains a small trick: the [`Group.Name`](https://learn.microsoft.com/dotnet/api/system.text.regularexpressions.group.name) property is read through reflection since it's available on most .NET versions but it is not included in the `netstandard2.0` APIs:
```csharp
private static readonly Func<Group, string?>? GetGroupName =
(Func<Group, string?>?)typeof(Group).GetProperty("Name")?.GetGetMethod().CreateDelegate(typeof(Func<Group, string?>));
...
Name = $"[{GetGroupName?.Invoke(g) ?? i.ToString()}]"
```
### Referencing the visualizer object source from the extension
First, we need to make sure that the visualizer object source library is packaged as part of the extension. We can do that in the extension's `.csproj` file:
```xml
<ItemGroup>
<Content Include="..\..\..\..\bin\samples\RegexMatchObjectSource\$(Configuration)\netstandard2.0\RegexMatchObjectSource.dll" Link="netstandard2.0\RegexMatchObjectSource.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RegexMatchObjectSource\RegexMatchObjectSource.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>
```
The `ProjectReference` guarantees that the visualizer object source library is built before the extension and the `Content` item makes sure that the visualizer object source DLL is copied into the `netstandard2.0` extension's subfolder where it will be discoverable by the debugger.
I have decided to use `ReferenceOutputAssembly="false"` to avoid a dependency of the extension assembly from the visualizer object source one. This allows using conditional compilation (`#if`) to have slightly different definitions of [`RegexCapture`](./RegexMatchObjectSource/RegexCapture.cs) in the two projects. Since I decided to avoid the dependency, I will need to:
1. link the `RegexMatch.cs` file (and the related `RegexCapture` and `RegexGroup` ones) so that they are available in both projects:
```xml
<ItemGroup>
<Compile Include="..\RegexMatchObjectSource\RegexGroup.cs" Link="SharedFiles\RegexGroup.cs" />
<Compile Include="..\RegexMatchObjectSource\RegexCapture.cs" Link="SharedFiles\RegexCapture.cs" />
<Compile Include="..\RegexMatchObjectSource\RegexMatch.cs" Link="SharedFiles\RegexMatch.cs" />
</ItemGroup>
```
2. Reference the `RegexMatchObjectSource` from the `DebuggerVisualizerProviderConfiguration` using its assembly-qualified name:
```csharp
public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new("Regex Match visualizer", typeof(Match))
{
VisualizerObjectSourceType = new("Microsoft.VisualStudio.Gladstone.RegexMatchVisualizer.ObjectSource.RegexMatchObjectSource, RegexMatchObjectSource"),
};
```
In most cases, having the extension project depend on the visualizer object source library is simpler: I could have simplified the `DebuggerVisualizerProviderConfiguration` to:
```csharp
public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new("Regex Match visualizer", typeof(Match))
{
VisualizerObjectSourceType = new(typeof(RegexMatchObjectSource)),
};
```
## The `MatchCollection` visualizer
Now that the `Match` visualizer is complete, we can add a second visualizer for the [`MatchCollection`](https://learn.microsoft.com/dotnet/api/system.text.regularexpressions.matchcollection) class. The process is exactly the same: create a new [`DebuggerVisualizerProvider`](./RegexMatchDebugVisualizer/RegexMatchCollection/RegexMatchCollectionDebuggerVisualizerProvider.cs) and its [remote user control](./RegexMatchDebugVisualizer/RegexMatchCollection/RegexMatchCollectionVisualizerUserControl.cs). Also, add a new [`VisualizerObjectSource`](./RegexMatchObjectSource/RegexMatchCollectionObjectSource.cs) to the visualizer object source library.
Each call to `RequestDataAsync` is allowed only 5 seconds to complete before throwing a timeout exception. Since the `MatchCollection` could contain many entries, the visualizer object source uses the `TransferData` method instead of `GetData`: `TransferData` accepts a parameter which allows the visualizer to query the collection entries one by one:
```csharp
public override void TransferData(object target, Stream incomingData, Stream outgoingData)
{
var index = (int)DeserializeFromJson(incomingData, typeof(int))!;
if (target is MatchCollection matchCollection && index < matchCollection.Count)
{
var result = RegexMatchObjectSource.Convert(matchCollection[index]);
result.Name = $"[{index}]";
SerializeAsJson(outgoingData, result);
}
else
{
SerializeAsJson(outgoingData, null);
}
}
```
Instead of using the `VisualizerTarget` directly, the `DebuggerVisualizerProvider` passes it to the remote user control so that it can asynchronously request the collection entries without delaying the display of the visualizer UI to the user.
```csharp
public override Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
{
return Task.FromResult<IRemoteUserControl>(new RegexMatchCollectionVisualizerUserControl(visualizerTarget));
}
```
The remote user control uses the `RequestDataAsync` override that takes a `message` parameter, which results in `TransferData` being invoked on the visualizer object source. The remote user control will loop, invoking `RequestDataAsync` for increasing index numbers until the visualizer object source returns `null`, which indicates the end of the collection:
```csharp
public override Task ControlLoadedAsync(CancellationToken cancellationToken)
{
_ = Task.Run(async () =>
{
for (int i = 0; ; i++)
{
RegexMatch? regexMatch = await this.visualizerTarget.ObjectSource.RequestDataAsync<int, RegexMatch?>(message: i, jsonSerializer: null, CancellationToken.None);
if (regexMatch is null)
{
break;
}
this.RegexMatches.Add(regexMatch);
}
});
return Task.CompletedTask;
}
```
This is a very simple implementation of a debugger visualizer which relies on `RequestDataAsync`. More complex implementations may pass more complex parameters to `RequestDataAsync` in order to retrieve different information from the visualizer object source. You could even invoke `RequestDataAsync` in response to the user's interactions with the remote user control, allowing the user to "explore" the content of, potentially very large, objects.

View File

@@ -1,49 +0,0 @@
using System.Text.RegularExpressions;
class Example
{
static void Main()
{
string text = "One car red car blue car";
string pat = @"(\w+)\s+(car)";
// Instantiate the regular expression object.
Regex r = new Regex(pat, RegexOptions.IgnoreCase);
// Match the regular expression pattern against a text string.
Match m = r.Match(text);
int matchCount = 0;
while (m.Success)
{
Console.WriteLine("Match" + (++matchCount));
for (int i = 1; i <= 2; i++)
{
Group g = m.Groups[i];
Console.WriteLine("Group" + i + "='" + g + "'");
CaptureCollection cc = g.Captures;
for (int j = 0; j < cc.Count; j++)
{
Capture c = cc[j];
System.Console.WriteLine("Capture" + j + "='" + c + "', Position=" + c.Index);
}
}
m = m.NextMatch();
}
}
}
// This example displays the following output:
// Match1
// Group1='One'
// Capture0='One', Position=0
// Group2='car'
// Capture0='car', Position=4
// Match2
// Group1='red'
// Capture0='red', Position=8
// Group2='car'
// Capture0='car', Position=12
// Match3
// Group1='blue'
// Capture0='blue', Position=16
// Group2='car'
// Capture0='car', Position=21

View File

@@ -1,10 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -1,25 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.4.11620.152
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestDebugVisualizer", "TestDebugVisualizer\TestDebugVisualizer.csproj", "{221A5163-F586-4766-A524-C8097AD06068}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{221A5163-F586-4766-A524-C8097AD06068}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{221A5163-F586-4766-A524-C8097AD06068}.Debug|Any CPU.Build.0 = Debug|Any CPU
{221A5163-F586-4766-A524-C8097AD06068}.Release|Any CPU.ActiveCfg = Release|Any CPU
{221A5163-F586-4766-A524-C8097AD06068}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C63DCA03-00AC-476D-B4FE-43DC4F4EBA35}
EndGlobalSection
EndGlobal

View File

@@ -1,196 +0,0 @@
# RegEx Vector3 Debug Visualizer
This VisualStudio.Extensibility extension adds two new debugger visualizers supporting the .NET [`Vector3`](https://learn.microsoft.com/dotnet/api/system.text.regularexpressions.Vector3) and [`Vector3Collection`](https://learn.microsoft.com/dotnet/api/system.text.regularexpressions.Vector3collection) classes.
![RegEx Vector3 visualizer](Vector3Visualizer.png "RegEx Vector3 visualizer")
The extension is composed of two projects: `Vector3DebugVisualizer`, the actual extension, and `Vector3ObjectSource`, the visualizer object source library.
## Creating the extension
The extension project is created as described in the [tutorial document](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/get-started/create-your-first-extension). You can also reference the [debugger visualizers guide](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/debugger-visualizer/debugger-visualizers) for additional information.
## The `Vector3` visualizer
The next step is to create a `DebuggerVisualizerProvider` class to visualize instances of [`Vector3`](https://learn.microsoft.com/dotnet/api/system.text.regularexpressions.Vector3):
```csharp
[VisualStudioContribution]
internal class Vector3DebuggerVisualizerProvider : DebuggerVisualizerProvider
{
...
```
If the `Vector3` type were serializable by Newtonsoft.Json, the visualizer implementation would be extremely simple:
```csharp
public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => ew("Regex Vector3 visualizer", typeof(Vector3));
public override async Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget isualizerTarget, CancellationToken cancellationToken)
{
var Vector3 = await visualizerTarget.ObjectSource.RequestDataAsync<Vector3>(jsonSerializer: null, cancellationToken);
return new Vector3VisualizerUserControl(Vector3);
}
```
Unfortunately `Vector3` is not serializable as-is, so we need a new serializable class [`Vector3`](Vector3ObjectSource/Vector3.cs). And we will need to create a visualizer object source library to convert the `Vector3` into a serializable `Vector3`, more about this in [a later paragraph](#the-visualizer-object-source). For now, let's just update the `RequestDataAsync` call to use `Vector3`:
```csharp
var Vector3 = await visualizerTarget.ObjectSource.RequestDataAsync<Vector3>(jsonSerializer: null, cancellationToken);
```
### Adding the remote user control
We now have to create the `Vector3VisualizerUserControl` [class](./Vector3DebugVisualizer/Vector3/Vector3VisualizerUserControl.cs) and its associated [XAML file](./Vector3DebugVisualizer/Vector3/Vector3VisualizerUserControl.xaml). This process is described in the [Remote UI documentation](https://learn.microsoft.com/visualstudio/extensibility/visualstudio.extensibility/inside-the-sdk/remote-ui).
Every time we create a remote user control like `Vector3VisualizerUserControl` we need to configure the corresponding XAML file as embedded resource. In this case, since the XAML file is in a subfolder, we also need to use `LogicalName` to make sure the name of the resource Vector3es the full name of the remote user control class. This is all done in the `.csproj` file:
```xml
<ItemGroup>
<Page Remove="Vector3VisualizerUserControl.xaml" />
<EmbeddedResource Include="Vector3\Vector3VisualizerUserControl.xaml" LogicalName="$(RootNamespace).Vector3VisualizerUserControl.xaml" />
</ItemGroup>
```
## The visualizer object source
The visualizer object source assembly will be loaded by the debugger into the process being debugged and will take care of converting the `Vector3` object into a serializable `Vector3`.
I will start creating a `Vector3ObjectSource` class library targeting `netstandard2.0` and adding a project reference to [Microsoft.VisualStudio.DebuggerVisualizers](https://www.nuget.org/packages/Microsoft.VisualStudio.DebuggerVisualizers) version 17.6 or newer. Targeting `netstandard2.0` will allow the debugger visualizer to easily work with a large variety of .NET versions.
I will then create a `Vector3ObjectSource` class extending `VisualizerObjectSource` and will override the `GetData` method to convert the `target` from a `Vector3` to a `Vector3` and use the `VisualizerObjectSource.SerializeAsJson` method to write the value to the `outgoingData` stream.
```csharp
public class Vector3ObjectSource : VisualizerObjectSource
{
public override void GetData(object target, Stream outgoingData)
{
if (target is Vector3 Vector3)
{
Vector3 result = Convert(Vector3);
SerializeAsJson(outgoingData, result);
}
}
...
```
The `GetData` method is invoked by the debugger when the `Vector3DebuggerVisualizerProvider` calls `RequestDataAsync`.
`SerializeAsJson` will serialize the `Vector3` object using Newtonsoft.Json, which is loaded by the debugger in the process being debugged via reflection. Since my visualizer object source doesn't need to refence Newtonsoft.Json directly, I didn't include a `PackageReference` to it, which is better since we should minimize the dependencies of the visualizer object source assembly. Because this code doesn't reference Newtonsoft.Json, the `Vector3` class uses `DataContract` and `DataMember` attributes to control serialization instead of the Newtonsoft.Json-specific types.
My [`Vector3ObjectSource`](./Vector3ObjectSource/Vector3ObjectSource.cs) implementation contains a small trick: the [`Group.Name`](https://learn.microsoft.com/dotnet/api/system.text.regularexpressions.group.name) property is read through reflection since it's available on most .NET versions but it is not included in the `netstandard2.0` APIs:
```csharp
private static readonly Func<Group, string?>? GetGroupName =
(Func<Group, string?>?)typeof(Group).GetProperty("Name")?.GetGetMethod().CreateDelegate(typeof(Func<Group, string?>));
...
Name = $"[{GetGroupName?.Invoke(g) ?? i.ToString()}]"
```
### Referencing the visualizer object source from the extension
First, we need to make sure that the visualizer object source library is packaged as part of the extension. We can do that in the extension's `.csproj` file:
```xml
<ItemGroup>
<Content Include="..\..\..\..\bin\samples\Vector3ObjectSource\$(Configuration)\netstandard2.0\Vector3ObjectSource.dll" Link="netstandard2.0\Vector3ObjectSource.dll">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Vector3ObjectSource\Vector3ObjectSource.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>
```
The `ProjectReference` guarantees that the visualizer object source library is built before the extension and the `Content` item makes sure that the visualizer object source DLL is copied into the `netstandard2.0` extension's subfolder where it will be discoverable by the debugger.
I have decided to use `ReferenceOutputAssembly="false"` to avoid a dependency of the extension assembly from the visualizer object source one. This allows using conditional compilation (`#if`) to have slightly different definitions of [`RegexCapture`](./Vector3ObjectSource/RegexCapture.cs) in the two projects. Since I decided to avoid the dependency, I will need to:
1. link the `Vector3.cs` file (and the related `RegexCapture` and `RegexGroup` ones) so that they are available in both projects:
```xml
<ItemGroup>
<Compile Include="..\Vector3ObjectSource\RegexGroup.cs" Link="SharedFiles\RegexGroup.cs" />
<Compile Include="..\Vector3ObjectSource\RegexCapture.cs" Link="SharedFiles\RegexCapture.cs" />
<Compile Include="..\Vector3ObjectSource\Vector3.cs" Link="SharedFiles\Vector3.cs" />
</ItemGroup>
```
2. Reference the `Vector3ObjectSource` from the `DebuggerVisualizerProviderConfiguration` using its assembly-qualified name:
```csharp
public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new("Regex Vector3 visualizer", typeof(Vector3))
{
VisualizerObjectSourceType = new("Microsoft.VisualStudio.Gladstone.Vector3Visualizer.ObjectSource.Vector3ObjectSource, Vector3ObjectSource"),
};
```
In most cases, having the extension project depend on the visualizer object source library is simpler: I could have simplified the `DebuggerVisualizerProviderConfiguration` to:
```csharp
public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new("Regex Vector3 visualizer", typeof(Vector3))
{
VisualizerObjectSourceType = new(typeof(Vector3ObjectSource)),
};
```
## The `Vector3Collection` visualizer
Now that the `Vector3` visualizer is complete, we can add a second visualizer for the [`Vector3Collection`](https://learn.microsoft.com/dotnet/api/system.text.regularexpressions.Vector3collection) class. The process is exactly the same: create a new [`DebuggerVisualizerProvider`](./Vector3DebugVisualizer/Vector3Collection/Vector3CollectionDebuggerVisualizerProvider.cs) and its [remote user control](./Vector3DebugVisualizer/Vector3Collection/Vector3CollectionVisualizerUserControl.cs). Also, add a new [`VisualizerObjectSource`](./Vector3ObjectSource/Vector3CollectionObjectSource.cs) to the visualizer object source library.
Each call to `RequestDataAsync` is allowed only 5 seconds to complete before throwing a timeout exception. Since the `Vector3Collection` could contain many entries, the visualizer object source uses the `TransferData` method instead of `GetData`: `TransferData` accepts a parameter which allows the visualizer to query the collection entries one by one:
```csharp
public override void TransferData(object target, Stream incomingData, Stream outgoingData)
{
var index = (int)DeserializeFromJson(incomingData, typeof(int))!;
if (target is Vector3Collection Vector3Collection && index < Vector3Collection.Count)
{
var result = Vector3ObjectSource.Convert(Vector3Collection[index]);
result.Name = $"[{index}]";
SerializeAsJson(outgoingData, result);
}
else
{
SerializeAsJson(outgoingData, null);
}
}
```
Instead of using the `VisualizerTarget` directly, the `DebuggerVisualizerProvider` passes it to the remote user control so that it can asynchronously request the collection entries without delaying the display of the visualizer UI to the user.
```csharp
public override Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
{
return Task.FromResult<IRemoteUserControl>(new Vector3CollectionVisualizerUserControl(visualizerTarget));
}
```
The remote user control uses the `RequestDataAsync` override that takes a `message` parameter, which results in `TransferData` being invoked on the visualizer object source. The remote user control will loop, invoking `RequestDataAsync` for increasing index numbers until the visualizer object source returns `null`, which indicates the end of the collection:
```csharp
public override Task ControlLoadedAsync(CancellationToken cancellationToken)
{
_ = Task.Run(async () =>
{
for (int i = 0; ; i++)
{
Vector3? Vector3 = await this.visualizerTarget.ObjectSource.RequestDataAsync<int, Vector3?>(message: i, jsonSerializer: null, CancellationToken.None);
if (Vector3 is null)
{
break;
}
this.Vector3es.Add(Vector3);
}
});
return Task.CompletedTask;
}
```
This is a very simple implementation of a debugger visualizer which relies on `RequestDataAsync`. More complex implementations may pass more complex parameters to `RequestDataAsync` in order to retrieve different information from the visualizer object source. You could even invoke `RequestDataAsync` in response to the user's interactions with the remote user control, allowing the user to "explore" the content of, potentially very large, objects.

View File

@@ -1,3 +0,0 @@
{
"Vector3Visualizer.Vector3DebuggerVisualizerProvider.DisplayName": "Vector3 visualizer"
}

View File

@@ -1,35 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Vector3Visualizer;
using System.Numerics;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.DebuggerVisualizers;
using Microsoft.VisualStudio.RpcContracts.RemoteUI;
/// <summary>
/// Debugger visualizer provider class for <see cref="Vector3"/>.
/// </summary>
[VisualStudioContribution]
internal sealed class Vector3DebuggerVisualizerProvider : DebuggerVisualizerProvider
{
/// <inheritdoc/>
public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new("%Vector3Visualizer.Vector3DebuggerVisualizerProvider.DisplayName%", typeof(Vector3))
{
VisualizerObjectSourceType = new("Vector3Visualizer.ObjectSource.Vector3ObjectSource, Vector3ObjectSource"),
Style = VisualizerStyle.ToolWindow,
};
/// <inheritdoc/>
public override async Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
{
var Vector3 = await visualizerTarget.ObjectSource.RequestDataAsync<Vector3>(jsonSerializer: null, cancellationToken);
return new Vector3VisualizerUserControl(Vector3);
}
}

View File

@@ -1,20 +0,0 @@
using System.Numerics;
using Microsoft.VisualStudio.Extensibility.UI;
namespace Vector3Visualizer;
/// <summary>
/// Remote user control to visualize the <see cref="Vector3"/> value.
/// </summary>
internal sealed class Vector3VisualizerUserControl : RemoteUserControl
{
/// <summary>
/// Initializes a new instance of the <see cref="Vector3VisualizerUserControl"/> class.
/// </summary>
/// <param name="dataContext">Data context of the remote control.</param>
public Vector3VisualizerUserControl(Vector3 dataContext)
: base(dataContext)
{
}
}

View File

@@ -1,7 +0,0 @@
<DataTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vs="http://schemas.microsoft.com/visualstudio/extensibility/2022/xaml"
xmlns:styles="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.15.0"
xmlns:colors="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.15.0">
</DataTemplate>

View File

@@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows8.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
<RootNamespace>Vector3Visualizer</RootNamespace>
<DefineConstants>$(DefineConstants);VISUALIZER</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Content Remove=".vsextension\string-resources.json" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Sdk" Version="17.14.40608" PrivateAssets="all" />
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Build" Version="17.14.40608" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Vector3\Vector3VisualizerUserControl.xaml" LogicalName="$(RootNamespace).Vector3VisualizerUserControl.xaml" />
</ItemGroup>
<ItemGroup>
<None Include=".vsextension\string-resources.json" />
</ItemGroup>
</Project>

View File

@@ -1,32 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Vector3Visualizer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.Extensibility;
/// <summary>
/// Extension entry point for the Vector3Visualizer sample extension.
/// </summary>
[VisualStudioContribution]
internal sealed class Vector3VisualizerExtension : Extension
{
/// <inheritdoc/>
public override ExtensionConfiguration ExtensionConfiguration => new()
{
Metadata = new(
id: "Vector3Visualizer.29d15448-6b97-42e5-97c7-bb12ded13b89",
version: this.ExtensionAssemblyVersion,
publisherName: "Microsoft",
displayName: "Vector3 Debugger Visualizer",
description: "A debugger visualizer for Vector3"),
};
/// <inheritdoc/>
protected override void InitializeServices(IServiceCollection serviceCollection)
{
base.InitializeServices(serviceCollection);
}
}

View File

@@ -1,33 +0,0 @@
using Microsoft.VisualStudio.Extensibility;
using Microsoft.VisualStudio.Extensibility.DebuggerVisualizers;
using Microsoft.VisualStudio.RpcContracts.RemoteUI;
using System.Collections.Generic;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using Vector3VisualizerSource;
namespace Vector3Visualizer;
[VisualStudioContribution]
internal class Vector3DebuggerVisualizerProvider : DebuggerVisualizerProvider
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("ConstantExpressionEvaluator", "CEE0027:String not localized", Justification = "<Pending>")]
public override DebuggerVisualizerProviderConfiguration DebuggerVisualizerProviderConfiguration => new(
[
new VisualizerTargetType("Vector3 Visualizer", "System.Numerics.Vector3, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e"),
new VisualizerTargetType("Vector3[] Visualizer", typeof(Vector3[])),
new VisualizerTargetType("List<Vector3> Visualizer", typeof(List<>)),
new VisualizerTargetType("Quaternion Visualizer", typeof(Quaternion))
])
{
VisualizerObjectSourceType = new VisualizerObjectSourceType(typeof(Vector3ObjectSource)),
Style = VisualizerStyle.ToolWindow,
};
public override async Task<IRemoteUserControl> CreateVisualizerAsync(VisualizerTarget visualizerTarget, CancellationToken cancellationToken)
{
Vector3Model? model = await visualizerTarget.ObjectSource.RequestDataAsync<Vector3Model?>(jsonSerializer: null, CancellationToken.None);
return await Task.FromResult<IRemoteUserControl>(new Vector3VisualizerUserControl(model));
//return await Task.FromResult<IRemoteUserControl>(new MyRemoteUserControl(model));
}
}

View File

@@ -1,25 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows8.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
<NeutralLanguage>en-US</NeutralLanguage>
</PropertyGroup>
<ItemGroup>
<None Remove="Vector3VisualizerUserControl.xaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Sdk" Version="17.14.40608" PrivateAssets="all" />
<PackageReference Include="Microsoft.VisualStudio.Extensibility.Build" Version="17.14.40608" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<Content Include="..\bin\Vector3VisualizerSource\$(Configuration)\netstandard2.0\\Vector3VisualizerSource.dll" Link="netstandard2.0\Vector3VisualizerSource.dll" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Vector3VisualizerUserControl.xaml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Vector3VisualizerSource\Vector3VisualizerSource.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.DebuggerVisualizers" Version="17.6.1032901" />
<PackageReference Include="System.Numerics.Vectors" Version="4.6.1" />
</ItemGroup>
</Project>