Skip to content

Commit

Permalink
Merge pull request #59708 from CyrusNajmabadi/embeddedClassification
Browse files Browse the repository at this point in the history
Break out embedded lang classification from semantic classification.
  • Loading branch information
CyrusNajmabadi authored Feb 24, 2022
2 parents f0bc164 + dcbfe28 commit 67a81ec
Show file tree
Hide file tree
Showing 34 changed files with 551 additions and 257 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,15 @@ public IEnumerable<ITagSpan<IClassificationTag>> GetAllTags(NormalizedSnapshotSp
var context = new TaggerContext<IClassificationTag>(document, snapshot);
var options = _globalOptions.GetClassificationOptions(document.Project.Language);

ThreadingContext.JoinableTaskFactory.Run(
() => SemanticClassificationUtilities.ProduceTagsAsync(context, new DocumentSnapshotSpan(document, spanToTag), classificationService, _owner._typeMap, options, cancellationToken));
ThreadingContext.JoinableTaskFactory.Run(async () =>
{
var snapshotSpan = new DocumentSnapshotSpan(document, spanToTag);
// When copying/pasting, ensure we have classifications fully computed for the requested spans
// for both semantic classifications and embedded lang classifications.
await ProduceTagsAsync(context, snapshotSpan, classificationService, options, ClassificationType.Semantic, cancellationToken).ConfigureAwait(false);
await ProduceTagsAsync(context, snapshotSpan, classificationService, options, ClassificationType.EmbeddedLanguage, cancellationToken).ConfigureAwait(false);
});

cachedTaggedSpan = spanToTag;
cachedTags = new TagSpanIntervalTree<IClassificationTag>(snapshot.TextBuffer, SpanTrackingMode.EdgeExclusive, context.tagSpans);
Expand All @@ -153,6 +160,14 @@ public IEnumerable<ITagSpan<IClassificationTag>> GetAllTags(NormalizedSnapshotSp
: cachedTags.GetIntersectingTagSpans(spans);
}

private Task ProduceTagsAsync(
TaggerContext<IClassificationTag> context, DocumentSnapshotSpan snapshotSpan,
IClassificationService classificationService, ClassificationOptions options, ClassificationType type, CancellationToken cancellationToken)
{
return ClassificationUtilities.ProduceTagsAsync(
context, snapshotSpan, classificationService, _owner._typeMap, options, type, cancellationToken);
}

private void GetCachedInfo(out SnapshotSpan? cachedTaggedSpan, out TagSpanIntervalTree<IClassificationTag>? cachedTags)
{
lock (_gate)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
Expand All @@ -22,7 +20,6 @@
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Editor.Implementation.Classification
Expand All @@ -32,35 +29,33 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Classification
/// in the editor. We use a view tagger so that we can only classify what's in view, and not
/// the whole file.
/// </summary>
[Export(typeof(IViewTaggerProvider))]
[TagType(typeof(IClassificationTag))]
[ContentType(ContentTypeNames.RoslynContentType)]
internal partial class SemanticClassificationViewTaggerProvider : AsynchronousViewTaggerProvider<IClassificationTag>
internal abstract class AbstractSemanticOrEmbeddedClassificationViewTaggerProvider : AsynchronousViewTaggerProvider<IClassificationTag>
{
private readonly ClassificationTypeMap _typeMap;
private readonly IGlobalOptionService _globalOptions;
private readonly ClassificationType _type;

// We want to track text changes so that we can try to only reclassify a method body if
// all edits were contained within one.
protected override TaggerTextChangeBehavior TextChangeBehavior => TaggerTextChangeBehavior.TrackTextChanges;
protected override IEnumerable<Option2<bool>> Options => SpecializedCollections.SingletonEnumerable(InternalFeatureOnOffOptions.SemanticColorizer);
protected sealed override TaggerTextChangeBehavior TextChangeBehavior => TaggerTextChangeBehavior.TrackTextChanges;
protected sealed override IEnumerable<Option2<bool>> Options => SpecializedCollections.SingletonEnumerable(InternalFeatureOnOffOptions.SemanticColorizer);

[ImportingConstructor]
[SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
public SemanticClassificationViewTaggerProvider(
protected AbstractSemanticOrEmbeddedClassificationViewTaggerProvider(
IThreadingContext threadingContext,
ClassificationTypeMap typeMap,
IGlobalOptionService globalOptions,
IAsynchronousOperationListenerProvider listenerProvider)
IAsynchronousOperationListenerProvider listenerProvider,
ClassificationType type)
: base(threadingContext, globalOptions, listenerProvider.GetListener(FeatureAttribute.Classification))
{
_typeMap = typeMap;
_globalOptions = globalOptions;
_type = type;
}

protected override TaggerDelay EventChangeDelay => TaggerDelay.Short;
protected sealed override TaggerDelay EventChangeDelay => TaggerDelay.Short;

protected override ITaggerEventSource CreateEventSource(ITextView textView, ITextBuffer subjectBuffer)
protected sealed override ITaggerEventSource CreateEventSource(ITextView textView, ITextBuffer subjectBuffer)
{
this.AssertIsForeground();

Expand All @@ -79,7 +74,7 @@ protected override ITaggerEventSource CreateEventSource(ITextView textView, ITex
TaggerEventSources.OnOptionChanged(subjectBuffer, ClassificationOptionsStorage.ClassifyReassignedVariables));
}

protected override IEnumerable<SnapshotSpan> GetSpansToTag(ITextView textView, ITextBuffer subjectBuffer)
protected sealed override IEnumerable<SnapshotSpan> GetSpansToTag(ITextView textView, ITextBuffer subjectBuffer)
{
this.AssertIsForeground();

Expand All @@ -95,7 +90,7 @@ protected override IEnumerable<SnapshotSpan> GetSpansToTag(ITextView textView, I
return SpecializedCollections.SingletonEnumerable(visibleSpanOpt.Value);
}

protected override Task ProduceTagsAsync(
protected sealed override Task ProduceTagsAsync(
TaggerContext<IClassificationTag> context, CancellationToken cancellationToken)
{
Debug.Assert(context.SpansToTag.IsSingle());
Expand Down Expand Up @@ -127,8 +122,8 @@ protected override Task ProduceTagsAsync(
}

var classificationOptions = _globalOptions.GetClassificationOptions(document.Project.Language);
return SemanticClassificationUtilities.ProduceTagsAsync(
context, spanToTag, classificationService, _typeMap, classificationOptions, cancellationToken);
return ClassificationUtilities.ProduceTagsAsync(
context, spanToTag, classificationService, _typeMap, classificationOptions, _type, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Classification;
Expand All @@ -17,7 +17,6 @@
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Storage;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.Text;
Expand All @@ -26,14 +25,31 @@

namespace Microsoft.CodeAnalysis.Editor.Implementation.Classification
{
internal static class SemanticClassificationUtilities
internal static class ClassificationUtilities
{
public static TagSpan<IClassificationTag> Convert(IClassificationTypeMap typeMap, ITextSnapshot snapshot, ClassifiedSpan classifiedSpan)
{
return new TagSpan<IClassificationTag>(
classifiedSpan.TextSpan.ToSnapshotSpan(snapshot),
new ClassificationTag(typeMap.GetClassificationType(classifiedSpan.ClassificationType)));
}

public static List<ITagSpan<IClassificationTag>> Convert(IClassificationTypeMap typeMap, ITextSnapshot snapshot, ArrayBuilder<ClassifiedSpan> classifiedSpans)
{
var result = new List<ITagSpan<IClassificationTag>>(capacity: classifiedSpans.Count);
foreach (var span in classifiedSpans)
result.Add(Convert(typeMap, snapshot, span));

return result;
}

public static async Task ProduceTagsAsync(
TaggerContext<IClassificationTag> context,
DocumentSnapshotSpan spanToTag,
IClassificationService classificationService,
ClassificationTypeMap typeMap,
ClassificationOptions options,
ClassificationType type,
CancellationToken cancellationToken)
{
var document = spanToTag.Document;
Expand All @@ -51,10 +67,9 @@ public static async Task ProduceTagsAsync(
// as we do not have to wait on skeletons to be built.

document = document.WithFrozenPartialSemantics(cancellationToken);
spanToTag = new DocumentSnapshotSpan(document, spanToTag.SnapshotSpan);

var classified = await TryClassifyContainingMemberSpanAsync(
context, spanToTag, classificationService, typeMap, options, cancellationToken).ConfigureAwait(false);
context, document, spanToTag.SnapshotSpan, classificationService, typeMap, options, type, cancellationToken).ConfigureAwait(false);
if (classified)
{
return;
Expand All @@ -63,15 +78,17 @@ public static async Task ProduceTagsAsync(
// We weren't able to use our specialized codepaths for semantic classifying.
// Fall back to classifying the full span that was asked for.
await ClassifySpansAsync(
context, spanToTag, classificationService, typeMap, options, cancellationToken).ConfigureAwait(false);
context, document, spanToTag.SnapshotSpan, classificationService, typeMap, options, type, cancellationToken).ConfigureAwait(false);
}

private static async Task<bool> TryClassifyContainingMemberSpanAsync(
TaggerContext<IClassificationTag> context,
DocumentSnapshotSpan spanToTag,
Document document,
SnapshotSpan snapshotSpan,
IClassificationService classificationService,
ClassificationTypeMap typeMap,
ClassificationOptions options,
ClassificationType type,
CancellationToken cancellationToken)
{
var range = context.TextChangeRange;
Expand All @@ -82,11 +99,8 @@ private static async Task<bool> TryClassifyContainingMemberSpanAsync(
}

// there was top level edit, check whether that edit updated top level element
var document = spanToTag.Document;
if (!document.SupportsSyntaxTree)
{
return false;
}

var lastSemanticVersion = (VersionStamp?)context.State;
if (lastSemanticVersion != null)
Expand All @@ -99,13 +113,13 @@ private static async Task<bool> TryClassifyContainingMemberSpanAsync(
}
}

var service = document.GetLanguageService<ISyntaxFactsService>();
var service = document.GetRequiredLanguageService<ISyntaxFactsService>();

// perf optimization. Check whether all edits since the last update has happened within
// a member. If it did, it will find the member that contains the changes and only refresh
// that member. If possible, try to get a speculative binder to make things even cheaper.

var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

var changedSpan = new TextSpan(range.Value.Span.Start, range.Value.NewLength);
var member = service.GetContainingMemberDeclaration(root, changedSpan.Start);
Expand All @@ -122,49 +136,42 @@ private static async Task<bool> TryClassifyContainingMemberSpanAsync(
return false;
}

var subSpan = subTextSpan.Contains(changedSpan) ? subTextSpan.ToSpan() : member.FullSpan.ToSpan();

var subSpanToTag = new DocumentSnapshotSpan(spanToTag.Document,
new SnapshotSpan(spanToTag.SnapshotSpan.Snapshot, subSpan));
var subSpanToTag = new SnapshotSpan(
snapshotSpan.Snapshot,
subTextSpan.Contains(changedSpan) ? subTextSpan.ToSpan() : member.FullSpan.ToSpan());

// re-classify only the member we're inside.
await ClassifySpansAsync(
context, subSpanToTag, classificationService, typeMap, options, cancellationToken).ConfigureAwait(false);
context, document, subSpanToTag, classificationService, typeMap, options, type, cancellationToken).ConfigureAwait(false);
return true;
}

private static async Task ClassifySpansAsync(
TaggerContext<IClassificationTag> context,
DocumentSnapshotSpan spanToTag,
Document document,
SnapshotSpan snapshotSpan,
IClassificationService classificationService,
ClassificationTypeMap typeMap,
ClassificationOptions options,
ClassificationType type,
CancellationToken cancellationToken)
{
try
{
var document = spanToTag.Document;
var snapshotSpan = spanToTag.SnapshotSpan;
var snapshot = snapshotSpan.Snapshot;

using (Logger.LogBlock(FunctionId.Tagger_SemanticClassification_TagProducer_ProduceTags, cancellationToken))
{
using var _ = ArrayBuilder<ClassifiedSpan>.GetInstance(out var classifiedSpans);

await classificationService.AddSemanticClassificationsAsync(
document,
snapshotSpan.Span.ToTextSpan(),
options,
classifiedSpans,
cancellationToken).ConfigureAwait(false);
await AddClassificationsAsync(
classificationService, options, document, snapshotSpan, classifiedSpans, type, cancellationToken).ConfigureAwait(false);

foreach (var span in classifiedSpans)
context.AddTag(ClassificationUtilities.Convert(typeMap, snapshotSpan.Snapshot, span));
context.AddTag(Convert(typeMap, snapshotSpan.Snapshot, span));

var version = await document.Project.GetDependentSemanticVersionAsync(cancellationToken).ConfigureAwait(false);

// Let the context know that this was the span we actually tried to tag.
context.SetSpansTagged(SpecializedCollections.SingletonEnumerable(spanToTag));
context.SetSpansTagged(ImmutableArray.Create(snapshotSpan));
context.State = version;
}
}
Expand All @@ -173,5 +180,30 @@ await classificationService.AddSemanticClassificationsAsync(
throw ExceptionUtilities.Unreachable;
}
}

private static async Task AddClassificationsAsync(
IClassificationService classificationService,
ClassificationOptions options,
Document document,
SnapshotSpan snapshotSpan,
ArrayBuilder<ClassifiedSpan> classifiedSpans,
ClassificationType type,
CancellationToken cancellationToken)
{
if (type == ClassificationType.Semantic)
{
await classificationService.AddSemanticClassificationsAsync(
document, snapshotSpan.Span.ToTextSpan(), options, classifiedSpans, cancellationToken).ConfigureAwait(false);
}
else if (type == ClassificationType.EmbeddedLanguage)
{
await classificationService.AddEmbeddedLanguageClassificationsAsync(
document, snapshotSpan.Span.ToTextSpan(), options, classifiedSpans, cancellationToken).ConfigureAwait(false);
}
else
{
throw ExceptionUtilities.UnexpectedValue(type);
}
}
}
}
Loading

0 comments on commit 67a81ec

Please sign in to comment.