From e66b56266baaed8f329c41a11d67eb508b4e0853 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Thu, 10 Feb 2022 18:16:57 -0800 Subject: [PATCH 1/5] Use IValueTrackingService to determine top level symbol instead of finding in the command handler --- .../ValueTrackedTreeItemViewModel.cs | 13 ++- .../ValueTrackingCommandHandler.cs | 104 ++++++------------ 2 files changed, 46 insertions(+), 71 deletions(-) diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs index d814e7a6daa0f..f1bd67be8cd96 100644 --- a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs @@ -78,6 +78,16 @@ private ValueTrackedTreeItemViewModel( } } + internal static ValueTask CreateAsync( + Solution solution, + ValueTrackedItem item, + ValueTrackingTreeViewModel treeViewModel, + IGlyphService glyphService, + IValueTrackingService valueTrackingService, + IThreadingContext threadingContext, + CancellationToken cancellationToken) + => CreateAsync(solution, item, treeViewModel, glyphService, valueTrackingService, threadingContext, ImmutableArray.Empty, cancellationToken); + internal static async ValueTask CreateAsync( Solution solution, ValueTrackedItem item, @@ -85,6 +95,7 @@ internal static async ValueTask CreateAsync( IGlyphService glyphService, IValueTrackingService valueTrackingService, IThreadingContext threadingContext, + ImmutableArray children, CancellationToken cancellationToken) { var document = solution.GetRequiredDocument(item.DocumentId); @@ -104,7 +115,7 @@ internal static async ValueTask CreateAsync( valueTrackingService, threadingContext, fileName, - children: ImmutableArray.Empty); + children); } private void CalculateChildren() diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs index e77986635bc75..1398cba8e9428 100644 --- a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs @@ -5,8 +5,13 @@ using System; using System.Collections.Immutable; using System.ComponentModel.Composition; +using System.Diagnostics; +using System.Linq; +using System.Reflection.Metadata; using System.Threading; using System.Threading.Tasks; +using System.Xml.Linq; +using EnvDTE; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Editor; @@ -25,6 +30,7 @@ using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; +using static System.Windows.Forms.VisualStyles.VisualStyleElement.TextBox; using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider; using Task = System.Threading.Tasks.Task; @@ -86,101 +92,59 @@ public bool ExecuteCommand(ValueTrackingEditorCommandArgs args, CommandExecution _threadingContext.JoinableTaskFactory.RunAsync(async () => { - var selectedSymbol = await GetSelectedSymbolAsync(textSpan, document, cancellationToken).ConfigureAwait(false); - if (selectedSymbol is null) + var service = document.Project.Solution.Workspace.Services.GetRequiredService(); + var items = await service.TrackValueSourceAsync(textSpan, document, cancellationToken).ConfigureAwait(false); + if (items.Length == 0) { - // TODO: Show error dialog return; } - var syntaxTree = document.GetRequiredSyntaxTreeSynchronously(cancellationToken); - var location = Location.Create(syntaxTree, textSpan); - - await ShowToolWindowAsync(args.TextView, selectedSymbol, location, document.Project.Solution, cancellationToken).ConfigureAwait(false); + await ShowToolWindowAsync(args.TextView, document, items, cancellationToken).ConfigureAwait(false); }); return true; } - private static async Task GetSelectedSymbolAsync(TextSpan textSpan, Document document, CancellationToken cancellationToken) - { - var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - var selectedNode = root.FindNode(textSpan); - if (selectedNode is null) - { - return null; - } - - var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); - var selectedSymbol = - semanticModel.GetSymbolInfo(selectedNode, cancellationToken).Symbol - ?? semanticModel.GetDeclaredSymbol(selectedNode, cancellationToken); - - if (selectedSymbol is null) - { - return null; - } - - return selectedSymbol switch - { - ILocalSymbol - or IPropertySymbol { SetMethod: not null } - or IFieldSymbol { IsReadOnly: false } - or IEventSymbol - or IParameterSymbol - => selectedSymbol, - - _ => null - }; - } - - private async Task ShowToolWindowAsync(ITextView textView, ISymbol selectedSymbol, Location location, Solution solution, CancellationToken cancellationToken) + private async Task ShowToolWindowAsync(ITextView textView, CodeAnalysis.Document document, ImmutableArray items, CancellationToken cancellationToken) { - var item = await ValueTrackedItem.TryCreateAsync(solution, location, selectedSymbol, cancellationToken: cancellationToken).ConfigureAwait(false); - if (item is null) - { - return; - } - var toolWindow = await GetOrCreateToolWindowAsync(textView, cancellationToken).ConfigureAwait(false); if (toolWindow?.ViewModel is null) { return; } - var valueTrackingService = solution.Workspace.Services.GetRequiredService(); + var rootItemMap = items.GroupBy(i => i.Parent, resultSelector: (key, items) => (parent: key, children: items.ToImmutableArray())).ToImmutableArray(); var classificationFormatMap = _classificationFormatMapService.GetClassificationFormatMap(textView); + var solution = document.Project.Solution; + var valueTrackingService = solution.Workspace.Services.GetRequiredService(); - var childItems = await valueTrackingService.TrackValueSourceAsync(solution, item, cancellationToken).ConfigureAwait(false); + var rootViewModels = await rootItemMap.SelectAsArrayAsync(async (pair, cancellationToken) => + { + var (parent, children) = pair; + + if (parent is null) + { + // If the parent items are null then each child is a top level item + return await children.SelectAsArrayAsync(async (child, cancellationToken) => + { + return await ValueTrackedTreeItemViewModel.CreateAsync(solution, child, toolWindow.ViewModel, _glyphService, valueTrackingService, _threadingContext, ImmutableArray.Empty, cancellationToken).ConfigureAwait(false); + }, cancellationToken).ConfigureAwait(false); + } - var childViewModels = await childItems.SelectAsArrayAsync((item, cancellationToken) => + var childrenViewModels = await children.SelectAsArrayAsync((item, cancellationToken) => ValueTrackedTreeItemViewModel.CreateAsync(solution, item, toolWindow.ViewModel, _glyphService, valueTrackingService, _threadingContext, cancellationToken), cancellationToken).ConfigureAwait(false); - RoslynDebug.AssertNotNull(location.SourceTree); - var document = solution.GetRequiredDocument(location.SourceTree); - var options = ClassificationOptions.From(document.Project); - - var sourceText = await location.SourceTree.GetTextAsync(cancellationToken).ConfigureAwait(false); - var documentSpan = await ClassifiedSpansAndHighlightSpanFactory.GetClassifiedDocumentSpanAsync(document, location.SourceSpan, options, cancellationToken).ConfigureAwait(false); - var classificationResult = await ClassifiedSpansAndHighlightSpanFactory.ClassifyAsync(documentSpan, options, cancellationToken).ConfigureAwait(false); - - var root = new TreeItemViewModel( - location.SourceSpan, - sourceText, - document.Id, - document.FilePath ?? document.Name, - selectedSymbol.GetGlyph(), - classificationResult.ClassifiedSpans, - toolWindow.ViewModel, - _glyphService, - _threadingContext, - solution.Workspace, - childViewModels); + var root = await ValueTrackedTreeItemViewModel.CreateAsync(solution, parent, toolWindow.ViewModel, _glyphService, valueTrackingService, _threadingContext, childrenViewModels, cancellationToken).ConfigureAwait(false); + return ImmutableArray.Create(root); + }, cancellationToken).ConfigureAwait(false); await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); toolWindow.ViewModel.Roots.Clear(); - toolWindow.ViewModel.Roots.Add(root); + foreach (var root in rootViewModels.SelectMany(items => items)) + { + toolWindow.ViewModel.Roots.Add(root); + } await ShowToolWindowAsync(cancellationToken).ConfigureAwait(true); } From 062b32dd445725a3472461ba7d7cd6d6d33b70bd Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Thu, 10 Feb 2022 18:18:49 -0800 Subject: [PATCH 2/5] Remove and sort usings --- .../Core/Def/ValueTracking/ValueTrackingCommandHandler.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs index 1398cba8e9428..e83963c4ce47e 100644 --- a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs @@ -5,21 +5,15 @@ using System; using System.Collections.Immutable; using System.ComponentModel.Composition; -using System.Diagnostics; using System.Linq; -using System.Reflection.Metadata; using System.Threading; using System.Threading.Tasks; -using System.Xml.Linq; -using EnvDTE; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Editor; using Microsoft.CodeAnalysis.Editor.Shared.Extensions; using Microsoft.CodeAnalysis.Editor.Shared.Utilities; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Internal.Log; -using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.ValueTracking; using Microsoft.VisualStudio.Commanding; @@ -30,7 +24,6 @@ using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; using Roslyn.Utilities; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.TextBox; using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider; using Task = System.Threading.Tasks.Task; From cc382f7b04d707761bb1ad543786e36aa0e1042b Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Fri, 11 Feb 2022 15:11:29 -0800 Subject: [PATCH 3/5] PR feedback --- .../ValueTrackedTreeItemViewModel.cs | 12 +----- .../ValueTrackingCommandHandler.cs | 39 +++++++++++-------- 2 files changed, 24 insertions(+), 27 deletions(-) diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs index f1bd67be8cd96..a4b7002334a35 100644 --- a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs @@ -78,16 +78,6 @@ private ValueTrackedTreeItemViewModel( } } - internal static ValueTask CreateAsync( - Solution solution, - ValueTrackedItem item, - ValueTrackingTreeViewModel treeViewModel, - IGlyphService glyphService, - IValueTrackingService valueTrackingService, - IThreadingContext threadingContext, - CancellationToken cancellationToken) - => CreateAsync(solution, item, treeViewModel, glyphService, valueTrackingService, threadingContext, ImmutableArray.Empty, cancellationToken); - internal static async ValueTask CreateAsync( Solution solution, ValueTrackedItem item, @@ -183,7 +173,7 @@ private async Task> CalculateChildrenAsync(Can cancellationToken).ConfigureAwait(false); return await valueTrackedItems.SelectAsArrayAsync((item, cancellationToken) => - CreateAsync(_solution, item, TreeViewModel, _glyphService, _valueTrackingService, ThreadingContext, cancellationToken), cancellationToken).ConfigureAwait(false); + CreateAsync(_solution, item, TreeViewModel, _glyphService, _valueTrackingService, ThreadingContext, ImmutableArray.Empty, cancellationToken), cancellationToken).ConfigureAwait(false); } } } diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs index e83963c4ce47e..aacf3ccc9c4f3 100644 --- a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs @@ -98,7 +98,7 @@ public bool ExecuteCommand(ValueTrackingEditorCommandArgs args, CommandExecution return true; } - private async Task ShowToolWindowAsync(ITextView textView, CodeAnalysis.Document document, ImmutableArray items, CancellationToken cancellationToken) + private async Task ShowToolWindowAsync(ITextView textView, Document document, ImmutableArray items, CancellationToken cancellationToken) { var toolWindow = await GetOrCreateToolWindowAsync(textView, cancellationToken).ConfigureAwait(false); if (toolWindow?.ViewModel is null) @@ -106,35 +106,42 @@ private async Task ShowToolWindowAsync(ITextView textView, CodeAnalysis.Document return; } - var rootItemMap = items.GroupBy(i => i.Parent, resultSelector: (key, items) => (parent: key, children: items.ToImmutableArray())).ToImmutableArray(); + var classificationFormatMap = _classificationFormatMapService.GetClassificationFormatMap(textView); var solution = document.Project.Solution; var valueTrackingService = solution.Workspace.Services.GetRequiredService(); + var rootItemMap = items.GroupBy(i => i.Parent, resultSelector: (key, items) => (parent: key, children: items)); - var rootViewModels = await rootItemMap.SelectAsArrayAsync(async (pair, cancellationToken) => - { - var (parent, children) = pair; + using var _ = CodeAnalysis.PooledObjects.ArrayBuilder.GetInstance(out var rootItems); + foreach (var (parent, children) in rootItemMap) + { if (parent is null) { - // If the parent items are null then each child is a top level item - return await children.SelectAsArrayAsync(async (child, cancellationToken) => + foreach (var child in children) { - return await ValueTrackedTreeItemViewModel.CreateAsync(solution, child, toolWindow.ViewModel, _glyphService, valueTrackingService, _threadingContext, ImmutableArray.Empty, cancellationToken).ConfigureAwait(false); - }, cancellationToken).ConfigureAwait(false); + var root = await ValueTrackedTreeItemViewModel.CreateAsync(solution, child, toolWindow.ViewModel, _glyphService, valueTrackingService, _threadingContext, ImmutableArray.Empty, cancellationToken).ConfigureAwait(false); + rootItems.Add(root); + } } + else + { + using var _1 = CodeAnalysis.PooledObjects.ArrayBuilder.GetInstance(out var childItems); + foreach (var child in children) + { + var childViewModel = await ValueTrackedTreeItemViewModel.CreateAsync(solution, child, toolWindow.ViewModel, _glyphService, valueTrackingService, _threadingContext, ImmutableArray.Empty, cancellationToken).ConfigureAwait(false); + childItems.Add(childViewModel); + } - var childrenViewModels = await children.SelectAsArrayAsync((item, cancellationToken) => - ValueTrackedTreeItemViewModel.CreateAsync(solution, item, toolWindow.ViewModel, _glyphService, valueTrackingService, _threadingContext, cancellationToken), cancellationToken).ConfigureAwait(false); - - var root = await ValueTrackedTreeItemViewModel.CreateAsync(solution, parent, toolWindow.ViewModel, _glyphService, valueTrackingService, _threadingContext, childrenViewModels, cancellationToken).ConfigureAwait(false); - return ImmutableArray.Create(root); - }, cancellationToken).ConfigureAwait(false); + var root = await ValueTrackedTreeItemViewModel.CreateAsync(solution, parent, toolWindow.ViewModel, _glyphService, valueTrackingService, _threadingContext, childItems.ToImmutable(), cancellationToken).ConfigureAwait(false); + rootItems.Add(root); + } + } await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); toolWindow.ViewModel.Roots.Clear(); - foreach (var root in rootViewModels.SelectMany(items => items)) + foreach (var root in rootItems) { toolWindow.ViewModel.Roots.Add(root); } From 2741068f96765fbbb8a3f1c35186ec5e132bd0e5 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Mon, 14 Feb 2022 13:31:16 -0800 Subject: [PATCH 4/5] Merge fixes --- .../Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs | 4 ++-- .../Core/Def/ValueTracking/ValueTrackingCommandHandler.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs index 744ad48d6a44e..995dbe4e19bb5 100644 --- a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackedTreeItemViewModel.cs @@ -83,12 +83,12 @@ private ValueTrackedTreeItemViewModel( internal static async ValueTask CreateAsync( Solution solution, ValueTrackedItem item, + ImmutableArray children, ValueTrackingTreeViewModel treeViewModel, IGlyphService glyphService, IValueTrackingService valueTrackingService, IGlobalOptionService globalOptions, IThreadingContext threadingContext, - ImmutableArray children, CancellationToken cancellationToken) { var document = solution.GetRequiredDocument(item.DocumentId); @@ -174,7 +174,7 @@ private async Task> CalculateChildrenAsync(Can cancellationToken).ConfigureAwait(false); return await valueTrackedItems.SelectAsArrayAsync((item, cancellationToken) => - CreateAsync(_solution, item, ImmutableArray.Empty, TreeViewModel, _glyphService, _valueTrackingService, _globalOptions, ThreadingContext, cancellationToken), cancellationToken).ConfigureAwait(false); + CreateAsync(_solution, item, children: ImmutableArray.Empty, TreeViewModel, _glyphService, _valueTrackingService, _globalOptions, ThreadingContext, cancellationToken), cancellationToken).ConfigureAwait(false); } } } diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs index 7f908473e976a..9a7f31ed12783 100644 --- a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs @@ -125,7 +125,7 @@ private async Task ShowToolWindowAsync(ITextView textView, Document document, Im { foreach (var child in children) { - var root = await ValueTrackedTreeItemViewModel.CreateAsync(solution, child, toolWindow.ViewModel, _glyphService, valueTrackingService, _threadingContext, ImmutableArray.Empty, cancellationToken).ConfigureAwait(false); + var root = await ValueTrackedTreeItemViewModel.CreateAsync(solution, child, children: ImmutableArray.Empty, toolWindow.ViewModel, _glyphService, valueTrackingService, _globalOptions, _threadingContext, cancellationToken).ConfigureAwait(false); rootItems.Add(root); } } @@ -134,11 +134,11 @@ private async Task ShowToolWindowAsync(ITextView textView, Document document, Im using var _1 = CodeAnalysis.PooledObjects.ArrayBuilder.GetInstance(out var childItems); foreach (var child in children) { - var childViewModel = await ValueTrackedTreeItemViewModel.CreateAsync(solution, child, toolWindow.ViewModel, _glyphService, valueTrackingService, _threadingContext, ImmutableArray.Empty, cancellationToken).ConfigureAwait(false); + var childViewModel = await ValueTrackedTreeItemViewModel.CreateAsync(solution, child, children: ImmutableArray.Empty, toolWindow.ViewModel, _glyphService, valueTrackingService, _globalOptions, _threadingContext, cancellationToken).ConfigureAwait(false); childItems.Add(childViewModel); } - var root = await ValueTrackedTreeItemViewModel.CreateAsync(solution, parent, toolWindow.ViewModel, _glyphService, valueTrackingService, _threadingContext, childItems.ToImmutable(), cancellationToken).ConfigureAwait(false); + var root = await ValueTrackedTreeItemViewModel.CreateAsync(solution, parent, childItems.ToImmutable(), toolWindow.ViewModel, _glyphService, valueTrackingService, _globalOptions, _threadingContext, cancellationToken).ConfigureAwait(false); rootItems.Add(root); } } From 1a9e0aa3f8e861243b6cc2b705650b5f4e8cc826 Mon Sep 17 00:00:00 2001 From: Andrew Hall Date: Mon, 14 Feb 2022 19:16:43 -0800 Subject: [PATCH 5/5] Correctness fix --- .../Core/Def/ValueTracking/ValueTrackingCommandHandler.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs index 9a7f31ed12783..a5c8066ba2ef7 100644 --- a/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs +++ b/src/VisualStudio/Core/Def/ValueTracking/ValueTrackingCommandHandler.cs @@ -111,7 +111,6 @@ private async Task ShowToolWindowAsync(ITextView textView, Document document, Im return; } - var classificationFormatMap = _classificationFormatMapService.GetClassificationFormatMap(textView); var solution = document.Project.Solution; var valueTrackingService = solution.Workspace.Services.GetRequiredService();