File: LineSeparators\LineSeparatorTaggerProvider.cs
Web Access
Project: src\src\EditorFeatures\Core.Wpf\Microsoft.CodeAnalysis.EditorFeatures.Wpf_ldwggcoo_wpftmp.csproj (Microsoft.CodeAnalysis.EditorFeatures.Wpf)
// 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;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Editor.Implementation.Tagging;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Tagging;
using Microsoft.CodeAnalysis.Editor.Tagging;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.LineSeparators;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Tagging;
using Microsoft.VisualStudio.Utilities;
 
namespace Microsoft.CodeAnalysis.Editor.Implementation.LineSeparators
{
    /// <summary>
    /// This factory is called to create taggers that provide information about where line
    /// separators go.
    /// </summary>
    [Export(typeof(ITaggerProvider))]
    [TagType(typeof(LineSeparatorTag))]
    [ContentType(ContentTypeNames.CSharpContentType)]
    [ContentType(ContentTypeNames.VisualBasicContentType)]
    internal sealed partial class LineSeparatorTaggerProvider : AsynchronousTaggerProvider<LineSeparatorTag>
    {
        private readonly IEditorFormatMap _editorFormatMap;
 
        protected sealed override ImmutableArray<IOption2> Options { get; } = ImmutableArray<IOption2>.Empty;
 
        private readonly object _lineSeparatorTagGate = new();
        private LineSeparatorTag _lineSeparatorTag;
 
        [ImportingConstructor]
        [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
        public LineSeparatorTaggerProvider(
            TaggerHost taggerHost,
            IEditorFormatMapService editorFormatMapService)
            : base(taggerHost, FeatureAttribute.LineSeparators)
        {
            _editorFormatMap = editorFormatMapService.GetEditorFormatMap("text");
            _editorFormatMap.FormatMappingChanged += OnFormatMappingChanged;
            _lineSeparatorTag = new LineSeparatorTag(_editorFormatMap);
        }
 
        protected override TaggerDelay EventChangeDelay => TaggerDelay.NearImmediate;
 
        private void OnFormatMappingChanged(object sender, FormatItemsEventArgs e)
        {
            lock (_lineSeparatorTagGate)
            {
                _lineSeparatorTag = new LineSeparatorTag(_editorFormatMap);
            }
        }
 
        protected override ITaggerEventSource CreateEventSource(
            ITextView? textView, ITextBuffer subjectBuffer)
        {
            return TaggerEventSources.Compose(
                new EditorFormatMapChangedEventSource(_editorFormatMap),
                TaggerEventSources.OnTextChanged(subjectBuffer));
        }
 
        protected override async Task ProduceTagsAsync(
            TaggerContext<LineSeparatorTag> context, DocumentSnapshotSpan documentSnapshotSpan, int? caretPosition, CancellationToken cancellationToken)
        {
            var document = documentSnapshotSpan.Document;
            if (document == null)
                return;
 
            if (!GlobalOptions.GetOption(LineSeparatorsOptionsStorage.LineSeparator, document.Project.Language))
                return;
 
            using (Logger.LogBlock(FunctionId.Tagger_LineSeparator_TagProducer_ProduceTags, cancellationToken))
            {
                var snapshotSpan = documentSnapshotSpan.SnapshotSpan;
                var lineSeparatorService = document.GetLanguageService<ILineSeparatorService>();
                if (lineSeparatorService == null)
                    return;
 
                var lineSeparatorSpans = await lineSeparatorService.GetLineSeparatorsAsync(document, snapshotSpan.Span.ToTextSpan(), cancellationToken).ConfigureAwait(false);
                cancellationToken.ThrowIfCancellationRequested();
 
                if (lineSeparatorSpans.Length == 0)
                    return;
 
                LineSeparatorTag tag;
                lock (_lineSeparatorTagGate)
                {
                    tag = _lineSeparatorTag;
                }
 
                foreach (var span in lineSeparatorSpans)
                    context.AddTag(new TagSpan<LineSeparatorTag>(span.ToSnapshotSpan(snapshotSpan.Snapshot), tag));
            }
        }
 
        /// <summary>
        /// We create and cache a separator tag to use (unless the format mapping changes).  So we can just use identity
        /// comparisons here.
        /// </summary>
        protected override bool TagEquals(LineSeparatorTag tag1, LineSeparatorTag tag2)
            => tag1 == tag2;
    }
}