| File: Text\TextLine.cs | Web Access |
| Project: src\roslyn\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis) |
// 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.Diagnostics; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Text { /// <summary> /// Information about the character boundaries of a single line of text. /// </summary> public readonly struct TextLine : IEquatable<TextLine> { private readonly SourceText? _text; // Encoding (64 bits total): // Bits 63-62 (2 bits): line break length at the end of this line (0 = no break / last line, // 1 = single-char break like \n, \r, etc., 2 = the \r\n Windows pair) // Bits 61-31 (31 bits): start position // Bits 30-0 (31 bits): total length including break = EndIncludingLineBreak - Start private readonly ulong _data; private const int BreakLenShift = 62; private const int StartShift = 31; private const ulong RawValueMask = 0x7FFFFFFFUL; // 31 bits, used for both start and length private TextLine(SourceText text, ulong data) { _text = text; _data = data; } private static ulong Pack(int start, int totalLength, int lineBreakLength) => ((ulong)(uint)lineBreakLength << BreakLenShift) | ((ulong)(uint)start << StartShift) | (uint)totalLength; /// <summary> /// Creates a <see cref="TextLine"/> instance. /// </summary> /// <param name="text">The source text.</param> /// <param name="span">The span of the line.</param> /// <returns>An instance of <see cref="TextLine"/>.</returns> /// <exception cref="ArgumentOutOfRangeException">The span does not represent a text line.</exception> public static TextLine FromSpan(SourceText text, TextSpan span) { if (text == null) { throw new ArgumentNullException(nameof(text)); } if (span.Start > text.Length || span.Start < 0 || span.End > text.Length) { throw new ArgumentOutOfRangeException(nameof(span)); } if (text.Length > 0) { // check span is start of line if (span.Start > 0 && !TextUtilities.IsAnyLineBreakCharacter(text[span.Start - 1])) { throw new ArgumentOutOfRangeException(nameof(span), CodeAnalysisResources.SpanDoesNotIncludeStartOfLine); } var lineBreakLen = 0; if (span.End > span.Start && TextUtilities.IsAnyLineBreakCharacter(text[span.End - 1])) { // End already includes line break - determine its length TextUtilities.GetStartAndLengthOfLineBreakEndingAt(text, span.End - 1, out _, out lineBreakLen); } else if (span.End < text.Length) { lineBreakLen = TextUtilities.GetLengthOfLineBreak(text, span.End); if (lineBreakLen > 0) { // adjust span to include line breaks span = new TextSpan(span.Start, span.Length + lineBreakLen); } } // check end of span is at end of line if (span.End < text.Length && lineBreakLen == 0) { throw new ArgumentOutOfRangeException(nameof(span), CodeAnalysisResources.SpanDoesNotIncludeEndOfLine); } return new TextLine(text, Pack(span.Start, span.Length, lineBreakLen)); } else { return new TextLine(text, Pack(0, 0, 0)); } } // Do not use unless you are certain the span you are passing in is valid! // This was added to allow SourceText.LineInfo's indexer to directly create TextLines // without the performance implications of calling FromSpan. internal static TextLine FromSpanUnsafe(SourceText text, TextSpan span, int lineBreakLength) { Debug.Assert(span.Start == 0 || TextUtilities.IsAnyLineBreakCharacter(text[span.Start - 1])); Debug.Assert(span.End == text.Length || TextUtilities.IsAnyLineBreakCharacter(text[span.End - 1])); Debug.Assert(lineBreakLength is >= 0 and <= 2); return new TextLine(text, Pack(span.Start, span.Length, lineBreakLength)); } /// <summary> /// Gets the source text. /// </summary> public SourceText? Text { get { return _text; } } /// <summary> /// Gets the zero-based line number. /// </summary> public int LineNumber { get { return _text?.Lines.IndexOf(Start) ?? 0; } } /// <summary> /// Gets the start position of the line. /// </summary> public int Start { get { return (int)((_data >>> StartShift) & RawValueMask); } } /// <summary> /// Gets the length of the line break sequence at the end of this line (0, 1, or 2). /// </summary> internal int LineBreakLength { get { return (int)(_data >>> BreakLenShift); } } /// <summary> /// Gets the end position of the line not including the line break. /// </summary> public int End { get { return EndIncludingLineBreak - LineBreakLength; } } /// <summary> /// Gets the end position of the line including the line break. /// </summary> public int EndIncludingLineBreak { get { return Start + (int)(_data & RawValueMask); } } /// <summary> /// Gets the line span not including the line break. /// </summary> public TextSpan Span { get { return TextSpan.FromBounds(this.Start, this.End); } } /// <summary> /// Gets the line span including the line break. /// </summary> public TextSpan SpanIncludingLineBreak { get { return TextSpan.FromBounds(this.Start, this.EndIncludingLineBreak); } } public override string ToString() { if (_text == null || _text.Length == 0) { return string.Empty; } else { return _text.ToString(this.Span); } } public static bool operator ==(TextLine left, TextLine right) { return left.Equals(right); } public static bool operator !=(TextLine left, TextLine right) { return !left.Equals(right); } public bool Equals(TextLine other) { return other._text == _text && other._data == _data; } public override bool Equals(object? obj) { if (obj is TextLine) { return Equals((TextLine)obj); } return false; } public override int GetHashCode() { return Hash.Combine(_text, Hash.Combine((int)_data, (int)(_data >>> 32))); } } }