File: Workspace\VisualStudioFormattingRuleFactoryServiceFactory.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_pxr0p0dn_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// 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.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Composition;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.LanguageServices.Implementation.Venus;
using Microsoft.VisualStudio.Text.Projection;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation;
 
[ExportWorkspaceService(typeof(IHostDependentFormattingRuleFactoryService), ServiceLayer.Host), Shared]
internal sealed class VisualStudioFormattingRuleFactoryService : IHostDependentFormattingRuleFactoryService
{
    [ImportingConstructor]
    [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
    public VisualStudioFormattingRuleFactoryService()
    {
    }
    public bool ShouldUseBaseIndentation(DocumentId documentId)
        => IsContainedDocument(documentId);
 
    public bool ShouldNotFormatOrCommitOnPaste(DocumentId documentId)
        => IsContainedDocument(documentId);
 
    private static bool IsContainedDocument(DocumentId documentId)
        => ContainedDocument.TryGetContainedDocument(documentId) != null;
 
    public AbstractFormattingRule CreateRule(ParsedDocument document, int position)
    {
        var containedDocument = ContainedDocument.TryGetContainedDocument(document.Id);
        if (containedDocument == null)
        {
            return NoOpFormattingRule.Instance;
        }
 
        var textContainer = document.Text.Container;
        if (textContainer.TryGetTextBuffer() is not IProjectionBuffer)
        {
            return NoOpFormattingRule.Instance;
        }
 
        using var pooledObject = SharedPools.Default<List<TextSpan>>().GetPooledObject();
        var spans = pooledObject.Object;
 
        var root = document.Root;
        var text = document.Text;
 
        spans.AddRange(containedDocument.GetEditorVisibleSpans());
 
        for (var i = 0; i < spans.Count; i++)
        {
            var visibleSpan = spans[i];
            if (visibleSpan.IntersectsWith(position) || visibleSpan.End == position)
            {
                return containedDocument.GetBaseIndentationRule(root, text, spans, i);
            }
        }
 
        // in razor (especially in @helper tag), it is possible for us to be asked for next line of visible span
        var line = text.Lines.GetLineFromPosition(position);
        if (line.LineNumber > 0)
        {
            line = text.Lines[line.LineNumber - 1];
 
            // find one that intersects with previous line
            for (var i = 0; i < spans.Count; i++)
            {
                var visibleSpan = spans[i];
                if (visibleSpan.IntersectsWith(line.Span))
                {
                    return containedDocument.GetBaseIndentationRule(root, text, spans, i);
                }
            }
        }
 
        FatalError.ReportAndCatch(
            new InvalidOperationException($"Can't find an intersection. Visible spans count: {spans.Count}"));
 
        return NoOpFormattingRule.Instance;
    }
 
    public IEnumerable<TextChange> FilterFormattedChanges(DocumentId documentId, TextSpan span, IList<TextChange> changes)
    {
        var containedDocument = ContainedDocument.TryGetContainedDocument(documentId);
        if (containedDocument == null)
        {
            return changes;
        }
 
        // in case of a Venus, when format document command is issued, Venus will call format API with each script block spans.
        // in that case, we need to make sure formatter doesn't overstep other script blocks content. in actual format selection case,
        // we need to format more than given selection otherwise, we will not adjust indentation of first token of the given selection.
        foreach (var visibleSpan in containedDocument.GetEditorVisibleSpans())
        {
            if (visibleSpan != span)
            {
                continue;
            }
 
            return changes.Where(c => span.IntersectsWith(c.Span));
        }
 
        return changes;
    }
}