Added MemoryStreamDebugVisualizer

This commit is contained in:
Matthias Heil
2026-01-13 11:40:39 +01:00
parent 38246e4fe8
commit e48d6a63f8
13 changed files with 626 additions and 0 deletions

View File

@@ -0,0 +1,150 @@
// 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();
}
}
}