コード例 #1
0
        private async Task <TextEdit[]> FormatOnServerAsync(
            FormattingContext context,
            Range projectedRange,
            CancellationToken cancellationToken)
        {
            var csharpSourceText = context.CodeDocument.GetCSharpSourceText();
            var spanToFormat     = projectedRange.AsTextSpan(csharpSourceText);
            var root             = await context.CSharpWorkspaceDocument.GetSyntaxRootAsync(cancellationToken);

            var workspace = context.CSharpWorkspace;

            // Formatting options will already be set in the workspace.
            var changes = CodeAnalysis.Formatting.Formatter.GetFormattedTextChanges(root, spanToFormat, workspace);

            var edits = changes.Select(c => c.AsTextEdit(csharpSourceText)).ToArray();

            return(edits);
        }
コード例 #2
0
        private bool TryMapFromProjectedDocumentRangeInclusive(RazorCodeDocument codeDocument, Range projectedRange, out Range originalRange)
        {
            originalRange = default;

            var csharpDoc            = codeDocument.GetCSharpDocument();
            var csharpSourceText     = SourceText.From(csharpDoc.GeneratedCode);
            var projectedRangeAsSpan = projectedRange.AsTextSpan(csharpSourceText);
            var range               = projectedRange;
            var startIndex          = projectedRangeAsSpan.Start;
            var startMappedDirectly = TryMapFromProjectedDocumentPosition(codeDocument, startIndex, out var hostDocumentStart, out _);

            var endIndex          = projectedRangeAsSpan.End;
            var endMappedDirectly = TryMapFromProjectedDocumentPosition(codeDocument, endIndex, out var hostDocumentEnd, out _);

            if (startMappedDirectly && endMappedDirectly)
            {
                // We strictly mapped the start/end of the projected range.
                originalRange = new Range(hostDocumentStart, hostDocumentEnd);
                return(true);
            }

            List <SourceMapping> candidateMappings;

            if (startMappedDirectly)
            {
                // Start of projected range intersects with a mapping
                candidateMappings = csharpDoc.SourceMappings.Where(mapping => IntersectsWith(startIndex, mapping.GeneratedSpan)).ToList();
            }
            else if (endMappedDirectly)
            {
                // End of projected range intersects with a mapping
                candidateMappings = csharpDoc.SourceMappings.Where(mapping => IntersectsWith(endIndex, mapping.GeneratedSpan)).ToList();
            }
            else
            {
                // Our range does not intersect with any mapping; we should see if it overlaps generated locations
                candidateMappings = csharpDoc.SourceMappings.Where(mapping => Overlaps(projectedRangeAsSpan, mapping.GeneratedSpan)).ToList();
            }

            if (candidateMappings.Count == 1)
            {
                // We're intersecting or overlapping a single mapping, lets choose that.

                var mapping = candidateMappings[0];
                originalRange = ConvertMapping(codeDocument.Source, mapping);
                return(true);
            }
            else
            {
                // More then 1 or exactly 0 intersecting/overlapping mappings
                return(false);
            }

            bool Overlaps(TextSpan projectedRangeAsSpan, SourceSpan span)
            {
                var overlapStart = Math.Max(projectedRangeAsSpan.Start, span.AbsoluteIndex);
                var overlapEnd   = Math.Min(projectedRangeAsSpan.End, span.AbsoluteIndex + span.Length);

                return(overlapStart < overlapEnd);
            }

            bool IntersectsWith(int position, SourceSpan span)
            {
                return(unchecked ((uint)(position - span.AbsoluteIndex) <= (uint)span.Length));
            }
コード例 #3
0
        //
        // 'minCSharpIndentLevel' refers to the minimum level of how much the C# formatter would indent code.
        // @code/@functions blocks contain class members and so are typically indented by 2 levels.
        // @{} blocks are put inside method body which means they are typically indented by 3 levels.
        //
        private SourceText ApplyCSharpEdits(FormattingContext context, Range codeBlockRange, TextEdit[] edits, int minCSharpIndentLevel)
        {
            var originalText          = context.SourceText;
            var originalCodeBlockSpan = codeBlockRange.AsTextSpan(originalText);

            // Sometimes the C# formatter edits outside the range we supply. Filter out those edits.
            var changes = edits.Select(e => e.AsTextChange(originalText)).Where(c => originalCodeBlockSpan.Contains(c.Span)).ToArray();

            if (changes.Length == 0)
            {
                return(originalText);
            }

            // Apply the C# edits to the document.
            var changedText = originalText.WithChanges(changes);

            TrackChangeInSpan(originalText, originalCodeBlockSpan, changedText, out var changedCodeBlockSpan, out var changeEncompassingSpan);

            // We now have the changed document with C# edits. But it might be indented more/less than what we want depending on the context.
            // So, we want to bring each line to the right level of indentation based on where the block is in the document.
            // We also need to only do this for the lines that are part of the input range to respect range formatting.
            var desiredIndentationLevel = context.Indentations[(int)codeBlockRange.Start.Line].IndentationLevel + 1;
            var editsToApply            = new List <TextChange>();
            var inputSpan = context.Range.AsTextSpan(originalText);

            TrackChangeInSpan(originalText, inputSpan, changedText, out var changedInputSpan, out _);
            var changedInputRange = changedInputSpan.AsRange(changedText);

            for (var i = (int)changedInputRange.Start.Line; i <= changedInputRange.End.Line; i++)
            {
                var line = changedText.Lines[i];
                if (line.Span.Length == 0)
                {
                    // Empty line. C# formatter didn't remove it so we won't either.
                    continue;
                }

                if (!changedCodeBlockSpan.Contains(line.Start))
                {
                    // Defensive check to make sure we're not handling lines that are not part of the current code block.
                    continue;
                }

                var leadingWhitespace     = line.GetLeadingWhitespace();
                var minCSharpIndentLength = context.GetIndentationLevelString(minCSharpIndentLevel).Length;
                if (leadingWhitespace.Length < minCSharpIndentLength)
                {
                    // For whatever reason, the C# formatter decided to not indent this. Leave it as is.
                    continue;
                }
                else
                {
                    // At this point we assume the C# formatter has relatively indented this line to the correct level.
                    // All we want to do at this point is to indent/unindent this line based on the absolute indentation of the block
                    // and the minimum C# indent level. We don't need to worry about the actual existing indentation here because it doesn't matter.
                    var effectiveDesiredIndentationLevel = desiredIndentationLevel - minCSharpIndentLevel;
                    var effectiveDesiredIndentation      = context.GetIndentationLevelString(Math.Abs(effectiveDesiredIndentationLevel));
                    if (effectiveDesiredIndentationLevel < 0)
                    {
                        // This means that we need to unindent.
                        var span = new TextSpan(line.Start, effectiveDesiredIndentation.Length);
                        editsToApply.Add(new TextChange(span, string.Empty));
                    }
                    else if (effectiveDesiredIndentationLevel > 0)
                    {
                        // This means that we need to indent.
                        var span = new TextSpan(line.Start, 0);
                        editsToApply.Add(new TextChange(span, effectiveDesiredIndentation));
                    }
                }
            }

            changedText = ApplyChanges(changedText, editsToApply);
            return(changedText);
        }