diff --git a/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/SyntaxVisualizerControl.xaml.cs b/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/SyntaxVisualizerControl.xaml.cs index c1b586f67..9c72580e5 100644 --- a/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/SyntaxVisualizerControl.xaml.cs +++ b/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Control/SyntaxVisualizerControl.xaml.cs @@ -37,6 +37,7 @@ public enum SyntaxCategory SyntaxToken, SyntaxTrivia, Operation, + EmbeddedClassification, } // A control for visually displaying the contents of a SyntaxTree. @@ -113,6 +114,9 @@ private class SyntaxTag public event SyntaxTokenDelegate? SyntaxTokenDirectedGraphRequested; public event SyntaxTokenDelegate? SyntaxTokenNavigationToSourceRequested; + public delegate void SyntaxClassifiedSpanDelegate(ClassifiedSpan span); + public event SyntaxClassifiedSpanDelegate? SyntaxClassifiedSpanNavigationToSourceRequested; + public delegate void SyntaxTriviaDelegate(SyntaxTrivia trivia); public event SyntaxTriviaDelegate? SyntaxTriviaDirectedGraphRequested; public event SyntaxTriviaDelegate? SyntaxTriviaNavigationToSourceRequested; @@ -205,6 +209,10 @@ public SyntaxVisualizerControl() ClassifiedSpan = classifiedSpans.FirstOrDefault(s => s.TextSpan.Contains(trivia.Span)); break; + case ClassifiedSpan span: + ClassifiedSpan = span; + break; + default: ClassifiedSpan = null; break; @@ -518,6 +526,9 @@ private void ExpandPathTo(TreeViewItem? item) foreach (TreeViewItem item in current.Items) { + if (item == null) + continue; + if (category != SyntaxCategory.Operation && ((SyntaxTag)item.Tag).Category == SyntaxCategory.Operation) { // Do not prefer navigating to IOperation nodes when clicking in source code @@ -780,6 +791,40 @@ private void AddNode(TreeViewItem? parentItem, SyntaxNode? node) } } + private void AddEmbeddedClassification(TreeViewItem parentItem, ClassifiedSpan classifiedSpan) + { + var tag = new SyntaxTag + { + Category = SyntaxCategory.EmbeddedClassification, + FullSpan = classifiedSpan.TextSpan, + Span = classifiedSpan.TextSpan, + ParentItem = parentItem + }; + + var item = CreateTreeViewItem(tag, $"{classifiedSpan.ClassificationType} {classifiedSpan.TextSpan}", false); + + item.Selected += new RoutedEventHandler((sender, e) => + { + _isNavigatingFromTreeToSource = true; + + typeTextLabel.Visibility = Visibility.Visible; + kindTextLabel.Visibility = Visibility.Hidden; + typeValueLabel.Content = classifiedSpan.GetType().Name; + kindValueLabel.Content = null; + _propertyGrid.SelectedObject = classifiedSpan; + + if (!_isNavigatingFromSourceToTree && SyntaxClassifiedSpanNavigationToSourceRequested != null) + { + SyntaxClassifiedSpanNavigationToSourceRequested(classifiedSpan); + } + + _isNavigatingFromTreeToSource = false; + e.Handled = true; + }); + + parentItem.Items.Add(item); + } + private void AddToken(TreeViewItem parentItem, SyntaxToken token) { var kind = token.GetKind(); @@ -877,6 +922,23 @@ private void AddToken(TreeViewItem parentItem, SyntaxToken token) } } } + + if (token.GetKind().Contains("String") && item.Items.Count == 0 && classifiedSpans != null) // no child nodes and tokens + { + var embeddedClassifications = classifiedSpans.Where(cs => token.Span.Contains(cs.TextSpan)); + + if (embeddedClassifications.Count() > 1) + { + foreach (var classifiedSpan in embeddedClassifications.OrderBy(cs => cs.TextSpan.Start)) + { + // skip the full span itself + if (classifiedSpan.TextSpan == token.Span) + continue; + + AddEmbeddedClassification(item, classifiedSpan); + } + } + } } private void AddTrivia(TreeViewItem parentItem, SyntaxTrivia trivia, bool isLeadingTrivia) diff --git a/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Extension/SyntaxVisualizerContainer.xaml.cs b/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Extension/SyntaxVisualizerContainer.xaml.cs index f9bcd54b9..35edf2720 100644 --- a/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Extension/SyntaxVisualizerContainer.xaml.cs +++ b/src/VisualStudio.Roslyn.SDK/SyntaxVisualizer/Roslyn.SyntaxVisualizer.Extension/SyntaxVisualizerContainer.xaml.cs @@ -65,6 +65,7 @@ internal SyntaxVisualizerContainer(SyntaxVisualizerToolWindow parent) syntaxVisualizer.SyntaxNodeNavigationToSourceRequested += node => NavigateToSource(node?.Span); syntaxVisualizer.SyntaxTokenNavigationToSourceRequested += token => NavigateToSource(token.Span); + syntaxVisualizer.SyntaxClassifiedSpanNavigationToSourceRequested += span => NavigateToSource(span.TextSpan); syntaxVisualizer.SyntaxTriviaNavigationToSourceRequested += trivia => NavigateToSource(trivia.Span); }