Files
Microsoft_IEnumerableVisual…/IEnumerableViewModel.cs
T

528 lines
23 KiB
C#
Raw Normal View History

2026-04-15 16:05:46 +02:00
// Decompiled with JetBrains decompiler
// Type: Microsoft.VisualStudio.Debugger.IEnumerableVisualizer.IEnumerableViewModel
// Assembly: IEnumerableVisualizer.UI, Version=18.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
// MVID: 8D605BCB-B575-4FCD-B6C8-446C06150F34
// Assembly location: C:\Program Files\Microsoft Visual Studio\18\Professional\Common7\IDE\CommonExtensions\Platform\Debugger\Visualizers\IEnumerableVisualizer.UI.dll
using Microsoft.VisualStudio.Debugger.IEnumerableVisualizer.Copilot;
using Microsoft.VisualStudio.Debugger.TabularDataUIShared;
using Microsoft.VisualStudio.Debugger.TabularDataUIShared.WPF;
using Microsoft.VisualStudio.DebuggerVisualizers;
using Microsoft.VisualStudio.Extensibility.DebuggerVisualizers;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Threading;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
#nullable enable
namespace Microsoft.VisualStudio.Debugger.IEnumerableVisualizer;
internal class IEnumerableViewModel : TabularDataViewModel, IExpressionCopilotHandler
{
private bool isMaximized;
public volatile int LoadedDebuggeeSideRowCount;
private bool _showTables;
private
#nullable disable
ObservableCollection<BaseTableViewModel> _tables;
internal object VisualizerObjectSource { get; }
internal VisualizerCommunicator Communicator { get; }
public override string VisualizerTelemetryPrefix
{
get => "VS/Diagnostics/Debugger/IEnumerableVisualizer/";
}
public bool IsMaximized
{
get => this.isMaximized;
set
{
if (this.isMaximized == value)
return;
this.isMaximized = value;
this.OnPropertyChanged(nameof (IsMaximized));
}
}
protected override async Task<bool> ExportDataToCSVAsync(string filePath, Encoding fileEncoding)
{
bool csvHelperAsync;
using (StreamWriter writer = new StreamWriter(filePath, false, fileEncoding))
csvHelperAsync = await this.ExportToCSVHelperAsync((TextWriter) writer, true);
return csvHelperAsync;
}
protected override async Task<bool> CopyDataToClipboardAsync()
{
StringBuilder sb = new StringBuilder();
bool clipboardAsync;
using (StringWriter writer = new StringWriter(sb))
{
int num = await this.ExportToCSVHelperAsync((TextWriter) writer, false) ? 1 : 0;
if (num != 0)
Clipboard.SetText(sb.ToString());
clipboardAsync = num != 0;
}
sb = (StringBuilder) null;
return clipboardAsync;
}
protected override async Task<bool> CopySelectedDataToClipboardAsync(
ICollection<Tuple<int, int>> cells,
int columnStartIndex,
int columnEndIndex,
int rowStartIndex,
int rowEndIndex,
bool isRectangularAndDense)
{
IEnumerableViewModel ienumerableViewModel = this;
List<ColumnInfo> list = ienumerableViewModel.SelectedTable.Columns.Where<ColumnInfo>((Func<ColumnInfo, bool>) (c => c.IsVisible && c.Index >= columnStartIndex && c.Index <= columnEndIndex)).ToList<ColumnInfo>();
StringBuilder sb = new StringBuilder();
using (StringWriter writer = new StringWriter(sb))
{
CSVWriter csvWriter = new CSVWriter(list.Select<ColumnInfo, string>((Func<ColumnInfo, string>) (c => c.DisplayName)), (TextWriter) writer);
IEnumerable<int> columnIndiciesToExport = list.Select<ColumnInfo, int>((Func<ColumnInfo, int>) (c => c.Index));
List<string> rowData = new List<string>(columnEndIndex - columnStartIndex);
for (int rowIndex = rowStartIndex; rowIndex <= rowEndIndex; ++rowIndex)
{
if (ienumerableViewModel.CancellableAsyncOperationToken.IsCancellationRequested || ienumerableViewModel.WindowClosingCancellationToken.IsCancellationRequested)
return false;
RowViewModel itemAt = (RowViewModel) ienumerableViewModel.SelectedTable.CollectionView.GetItemAt(rowIndex);
foreach (int columnIndex in columnIndiciesToExport)
{
if (isRectangularAndDense || cells.Contains(Tuple.Create<int, int>(rowIndex, columnIndex)))
rowData.Add(itemAt[columnIndex]);
else
rowData.Add(string.Empty);
}
await csvWriter.WriteRowAsync((IEnumerable<string>) rowData);
rowData.Clear();
}
csvWriter = (CSVWriter) null;
columnIndiciesToExport = (IEnumerable<int>) null;
rowData = (List<string>) null;
}
Clipboard.SetText(sb.ToString());
return true;
}
protected override async Task<bool> RefreshUnderlyingDataAsync(CancellationToken cancelToken)
{
IEnumerableViewModel iEnumerableViewModel = this;
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(new CancellationToken());
iEnumerableViewModel.LoadedDebuggeeSideRowCount = 0;
iEnumerableViewModel.IsStillLoadingDebuggeeSideRows = true;
iEnumerableViewModel.LastSearchQuery = (IVsSearchQuery) null;
if (cancelToken.IsCancellationRequested)
return false;
List<TableDisplayCache> tableListAsync = await iEnumerableViewModel.Communicator.GetTableListAsync(cancelToken, true);
int num = Math.Min(tableListAsync.Count, iEnumerableViewModel.Tables.Count);
int count = iEnumerableViewModel.Tables.Count;
for (int index = 0; index < num; ++index)
{
if (cancelToken.IsCancellationRequested)
return false;
TableDisplayCache tableModel = tableListAsync[index];
if (iEnumerableViewModel.Tables[index] is TableViewModel table)
{
IReadOnlyList<ColumnInfo> columns = table.Columns;
string name = table.Name;
table.SetModel(tableModel);
if (table.Name.Equals(name, StringComparison.Ordinal))
iEnumerableViewModel.RestoreColumnInfo(table, columns);
table.LoadRows();
}
else if (iEnumerableViewModel.Tables[index] is EmptyTableViewModel)
{
iEnumerableViewModel.Tables[index].ClearCache();
iEnumerableViewModel.Tables[index] = (BaseTableViewModel) new TableViewModel(iEnumerableViewModel, tableModel);
}
}
if (count > tableListAsync.Count)
{
for (int index = count - 1; index >= tableListAsync.Count; --index)
{
iEnumerableViewModel.Tables[index].ClearCache();
iEnumerableViewModel.Tables.RemoveAt(index);
}
}
else if (count < tableListAsync.Count)
{
for (int index = num; index < tableListAsync.Count; ++index)
{
TableDisplayCache tableModel = tableListAsync[index];
iEnumerableViewModel.Tables.Add((BaseTableViewModel) new TableViewModel(iEnumerableViewModel, tableModel));
}
}
await iEnumerableViewModel.InitializeVisualizerPropertiesAsync();
return true;
}
private void RestoreColumnInfo(
TableViewModel updatedTableViewModel,
IReadOnlyList<ColumnInfo> originalColumnInfos)
{
foreach (ColumnInfo columnInfo in originalColumnInfos.Skip<ColumnInfo>(1))
{
if (columnInfo.Index >= updatedTableViewModel.Columns.Count)
break;
ColumnInfo column = updatedTableViewModel.Columns[columnInfo.Index];
if (column.DisplayName.Equals(columnInfo.DisplayName, StringComparison.Ordinal))
{
bool flag = false;
if (columnInfo.ChildrenIndices != null && column.IsExpandable)
{
this.Communicator.ExpandColumn(updatedTableViewModel.Index, columnInfo.Index);
flag = true;
}
if (!columnInfo.IsVisible)
{
this.Communicator.HideColumn(updatedTableViewModel.Index, columnInfo.Index);
flag = true;
}
if (flag)
updatedTableViewModel.RefreshColumns();
}
}
}
private async Task<bool> ExportToCSVHelperAsync(TextWriter writer, bool shouldWriteHeaders)
{
IEnumerableViewModel ienumerableViewModel = this;
List<ColumnInfo> columns = ienumerableViewModel.SelectedTable.Columns.Where<ColumnInfo>((Func<ColumnInfo, bool>) (c => c.IsVisible)).ToList<ColumnInfo>();
if (columns.Count == 0)
{
await writer.WriteLineAsync();
return true;
}
CSVWriter csvWriter = new CSVWriter(columns.Select<ColumnInfo, string>((Func<ColumnInfo, string>) (c => c.DisplayName)), writer);
if (shouldWriteHeaders)
await csvWriter.WriteHeaderAsync();
IEnumerable<int> columnIndiciesToExport = (IEnumerable<int>) columns.Select<ColumnInfo, int>((Func<ColumnInfo, int>) (c => c.Index)).ToList<int>();
foreach (RowViewModel rowViewModel in (IEnumerable) ienumerableViewModel.SelectedTable.CollectionView)
{
CancellationToken cancellationToken = ienumerableViewModel.CancellableAsyncOperationToken;
if (!cancellationToken.IsCancellationRequested)
{
cancellationToken = ienumerableViewModel.WindowClosingCancellationToken;
if (!cancellationToken.IsCancellationRequested)
{
List<string> rowData = new List<string>();
foreach (int columnIndex in columnIndiciesToExport)
rowData.Add(rowViewModel[columnIndex]);
await csvWriter.WriteRowAsync((IEnumerable<string>) rowData);
continue;
}
}
return false;
}
return true;
}
public IEnumerableViewModel(
VisualizerObjectSourceClient visualizerObjectSource,
string visualizedExpression,
JoinableTaskFactory jtf)
: base(visualizedExpression, jtf)
{
this.VisualizerObjectSource = (object) (visualizerObjectSource ?? throw new ArgumentNullException(nameof (visualizerObjectSource)));
this.Tables = new ObservableCollection<BaseTableViewModel>();
this.Communicator = new VisualizerCommunicator(this);
}
public IEnumerableViewModel(
IAsyncVisualizerObjectProvider visualizerObjectSource,
JoinableTaskFactory jtf)
: base((string) null, jtf)
{
this.VisualizerObjectSource = (object) (visualizerObjectSource ?? throw new ArgumentNullException(nameof (visualizerObjectSource)));
this.Tables = new ObservableCollection<BaseTableViewModel>();
this.Communicator = new VisualizerCommunicator(this);
}
public event EventHandler<ContextMenuCommandArgs> ColumnsUpdateAttempted;
public event EventHandler<LatestDataAvailable> LatestDataAvailableUpdated;
public event EventHandler<PausedOperationInfo> ContinuePausedOperation;
public IViewHelper ViewHelper { get; set; }
internal UpdateTableRowResult UpdateTableRowResults(
TableViewModel table,
int rowIndex,
string[] values)
{
return this.Communicator.UpdateTableRowResults(table.Index, rowIndex, values);
}
internal TableDisplayCache InvalidateTableModel(TableViewModel table, out bool succeeded)
{
return this.Communicator.InvalidateTableCache(table.Index, out succeeded);
}
internal bool IsColumnLocked(ColumnInfo columnInfo)
{
return this.Communicator.IsColumnLocked(columnInfo);
}
internal void ExpandColumn(int columnIndex)
{
this.ContextMenuCommandHelper(Resources.G_ExpandColumnError, (Func<(bool, bool)>) (() =>
{
SuccessResult successResult = this.Communicator.ExpandColumn(this.SelectedTableIndex, columnIndex);
TelemetryUtilities.LogVisualizerTelemetry((TabularDataViewModel) this, TelemetryConstants.ExpandColumnCompletedEvent, TelemetryConstants.ExpandColumnEvent, (IEnumerable<(string, string)>) new List<(string, string)>()
{
(TelemetryConstants.PropertyResult, FormattableString.Invariant(FormattableStringFactory.Create("{0}", (object) successResult.WasSuccessful)))
});
return (successResult.WasSuccessful, true);
}));
}
internal void HideColumn(int columnIndex)
{
this.ContextMenuCommandHelper(Resources.G_HideColumnError, (Func<(bool, bool)>) (() =>
{
SuccessResult successResult = this.Communicator.HideColumn(this.SelectedTableIndex, columnIndex);
if (successResult.WasSuccessful)
this.SelectedTable.Columns[columnIndex].IsVisible = false;
TelemetryUtilities.LogVisualizerTelemetry((TabularDataViewModel) this, TelemetryConstants.HideColumnCompletedEvent, TelemetryConstants.HideColumnEvent, (IEnumerable<(string, string)>) new List<(string, string)>()
{
(TelemetryConstants.PropertyResult, FormattableString.Invariant(FormattableStringFactory.Create("{0}", (object) successResult.WasSuccessful)))
});
return (successResult.WasSuccessful, false);
}));
}
internal void HideChildren(int columnIndex)
{
this.ContextMenuCommandHelper(Resources.G_HideColumnsError, (Func<(bool, bool)>) (() =>
{
SuccessResult successResult = this.Communicator.HideChildren(this.SelectedTableIndex, columnIndex);
if (successResult.WasSuccessful)
{
foreach (int childrenIndex in this.SelectedTable.Columns[columnIndex].ChildrenIndices)
this.SelectedTable.Columns[childrenIndex].IsVisible = false;
}
TelemetryUtilities.LogVisualizerTelemetry((TabularDataViewModel) this, TelemetryConstants.HideChildrenCompletedEvent, TelemetryConstants.HideChildrenEvent, (IEnumerable<(string, string)>) new List<(string, string)>()
{
(TelemetryConstants.PropertyResult, FormattableString.Invariant(FormattableStringFactory.Create("{0}", (object) successResult.WasSuccessful)))
});
return (successResult.WasSuccessful, false);
}));
}
private void ContextMenuCommandHelper(string errorMessage, Func<(bool, bool)> customOperation)
{
(bool wasSuccessful, bool flag) = customOperation();
if (wasSuccessful & flag)
{
this.SelectedTable.RefreshColumns();
this.SelectedTable.InvalidateTableModel();
}
EventHandler<ContextMenuCommandArgs> columnsUpdateAttempted = this.ColumnsUpdateAttempted;
if (columnsUpdateAttempted == null)
return;
columnsUpdateAttempted((object) this, new ContextMenuCommandArgs(wasSuccessful, errorMessage));
}
public DataObjectTypeName DataObjectType { get; private set; }
public bool ShowTables
{
get => this._showTables;
set
{
if (this._showTables == value)
return;
this._showTables = value;
this.OnPropertyChanged(nameof (ShowTables));
}
}
public ObservableCollection<BaseTableViewModel> Tables
{
get => this._tables;
set
{
if (this._tables == value)
return;
this._tables = value;
this.OnPropertyChanged(nameof (Tables));
}
}
public TableViewModel SelectedTable => this.Tables[this.SelectedTableIndex] as TableViewModel;
public override ITableViewModel SelectedSimplifiedTable => (ITableViewModel) this.SelectedTable;
public async Task StartFetchingRemainingRowsAsync(CancellationToken cancellationToken)
{
IEnumerableViewModel sender = this;
bool shouldExit = false;
sender.IsStillLoadingDebuggeeSideRows = true;
while (!shouldExit)
{
CancellationToken cancellationToken1 = sender.WindowClosingCancellationToken;
if (cancellationToken1.IsCancellationRequested || cancellationToken.IsCancellationRequested)
break;
int newlyLoadedRowCount = (await sender.Communicator.AdvanceIEnumerableDataSourceAsync(5000, cancellationToken)).Value;
if (newlyLoadedRowCount != 5000)
shouldExit = true;
await Task.Yield();
cancellationToken1 = sender.WindowClosingCancellationToken;
if (!cancellationToken1.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
{
TableViewModel selectedTable = sender.SelectedTable;
sender.LoadedDebuggeeSideRowCount += newlyLoadedRowCount;
if (sender.LoadedDebuggeeSideRowCount > 1000000)
{
sender.LoadedDebuggeeSideRowCount = 1000000;
sender.StatusBarMessage = string.Format((IFormatProvider) CultureInfo.CurrentCulture, Resources.G_MaxRowsReached);
shouldExit = true;
}
if (shouldExit)
{
sender.IsStillLoadingDebuggeeSideRows = false;
IEnumerableViewModel viewModel = sender;
string debuggeeSideRowsEvent = TelemetryConstants.FinishedLoadingDebuggeeSideRowsEvent;
List<(string, string)> valueTupleList1 = new List<(string, string)>();
valueTupleList1.Add((TelemetryConstants.PropertyLoadedDebuggeeSideRowCount, FormattableString.Invariant(FormattableStringFactory.Create("{0}", (object) sender.LoadedDebuggeeSideRowCount))));
valueTupleList1.Add((TelemetryConstants.PropertyCurrentTableRowsCount, FormattableString.Invariant(FormattableStringFactory.Create("{0}", (object) selectedTable?.TableRows?.Count.GetValueOrDefault()))));
List<(string, string)> valueTupleList2 = valueTupleList1;
string tableColumnsCount = TelemetryConstants.PropertyCurrentTableColumnsCount;
object[] objArray = new object[1];
TableViewModel tableViewModel = selectedTable;
objArray[0] = (object) (tableViewModel != null ? tableViewModel.ColumnCount : 0);
string str = FormattableString.Invariant(FormattableStringFactory.Create("{0}", objArray));
(string, string) valueTuple = (tableColumnsCount, str);
valueTupleList2.Add(valueTuple);
List<(string, string)> properties = valueTupleList1;
await TelemetryUtilities.LogVisualizerTelemetryAsync((TabularDataViewModel) viewModel, debuggeeSideRowsEvent, (IEnumerable<(string, string)>) properties);
if (sender.IsCancellableAsyncOperationInProgress)
{
EventHandler<PausedOperationInfo> continuePausedOperation = sender.ContinuePausedOperation;
if (continuePausedOperation != null)
continuePausedOperation((object) sender, new PausedOperationInfo(sender.AsyncOperationsTracker.LastUserAsyncOperation));
}
}
if (selectedTable?.TableRows != null && selectedTable.TableRows.Count < sender.LoadedDebuggeeSideRowCount)
{
int num = selectedTable.TableRows.Count + (sender.LoadedDebuggeeSideRowCount - selectedTable.TableRows.Count);
for (int count = selectedTable.TableRows.Count; count < num; ++count)
selectedTable.TableRows.Add(new RowViewModel((ITableViewModel) selectedTable, count));
}
EventHandler<LatestDataAvailable> availableUpdated = sender.LatestDataAvailableUpdated;
if (availableUpdated != null)
availableUpdated((object) sender, new LatestDataAvailable(sender.LoadedDebuggeeSideRowCount));
sender.RefreshSelectedRowsStatusBarText();
selectedTable = (TableViewModel) null;
}
}
}
public override void TableRowsHaveAvailableData(
int rowIndex,
int rowCount,
bool hideProgressOnCompletion)
{
if (this.SelectedTable?.TableRows != null)
{
for (int index = 0; index < rowCount; ++index)
{
this.SelectedTable.TableRows[rowIndex + index].HasData = true;
this.SelectedTable.TableRows[rowIndex + index].OnValuesChanged();
}
}
if (!hideProgressOnCompletion)
return;
this.AsyncOperationsTracker.SetAsyncOperationStatusFor(AsyncOperationsTracker.AsyncOperationType.FETCHING_PAGE, false);
}
public async Task InitializeAsync()
{
IEnumerableViewModel iEnumerableViewModel = this;
try
{
foreach (TableDisplayCache tableModel in await iEnumerableViewModel.Communicator.GetTableListAsync(CancellationToken.None))
iEnumerableViewModel.Tables.Add((BaseTableViewModel) new TableViewModel(iEnumerableViewModel, tableModel));
await iEnumerableViewModel.InitializeVisualizerPropertiesAsync();
}
catch (Exception ex)
{
iEnumerableViewModel.IsEnabled = false;
}
}
private async Task InitializeVisualizerPropertiesAsync()
{
IEnumerableViewModel ienumerableViewModel = this;
ienumerableViewModel.DataObjectType = ienumerableViewModel.Communicator.DataObjectType;
ienumerableViewModel.ShowTables = ienumerableViewModel.Tables.Count > 1;
ienumerableViewModel.IsEnabled = ienumerableViewModel.Tables.Any<BaseTableViewModel>();
if (!ienumerableViewModel.IsEnabled)
ienumerableViewModel.Tables.Add((BaseTableViewModel) new EmptyTableViewModel(ienumerableViewModel));
if (ienumerableViewModel.Tables.Count > 0)
ienumerableViewModel.SelectedTableIndex = 0;
await TelemetryUtilities.LogVisualizerTelemetryAsync((TabularDataViewModel) ienumerableViewModel, TelemetryConstants.InitializedEvent, (IEnumerable<(string, string)>) new List<(string, string)>()
{
(TelemetryConstants.PropertyTableCount, FormattableString.Invariant(FormattableStringFactory.Create("{0}", (object) ienumerableViewModel.Tables.Count)))
});
}
public async Task<CopilotSessionStartResult> StartCopilotSessionAsync(
string currentExpression,
ExpressionReplaceCallback replaceCallback,
CopilotSessionType sessionType,
string additionalUserContext,
CancellationToken cancellationToken)
{
TableViewModel selectedTable = this.SelectedTable;
string type = selectedTable != null ? selectedTable.Columns.FirstOrDefault<ColumnInfo>()?.Type : (string) null;
return NullableHelpers.IsNullOrEmpty(type) || IEnumerableCopilotService.Instance == null ? IEnumerableCopilotService.FailedSessionStart : await IEnumerableCopilotService.Instance.StartCopilotAsync(sessionType, currentExpression, type, this.GetVisualizedExpressionTypeMembers(type), replaceCallback, additionalUserContext, cancellationToken);
}
public async Task<bool> UpdateSessionContextAsync(
Guid sessionId,
string updatedExpression,
string additionalUserContext,
CancellationToken cancellationToken)
{
TableViewModel selectedTable = this.SelectedTable;
string type = selectedTable != null ? selectedTable.Columns.FirstOrDefault<ColumnInfo>()?.Type : (string) null;
return !NullableHelpers.IsNullOrEmpty(type) && IEnumerableCopilotService.Instance != null && await IEnumerableCopilotService.Instance.UpdateSessionContextAsync(sessionId, updatedExpression, type, this.GetVisualizedExpressionTypeMembers(type), additionalUserContext, cancellationToken);
}
public Task<CopilotSessionStartResult> OpenSessionInChatAsync(
Guid sessionId,
CancellationToken cancellationToken)
{
return IEnumerableCopilotService.Instance == null ? Task.FromResult<CopilotSessionStartResult>(new CopilotSessionStartResult(false, new Guid?(sessionId))) : IEnumerableCopilotService.Instance.OpenSessionInChatAsync(sessionId, cancellationToken);
}
public Task ShowCopilotChatToolWindowAsync(CancellationToken cancellationToken)
{
return IEnumerableCopilotService.ShowCopilotChatToolWindowAsync(cancellationToken);
}
private IEnumerable<IEnumerableTypeDefinitionEntry> GetVisualizedExpressionTypeMembers(
string expressionTypeName)
{
return this.SelectedTable.Columns.Skip<ColumnInfo>(1).Select<ColumnInfo, IEnumerableTypeDefinitionEntry>((Func<ColumnInfo, IEnumerableTypeDefinitionEntry>) (c => new IEnumerableTypeDefinitionEntry(c.Type, PackageUtilities.TrimPrefix(c.DisplayName, expressionTypeName + ".", StringComparison.Ordinal))));
}
}