Deleted MemoryStreamDebugVisualizer
Deleted NrxDebugVisualizer
This commit is contained in:
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<Solution>
|
|
||||||
<Project Path="MemoryStreamObjectSource/MemoryStreamObjectSource.csproj" />
|
|
||||||
<Project Path="MemoryStreamVisualizer/MemoryStreamVisualizer.csproj" />
|
|
||||||
<Project Path="TestApp/TestApp.csproj" />
|
|
||||||
</Solution>
|
|
||||||
@@ -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.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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);
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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
|
|
||||||
@@ -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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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.
|
|
||||||
@@ -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
|
|
||||||
@@ -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>
|
|
||||||
@@ -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
|
|
||||||
@@ -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.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"Vector3Visualizer.Vector3DebuggerVisualizerProvider.DisplayName": "Vector3 visualizer"
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
Reference in New Issue
Block a user