File: Classification\Semantic\AbstractSemanticOrEmbeddedClassificationViewTaggerProvider.cs
Web Access
Project: ..\..\..\src\EditorFeatures\Core\Microsoft.CodeAnalysis.EditorFeatures.csproj (Microsoft.CodeAnalysis.EditorFeatures)
// 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.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.Tagging;
using Microsoft.CodeAnalysis.LanguageServer;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Workspaces;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Tagging;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Classification
{
    /// <summary>
    /// This is the tagger we use for view classification scenarios.  It is used for classifying code
    /// in the editor.  We use a view tagger so that we can only classify what's in view, and not
    /// the whole file.
    /// </summary>
    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 sealed override TaggerTextChangeBehavior TextChangeBehavior => TaggerTextChangeBehavior.TrackTextChanges;
        protected sealed override ImmutableArray<IOption2> Options { get; } = ImmutableArray.Create<IOption2>(SemanticColorizerOptionsStorage.SemanticColorizer);
 
        protected AbstractSemanticOrEmbeddedClassificationViewTaggerProvider(
            IThreadingContext threadingContext,
            ClassificationTypeMap typeMap,
            IGlobalOptionService globalOptions,
            ITextBufferVisibilityTracker? visibilityTracker,
            IAsynchronousOperationListenerProvider listenerProvider,
            ClassificationType type)
            : base(threadingContext, globalOptions, visibilityTracker, listenerProvider.GetListener(FeatureAttribute.Classification))
        {
            _typeMap = typeMap;
            _globalOptions = globalOptions;
            _type = type;
        }
 
        protected sealed override TaggerDelay EventChangeDelay => TaggerDelay.Short;
 
        protected sealed override ITaggerEventSource CreateEventSource(ITextView? textView, ITextBuffer subjectBuffer)
        {
            this.ThreadingContext.ThrowIfNotOnUIThread();
            Contract.ThrowIfNull(textView);
 
            // Note: we don't listen for OnTextChanged.  They'll get reported by the ViewSpan changing and also the
            // SemanticChange notification. 
            // 
            // Note: because we use frozen-partial documents for semantic classification, we may end up with incomplete
            // semantics (esp. during solution load).  Because of this, we also register to hear when the full
            // compilation is available so that reclassify and bring ourselves up to date.
            return new CompilationAvailableTaggerEventSource(
                subjectBuffer,
                AsyncListener,
                TaggerEventSources.OnViewSpanChanged(ThreadingContext, textView),
                TaggerEventSources.OnWorkspaceChanged(subjectBuffer, AsyncListener),
                TaggerEventSources.OnDocumentActiveContextChanged(subjectBuffer),
                TaggerEventSources.OnGlobalOptionChanged(_globalOptions, ClassificationOptionsStorage.ClassifyReassignedVariables));
        }
 
        protected sealed override IEnumerable<SnapshotSpan> GetSpansToTag(ITextView? textView, ITextBuffer subjectBuffer)
        {
            this.ThreadingContext.ThrowIfNotOnUIThread();
            Contract.ThrowIfNull(textView);
 
            // Find the visible span some 100 lines +/- what's actually in view.  This way
            // if the user scrolls up/down, we'll already have the results.
            var visibleSpanOpt = textView.GetVisibleLinesSpan(subjectBuffer, extraLines: 100);
            if (visibleSpanOpt == null)
                // Couldn't find anything visible, just fall back to classifying everything.
                return base.GetSpansToTag(textView, subjectBuffer);
 
            return SpecializedCollections.SingletonEnumerable(visibleSpanOpt.Value);
        }
 
        protected sealed override Task ProduceTagsAsync(
            TaggerContext<IClassificationTag> context, CancellationToken cancellationToken)
        {
            Debug.Assert(context.SpansToTag.IsSingle());
 
            var spanToTag = context.SpansToTag.Single();
 
            var document = spanToTag.Document;
            if (document == null)
                return Task.CompletedTask;
 
            // Attempt to get a classification service which will actually produce the results.
            // If we can't (because we have no Document, or because the language doesn't support
            // this service), then bail out immediately.
            var classificationService = document.GetLanguageService<IClassificationService>();
            if (classificationService == null)
                return Task.CompletedTask;
 
            // The LSP client will handle producing tags when running under the LSP editor.
            // Our tagger implementation should return nothing to prevent conflicts.
            var workspaceContextService = document.Project.Solution.Services.GetRequiredService<IWorkspaceContextService>();
            if (workspaceContextService?.IsInLspEditorContext() == true)
                return Task.CompletedTask;
 
            // If the LSP semantic tokens feature flag is enabled, return nothing to prevent conflicts.
            var isLspSemanticTokensEnabled = _globalOptions.GetOption(LspOptionsStorage.LspSemanticTokensFeatureFlag);
            if (isLspSemanticTokensEnabled)
                return Task.CompletedTask;
 
            var classificationOptions = _globalOptions.GetClassificationOptions(document.Project.Language);
            return ClassificationUtilities.ProduceTagsAsync(
                context, spanToTag, classificationService, _typeMap, classificationOptions, _type, cancellationToken);
        }
 
        protected override bool TagEquals(IClassificationTag tag1, IClassificationTag tag2)
            => tag1.ClassificationType.Classification == tag2.ClassificationType.Classification;
    }
}