/// <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 ); } bool endIncludesLineBreak = false; if (span.End > span.Start) { endIncludesLineBreak = TextUtilities.IsAnyLineBreakCharacter( text[span.End - 1] ); } if (!endIncludesLineBreak && span.End < text.Length) { var lineBreakLen = TextUtilities.GetLengthOfLineBreak(text, span.End); if (lineBreakLen > 0) { // adjust span to include line breaks endIncludesLineBreak = true; span = new TextSpan(span.Start, span.Length + lineBreakLen); } } // check end of span is at end of line if (span.End < text.Length && !endIncludesLineBreak) { throw new ArgumentOutOfRangeException( nameof(span), CodeAnalysisResources.SpanDoesNotIncludeEndOfLine ); } return(new TextLine(text, span.Start, span.End)); } else { return(new TextLine(text, 0, 0)); } }
private int[] ParseLineStarts() { int length = this.Length; // Corner case check if (0 == this.Length) { return(new[] { 0 }); } var position = 0; var index = 0; var arrayBuilder = ArrayBuilder <int> .GetInstance(); // The following loop goes through every character in the text. It is highly // performance critical, and thus inlines knowledge about common line breaks // and non-line breaks. while (index < length) { char c = this[index++]; // Common case - ASCII & not a line break // if (c > '\r' && c <= 127) // if (c >= ('\r'+1) && c <= 127) const uint bias = '\r' + 1; if (unchecked (c - bias) <= (127 - bias)) { continue; } // Assumes that the only 2-char line break sequence is CR+LF if (c == '\r' && index < length && this[index] == '\n') { index++; } else if (!TextUtilities.IsAnyLineBreakCharacter(c)) { continue; } arrayBuilder.Add(position); position = index; } // Create a start for the final line. arrayBuilder.Add(position); return(arrayBuilder.ToArrayAndFree()); }
private int[] ParseLineStarts() { // Corner case check if (0 == this.Length) { return(new[] { 0 }); } var lineStarts = ArrayBuilder <int> .GetInstance(); lineStarts.Add(0); // there is always the first line var lastWasCR = false; // The following loop goes through every character in the text. It is highly // performance critical, and thus inlines knowledge about common line breaks // and non-line breaks. EnumerateChars((int position, char[] buffer, int length) => { var index = 0; if (lastWasCR) { if (length > 0 && buffer[0] == '\n') { index++; } lineStarts.Add(position + index); lastWasCR = false; } while (index < length) { char c = buffer[index]; index++; // Common case - ASCII & not a line break // if (c > '\r' && c <= 127) // if (c >= ('\r'+1) && c <= 127) const uint bias = '\r' + 1; if (unchecked (c - bias) <= (127 - bias)) { continue; } // Assumes that the only 2-char line break sequence is CR+LF if (c == '\r') { if (index < length && buffer[index] == '\n') { index++; } else if (index >= length) { lastWasCR = true; continue; } } else if (!TextUtilities.IsAnyLineBreakCharacter(c)) { continue; } // next line starts at index lineStarts.Add(position + index); } }); return(lineStarts.ToArrayAndFree()); }
protected override TextLineCollection GetLinesCore() { var oldLineInfo = _oldText.Lines; var lineStarts = ArrayBuilder <int> .GetInstance(); lineStarts.Add(0); // position in the original document var position = 0; // delta generated by already processed changes (position in the new document = position + delta) var delta = 0; // true if last segment ends with CR and we need to check for CR+LF code below assumes that both CR and LF are also line breaks alone var endsWithCR = false; foreach (var change in _changes) { // change.Span.Start < position already ruled out by SourceText.WithChanges // if we've skipped a range, add if (change.Span.Start > position) { if (endsWithCR && _newText[position + delta] == '\n') { lineStarts.RemoveLast(); } var lps = oldLineInfo.GetLinePositionSpan(TextSpan.FromBounds(position, change.Span.Start)); for (int i = lps.Start.Line + 1; i <= lps.End.Line; i++) { lineStarts.Add(oldLineInfo[i].Start + delta); } endsWithCR = _oldText[change.Span.Start - 1] == '\r'; // in case change is inserted between CR+LF we treat CR as line break alone, but this line break might be retracted and replaced with new one in case LF is inserted if (endsWithCR && change.Span.Start < _oldText.Length && _oldText[change.Span.Start] == '\n') { lineStarts.Add(change.Span.Start + delta); } } if (change.NewLength > 0) { var text = GetSubText(new TextSpan(change.Span.Start + delta, change.NewLength)); // optimizations copied from SourceText.LineInfo.ParseLineStarts var index = 0; while (index < text.Length) { char c = text[index++]; // Common case - ASCII & not a line break // if (c > '\r' && c <= 127) // if (c >= ('\r'+1) && c <= 127) const uint bias = '\r' + 1; if (unchecked (c - bias) <= (127 - bias)) { continue; } if (endsWithCR && c == '\n') { lineStarts.RemoveLast(); } else if (c == '\r' && index < text.Length && text[index] == '\n') { index++; } else if (!TextUtilities.IsAnyLineBreakCharacter(c)) { continue; } lineStarts.Add(change.Span.Start + delta + index); } endsWithCR = text[change.NewLength - 1] == '\r'; } position = change.Span.End; delta += (change.NewLength - change.Span.Length); } if (position < _oldText.Length) { if (endsWithCR && _newText[position + delta] == '\n') { lineStarts.RemoveLast(); } var lps = oldLineInfo.GetLinePositionSpan(TextSpan.FromBounds(position, _oldText.Length)); for (int i = lps.Start.Line + 1; i <= lps.End.Line; i++) { lineStarts.Add(oldLineInfo[i].Start + delta); } } return(new LineInfo(this, lineStarts.ToArrayAndFree())); }