File: StackTraceExplorer\StackTraceExplorerRootViewModel.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_pxr0p0dn_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.StackTraceExplorer;
using Microsoft.VisualStudio.LanguageServices.Utilities;
using Microsoft.VisualStudio.Text.Classification;
 
namespace Microsoft.VisualStudio.LanguageServices.StackTraceExplorer;
 
internal class StackTraceExplorerRootViewModel : ViewModelBase
{
    private readonly VisualStudioWorkspace _workspace;
    private readonly IClassificationFormatMap _formatMap;
    private readonly ClassificationTypeMap _typeMap;
    private readonly IThreadingContext _threadingContext;
 
    public StackTraceExplorerRootViewModel(IThreadingContext threadingContext, VisualStudioWorkspace workspace, IClassificationFormatMap formatMap, ClassificationTypeMap typeMap)
    {
        _threadingContext = threadingContext;
        _workspace = workspace;
        _formatMap = formatMap;
        _typeMap = typeMap;
    }
 
    public ObservableCollection<StackTraceExplorerTab> Tabs { get; } = [];
 
    private StackTraceExplorerTab? _selectedTab;
    public StackTraceExplorerTab? SelectedTab
    {
        get => _selectedTab;
        set => SetProperty(ref _selectedTab, value);
    }
 
    /// <summary>
    /// Returns true if there's a tab that already matches the text
    /// </summary>
    public bool ContainsTab(string text)
        => Tabs.Any(tab => tab.Content.ViewModel.Matches(text));
 
    public async Task AddNewTabAsync(StackTraceAnalysisResult? result, string originalText, CancellationToken cancellationToken)
    {
        // Name will always have an index. Use the highest index opened + 1.
        var highestIndex = Tabs.Count == 0
            ? 0
            : Tabs.Max(t => t.NameIndex);
 
        var newTab = new StackTraceExplorerTab(_threadingContext, _workspace, _formatMap, _typeMap, highestIndex + 1);
        Tabs.Add(newTab);
 
        SelectedTab = newTab;
 
        newTab.OnClosed += Tab_Closed;
        if (result.HasValue)
        {
            await newTab.Content.ViewModel.SetStackTraceResultAsync(result.Value, originalText, cancellationToken).ConfigureAwait(false);
        }
    }
 
    public async Task DoPasteAsync(CancellationToken cancellationToken)
    {
        var text = Clipboard.GetText();
        if (string.IsNullOrEmpty(text))
        {
            return;
        }
 
        var result = await StackTraceAnalyzer.AnalyzeAsync(text, cancellationToken).ConfigureAwait(false);
        if (SelectedTab is { IsEmpty: true })
        {
            // Paste in the SelectedTab instead of opening a new tab
            // for cases where there are no contents in the current tab
            await SelectedTab.Content.ViewModel.SetStackTraceResultAsync(result, text, cancellationToken).ConfigureAwait(false);
        }
        else
        {
            await AddNewTabAsync(result, text, cancellationToken).ConfigureAwait(false);
        }
    }
 
    public void DoPasteSynchronously(CancellationToken cancellationToken)
    {
#pragma warning disable VSTHRD102 // Implement internal logic asynchronously
        _threadingContext.JoinableTaskFactory.Run(() => DoPasteAsync(cancellationToken));
#pragma warning restore VSTHRD102 // Implement internal logic asynchronously
    }
 
    private void Tab_Closed(object sender, System.EventArgs e)
    {
        var tab = (StackTraceExplorerTab)sender;
        Tabs.Remove(tab);
        tab.OnClosed -= Tab_Closed;
    }
}