diff --git a/src/EditorFeatures/Core/Extensibility/Commands/PredefinedCommandHandlerNames.cs b/src/EditorFeatures/Core/Extensibility/Commands/PredefinedCommandHandlerNames.cs
index 545e53524b7d7..531132d451939 100644
--- a/src/EditorFeatures/Core/Extensibility/Commands/PredefinedCommandHandlerNames.cs
+++ b/src/EditorFeatures/Core/Extensibility/Commands/PredefinedCommandHandlerNames.cs
@@ -178,5 +178,10 @@ internal static class PredefinedCommandHandlerNames
/// Command handler name for Edit and Continue file save handler.
///
public const string EditAndContinueFileSave = "Edit and Continue Save File Handler";
+
+ ///
+ /// Command handler name for showing the Value Tracking tool window.
+ ///
+ public const string ShowValueTracking = "Show Value Tracking";
}
}
diff --git a/src/EditorFeatures/Core/ValueTracking/ValueTrackedItem.cs b/src/EditorFeatures/Core/ValueTracking/ValueTrackedItem.cs
index 0c8bd812e5b38..3fff8bac5048f 100644
--- a/src/EditorFeatures/Core/ValueTracking/ValueTrackedItem.cs
+++ b/src/EditorFeatures/Core/ValueTracking/ValueTrackedItem.cs
@@ -2,19 +2,86 @@
// 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.Immutable;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Classification;
+using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+using Microsoft.CodeAnalysis.Text;
+using Roslyn.Utilities;
+
namespace Microsoft.CodeAnalysis.ValueTracking
{
internal class ValueTrackedItem
{
- public Location Location { get; }
public ISymbol Symbol { get; }
+ public ValueTrackedItem? Parent { get; }
+
+ public Document Document { get; }
+ public TextSpan Span { get; }
+ public ImmutableArray ClassifiedSpans { get; }
+ public SourceText SourceText { get; }
+ public LineSpan LineSpan { get; }
- public ValueTrackedItem(
- Location location,
- ISymbol symbol)
+ private ValueTrackedItem(
+ ISymbol symbol,
+ SourceText sourceText,
+ ImmutableArray classifiedSpans,
+ TextSpan textSpan,
+ Document document,
+ LineSpan lineSpan,
+ ValueTrackedItem? parent = null)
{
- Location = location;
Symbol = symbol;
+ Parent = parent;
+
+ Span = textSpan;
+ ClassifiedSpans = classifiedSpans;
+ SourceText = sourceText;
+ LineSpan = lineSpan;
+ Document = document;
+ }
+
+ public static async Task TryCreateAsync(Solution solution, Location location, ISymbol symbol, ValueTrackedItem? parent = null, CancellationToken cancellationToken = default)
+ {
+ Contract.ThrowIfNull(location.SourceTree);
+
+ var document = solution.GetRequiredDocument(location.SourceTree);
+ var excerptService = document.Services.GetService();
+ SourceText? sourceText = null;
+ ImmutableArray classifiedSpans = default;
+
+ if (excerptService != null)
+ {
+ var result = await excerptService.TryExcerptAsync(document, location.SourceSpan, ExcerptMode.SingleLine, cancellationToken).ConfigureAwait(false);
+ if (result.HasValue)
+ {
+ var value = result.Value;
+ sourceText = value.Content;
+ }
+ }
+
+ if (sourceText is null)
+ {
+ var documentSpan = await ClassifiedSpansAndHighlightSpanFactory.GetClassifiedDocumentSpanAsync(document, location.SourceSpan, cancellationToken).ConfigureAwait(false);
+ var classificationResult = await ClassifiedSpansAndHighlightSpanFactory.ClassifyAsync(documentSpan, cancellationToken).ConfigureAwait(false);
+ classifiedSpans = classificationResult.ClassifiedSpans;
+ sourceText = await location.SourceTree.GetTextAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ sourceText.GetLineAndOffset(location.SourceSpan.Start, out var lineStart, out var _);
+ sourceText.GetLineAndOffset(location.SourceSpan.End, out var lineEnd, out var _);
+ var lineSpan = LineSpan.FromBounds(lineStart, lineEnd);
+
+ return new ValueTrackedItem(
+ symbol,
+ sourceText,
+ classifiedSpans,
+ location.SourceSpan,
+ document,
+ lineSpan,
+ parent: parent);
}
}
}
diff --git a/src/EditorFeatures/Core/ValueTracking/ValueTrackingService.cs b/src/EditorFeatures/Core/ValueTracking/ValueTrackingService.cs
index 95c39d8d344bd..9effa93548c32 100644
--- a/src/EditorFeatures/Core/ValueTracking/ValueTrackingService.cs
+++ b/src/EditorFeatures/Core/ValueTracking/ValueTrackingService.cs
@@ -49,7 +49,11 @@ or ILocalSymbol
foreach (var syntaxRef in symbol.DeclaringSyntaxReferences)
{
var location = Location.Create(syntaxRef.SyntaxTree, syntaxRef.Span);
- progressCollector.Report(new ValueTrackedItem(location, symbol));
+ var item = await ValueTrackedItem.TryCreateAsync(solution, location, symbol, parent: null, cancellationToken).ConfigureAwait(false);
+ if (item is not null)
+ {
+ progressCollector.Report(item);
+ }
}
var findReferenceProgressCollector = new FindReferencesProgress(progressCollector);
@@ -75,7 +79,7 @@ public Task TrackValueSourceAsync(
ValueTrackingProgressCollector progressCollector,
CancellationToken cancellationToken)
{
- throw new NotImplementedException();
+ return Task.CompletedTask;
}
private class FindReferencesProgress : IStreamingFindReferencesProgress, IStreamingProgressTracker
@@ -100,14 +104,17 @@ public FindReferencesProgress(ValueTrackingProgressCollector valueTrackingProgre
public ValueTask OnFindInDocumentStartedAsync(Document document) => new();
- public ValueTask OnReferenceFoundAsync(ISymbol symbol, ReferenceLocation location)
+ public async ValueTask OnReferenceFoundAsync(ISymbol symbol, ReferenceLocation location)
{
if (location.IsWrittenTo)
{
- _valueTrackingProgressCollector.Report(new ValueTrackedItem(location.Location, symbol));
+ var solution = location.Document.Project.Solution;
+ var item = await ValueTrackedItem.TryCreateAsync(solution, location.Location, symbol, parent: null, CancellationToken.None).ConfigureAwait(false);
+ if (item is not null)
+ {
+ _valueTrackingProgressCollector.Report(item);
+ }
}
-
- return new();
}
public ValueTask OnStartedAsync() => new();
diff --git a/src/EditorFeatures/Test/ValueTracking/AbstractBaseValueTrackingTests.cs b/src/EditorFeatures/Test/ValueTracking/AbstractBaseValueTrackingTests.cs
index 40d4d8d82a33a..7e0719f1c0e3b 100644
--- a/src/EditorFeatures/Test/ValueTracking/AbstractBaseValueTrackingTests.cs
+++ b/src/EditorFeatures/Test/ValueTracking/AbstractBaseValueTrackingTests.cs
@@ -44,8 +44,7 @@ internal static async Task GetSelectedSymbolAsync(TextSpan textSpan, Do
internal static void ValidateItem(ValueTrackedItem item, int line)
{
- var lineSpan = item.Location.GetLineSpan();
- Assert.Equal(line, lineSpan.StartLinePosition.Line);
+ Assert.Equal(line, item.LineSpan.Start);
}
}
}
diff --git a/src/VisualStudio/Core/Def/Commands.vsct b/src/VisualStudio/Core/Def/Commands.vsct
index 422b93c7d0bfd..ae1d78ad48c01 100644
--- a/src/VisualStudio/Core/Def/Commands.vsct
+++ b/src/VisualStudio/Core/Def/Commands.vsct
@@ -411,6 +411,19 @@
RemoveUnusedReferences
+
+
@@ -516,6 +529,10 @@
+
+
+
+
@@ -601,6 +618,8 @@
+
+
diff --git a/src/VisualStudio/Core/Def/Guids.cs b/src/VisualStudio/Core/Def/Guids.cs
index 6f058e3c50002..994acb0fbbebc 100644
--- a/src/VisualStudio/Core/Def/Guids.cs
+++ b/src/VisualStudio/Core/Def/Guids.cs
@@ -125,6 +125,9 @@ internal static class Guids
public static readonly Guid RoslynCommandSetId = new(RoslynCommandSetIdString);
public static readonly Guid RoslynGroupId = new(RoslynGroupIdString);
+ public const string ValueTrackingToolWindowIdString = "60a19d42-2dd7-43f3-be90-c7a9cb7d28f4";
+ public static readonly Guid ValueTrackingToolWindowId = new(ValueTrackingToolWindowIdString);
+
// TODO: Remove pending https://github.com/dotnet/roslyn/issues/8927 .
// Interactive guids
public const string InteractiveCommandSetIdString = "00B8868B-F9F5-4970-A048-410B05508506";
diff --git a/src/VisualStudio/Core/Def/ID.RoslynCommands.cs b/src/VisualStudio/Core/Def/ID.RoslynCommands.cs
index d89285327286e..ad65fd8e87b1f 100644
--- a/src/VisualStudio/Core/Def/ID.RoslynCommands.cs
+++ b/src/VisualStudio/Core/Def/ID.RoslynCommands.cs
@@ -50,6 +50,7 @@ public static class RoslynCommands
public const int RunCodeAnalysisForProject = 0x0201;
public const int RemoveUnusedReferences = 0x0202;
+ public const int GoToValueTrackingWindow = 0x0203;
}
}
}
diff --git a/src/VisualStudio/Core/Def/Implementation/CommandBindings.cs b/src/VisualStudio/Core/Def/Implementation/CommandBindings.cs
index 27a6efccbd0ea..b2992efa119ab 100644
--- a/src/VisualStudio/Core/Def/Implementation/CommandBindings.cs
+++ b/src/VisualStudio/Core/Def/Implementation/CommandBindings.cs
@@ -8,6 +8,7 @@
using Microsoft.CodeAnalysis.Editor.Commanding.Commands;
using Microsoft.VisualStudio.Editor.Commanding;
using Microsoft.VisualStudio.LanguageServices;
+using Microsoft.VisualStudio.LanguageServices.ValueTracking;
namespace Microsoft.VisualStudio.Editor.Implementation
{
@@ -28,5 +29,9 @@ internal sealed class CommandBindings
[Export]
[CommandBinding(Guids.CSharpGroupIdString, ID.CSharpCommands.ContextOrganizeRemoveAndSort, typeof(SortAndRemoveUnnecessaryImportsCommandArgs))]
internal CommandBindingDefinition contextOrganizeRemoveAndSortCommandBinding;
+
+ [Export]
+ [CommandBinding(Guids.RoslynGroupIdString, ID.RoslynCommands.GoToValueTrackingWindow, typeof(ValueTrackingEditorCommandArgs))]
+ internal CommandBindingDefinition gotoDataFlowToolCommandBinding;
}
}
diff --git a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef
index 27e41b380e9ba..d1b8281f52747 100644
--- a/src/VisualStudio/Core/Def/PackageRegistration.pkgdef
+++ b/src/VisualStudio/Core/Def/PackageRegistration.pkgdef
@@ -24,3 +24,9 @@
"Value"=dword:00000000
"Title"="Enable experimental C#/VB LSP completion experience"
"PreviewPaneChannels"="IntPreview,int.main"
+
+// The option page configuration is duplicated in RoslynPackage
+// [ProvideToolWindow(typeof(ValueTracking.ValueTrackingToolWindow))]
+[$RootKey$\ToolWindows\{60a19d42-2dd7-43f3-be90-c7a9cb7d28f4}]
+"Name"="Microsoft.VisualStudio.LanguageServices.ValueTracking.ValueTrackingToolWindow"
+@="{6cf2e545-6109-4730-8883-cf43d7aec3e1}"
diff --git a/src/VisualStudio/Core/Def/RoslynPackage.cs b/src/VisualStudio/Core/Def/RoslynPackage.cs
index 7261ce5c45d59..57e373566a571 100644
--- a/src/VisualStudio/Core/Def/RoslynPackage.cs
+++ b/src/VisualStudio/Core/Def/RoslynPackage.cs
@@ -19,28 +19,23 @@
using Microsoft.CodeAnalysis.Logging;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Options;
-using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Telemetry;
using Microsoft.CodeAnalysis.Versions;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.LanguageServices.ColorSchemes;
using Microsoft.VisualStudio.LanguageServices.Experimentation;
using Microsoft.VisualStudio.LanguageServices.Implementation;
-using Microsoft.VisualStudio.LanguageServices.Implementation.DesignerAttribute;
using Microsoft.VisualStudio.LanguageServices.Implementation.Diagnostics;
using Microsoft.VisualStudio.LanguageServices.Implementation.Interactive;
using Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem.RuleSets;
-using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectTelemetry;
using Microsoft.VisualStudio.LanguageServices.Implementation.TableDataSource;
-using Microsoft.VisualStudio.LanguageServices.Implementation.TodoComments;
using Microsoft.VisualStudio.LanguageServices.Implementation.UnusedReferences;
using Microsoft.VisualStudio.LanguageServices.Telemetry;
using Microsoft.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
-using Microsoft.VisualStudio.Shell.ServiceBroker;
using Microsoft.VisualStudio.TaskStatusCenter;
using Microsoft.VisualStudio.Telemetry;
using Microsoft.VisualStudio.TextManager.Interop;
@@ -51,6 +46,9 @@
namespace Microsoft.VisualStudio.LanguageServices.Setup
{
[Guid(Guids.RoslynPackageIdString)]
+
+ // The option page configuration is duplicated in PackageRegistration.pkgdef
+ [ProvideToolWindow(typeof(ValueTracking.ValueTrackingToolWindow))]
internal sealed class RoslynPackage : AbstractPackage
{
// The randomly-generated key name is used for serializing the ILSpy decompiler EULA preference to the .SUO
@@ -188,6 +186,19 @@ protected override async Task LoadComponentsAsync(CancellationToken cancellation
LoadComponentsBackgroundAsync(cancellationToken).Forget();
}
+ // Overrides for VSSDK003 fix
+ // See https://github.com/Microsoft/VSSDK-Analyzers/blob/main/doc/VSSDK003.md
+ public override IVsAsyncToolWindowFactory GetAsyncToolWindowFactory(Guid toolWindowType)
+ => toolWindowType == typeof(ValueTracking.ValueTrackingToolWindow).GUID
+ ? this
+ : base.GetAsyncToolWindowFactory(toolWindowType);
+
+ protected override string GetToolWindowTitle(Type toolWindowType, int id)
+ => base.GetToolWindowTitle(toolWindowType, id);
+
+ protected override Task