IndentationHelper.cs 5.9 KB
Newer Older
M
Matt Warren 已提交
1 2 3 4 5
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
C
Carol Hu 已提交
6
using Microsoft.CodeAnalysis.Classification;
M
Matt Warren 已提交
7 8
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
M
Matt Warren 已提交
9 10 11

namespace Microsoft.CodeAnalysis.QuickInfo
{
M
Matt Warren 已提交
12 13
    // Reproduces logic in IProjectionBufferFactoryServiceExtensions (editor layer) 
    // Used for tests currenty, but probably needed for other non-vs-editor API consumers.
M
Matt Warren 已提交
14 15 16 17 18 19 20 21
    internal static class IndentationHelper
    {
        /// <summary>
        /// Recomputes span segments so that all text lines appear to have the same reduction in indentation.
        /// This operation is typically used to align text for display when the initial span does not include all of the first line's identation.
        /// This operation will potentially split spans that cover multiple lines into separate spans.
        /// </summary>
        /// <param name="text"></param>
C
Carol Hu 已提交
22
        /// <param name="classifiedSpans">The initial set of spans to align.</param>
M
Matt Warren 已提交
23 24
        /// <param name="tabSize">The number of spaces to </param>
        /// <returns></returns>
C
Carol Hu 已提交
25
        public static ImmutableArray<ClassifiedSpan> GetSpansWithAlignedIndentation(
M
Matt Warren 已提交
26
            SourceText text,
C
Carol Hu 已提交
27
            ImmutableArray<ClassifiedSpan> classifiedSpans,
M
Matt Warren 已提交
28 29
            int tabSize)
        {
C
Carol Hu 已提交
30
            if (!classifiedSpans.IsDefault && classifiedSpans.Length > 0)
M
Matt Warren 已提交
31
            {
M
Matt Warren 已提交
32
                // We need to figure out the shortest indentation level of the exposed lines.  We'll
M
Matt Warren 已提交
33
                // then remove that indentation from all lines.
C
Carol Hu 已提交
34
                var indentationColumn = DetermineIndentationColumn(text, classifiedSpans, tabSize);
M
Matt Warren 已提交
35

C
Carol Hu 已提交
36 37
                string spanClassificationType = null;
                var adjustedClassifiedSpans = new List<ClassifiedSpan>();
M
Matt Warren 已提交
38

C
Carol Hu 已提交
39
                for (var i = 0; i < classifiedSpans.Length; i++)
M
Matt Warren 已提交
40
                {
C
Carol Hu 已提交
41 42 43 44
                    var classifiedSpan = classifiedSpans[i];
                    spanClassificationType = classifiedSpan.ClassificationType;
                    var span = classifiedSpan.TextSpan;

M
Matt Warren 已提交
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
                    var startLineNumber = text.Lines.GetLineFromPosition(span.Start).LineNumber;
                    var endLineNumber = text.Lines.GetLineFromPosition(span.End).LineNumber;

                    for (var lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++)
                    {
                        var line = text.Lines[lineNumber];
                        var lineOffsetOfColumn = line.GetLineOffsetFromColumn(indentationColumn, tabSize);

                        var deletion = TextSpan.FromBounds(line.Start, line.Start + lineOffsetOfColumn);

                        if (deletion.Start > span.Start)
                        {
                            var spanBeforeDeletion = TextSpan.FromBounds(span.Start, Math.Min(span.End, deletion.Start));
                            if (spanBeforeDeletion.Length > 0)
                            {
C
Carol Hu 已提交
60
                                adjustedClassifiedSpans.Add(new ClassifiedSpan(spanClassificationType, spanBeforeDeletion));
M
Matt Warren 已提交
61 62 63 64 65 66 67 68 69 70 71
                            }
                        }

                        if (deletion.End > span.Start)
                        {
                            span = TextSpan.FromBounds(Math.Min(deletion.End, span.End), span.End);
                        }
                    }

                    if (span.Length > 0)
                    {
C
Carol Hu 已提交
72
                        adjustedClassifiedSpans.Add(new ClassifiedSpan(spanClassificationType, span));
M
Matt Warren 已提交
73 74 75
                    }
                }

C
Carol Hu 已提交
76
                return adjustedClassifiedSpans.ToImmutableArray();
M
Matt Warren 已提交
77 78 79
            }
            else
            {
C
Carol Hu 已提交
80
                return ImmutableArray<ClassifiedSpan>.Empty;
M
Matt Warren 已提交
81 82 83 84 85
            }
        }

        private static int DetermineIndentationColumn(
            SourceText text,
C
Carol Hu 已提交
86
            ImmutableArray<ClassifiedSpan> spans,
M
Matt Warren 已提交
87 88 89 90 91
            int tabSize)
        {
            int? indentationColumn = null;
            foreach (var span in spans)
            {
C
Carol Hu 已提交
92 93
                var startLineNumber = text.Lines.GetLineFromPosition(span.TextSpan.Start).LineNumber;
                var endLineNumber = text.Lines.GetLineFromPosition(span.TextSpan.End).LineNumber;
M
Matt Warren 已提交
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109

                // If the span starts after the first non-whitespace of the first line, we'll
                // exclude that line to avoid throwing off the calculation. Otherwise, the
                // incorrect indentation will be returned for lambda cases like so:
                //
                // void M()
                // {
                //     Func<int> f = () =>
                //         {
                //             return 1;
                //         };
                // }
                //
                // Without throwing out the first line in the example above, the indentation column
                // used will be 4, rather than 8.
                var startLineFirstNonWhitespace = text.Lines[startLineNumber].GetFirstNonWhitespacePosition();
C
Carol Hu 已提交
110
                if (startLineFirstNonWhitespace.HasValue && startLineFirstNonWhitespace.Value < span.TextSpan.Start)
M
Matt Warren 已提交
111 112 113 114 115 116 117
                {
                    startLineNumber++;
                }

                for (var lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++)
                {
                    var line = text.Lines[lineNumber];
118
                    if (line.IsEmptyOrWhitespace())
M
Matt Warren 已提交
119 120 121 122 123 124 125 126 127 128 129 130 131
                    {
                        continue;
                    }

                    indentationColumn = indentationColumn.HasValue
                        ? Math.Min(indentationColumn.Value, line.GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(tabSize))
                        : line.GetColumnOfFirstNonWhitespaceCharacterOrEndOfLine(tabSize);
                }
            }

            return indentationColumn ?? 0;
        }
    }
132
}