|
// 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.Collections.Immutable;
using System.IO;
using System.Text;
using System.Threading;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Shared.Extensions
{
internal static partial class SourceTextExtensions
{
public static void GetLineAndOffset(this SourceText text, int position, out int lineNumber, out int offset)
{
var line = text.Lines.GetLineFromPosition(position);
lineNumber = line.LineNumber;
offset = position - line.Start;
}
public static int GetOffset(this SourceText text, int position)
{
GetLineAndOffset(text, position, out _, out var offset);
return offset;
}
public static void GetLinesAndOffsets(
this SourceText text,
TextSpan textSpan,
out int startLineNumber,
out int startOffset,
out int endLineNumber,
out int endOffset)
{
text.GetLineAndOffset(textSpan.Start, out startLineNumber, out startOffset);
text.GetLineAndOffset(textSpan.End, out endLineNumber, out endOffset);
}
public static TextChangeRange GetEncompassingTextChangeRange(this SourceText newText, SourceText oldText)
{
var ranges = newText.GetChangeRanges(oldText);
if (ranges.Count == 0)
{
return default;
}
// simple case.
if (ranges.Count == 1)
{
return ranges[0];
}
return TextChangeRange.Collapse(ranges);
}
public static int IndexOf(this SourceText text, string value, int startIndex, bool caseSensitive)
{
var length = text.Length - value.Length;
var normalized = caseSensitive ? value : CaseInsensitiveComparison.ToLower(value);
for (var i = startIndex; i <= length; i++)
{
var match = true;
for (var j = 0; j < normalized.Length; j++)
{
// just use indexer of source text. perf of indexer depends on actual implementation of SourceText.
// * all of our implementation at editor layer should provide either O(1) or O(logn).
//
// only one implementation we have that could have bad indexer perf is CompositeText with heavily modified text
// at compiler layer but I believe that being used in find all reference will be very rare if not none.
if (!Match(normalized[j], text[i + j], caseSensitive))
{
match = false;
break;
}
}
if (match)
{
return i;
}
}
return -1;
}
public static int LastIndexOf(this SourceText text, string value, int startIndex, bool caseSensitive)
{
var normalized = caseSensitive ? value : CaseInsensitiveComparison.ToLower(value);
startIndex = startIndex + normalized.Length > text.Length
? text.Length - normalized.Length
: startIndex;
for (var i = startIndex; i >= 0; i--)
{
var match = true;
for (var j = 0; j < normalized.Length; j++)
{
// just use indexer of source text. perf of indexer depends on actual implementation of SourceText.
// * all of our implementation at editor layer should provide either O(1) or O(logn).
//
// only one implementation we have that could have bad indexer perf is CompositeText with heavily modified text
// at compiler layer but I believe that being used in find all reference will be very rare if not none.
if (!Match(normalized[j], text[i + j], caseSensitive))
{
match = false;
break;
}
}
if (match)
{
return i;
}
}
return -1;
}
private static bool Match(char normalizedLeft, char right, bool caseSensitive)
=> caseSensitive ? normalizedLeft == right : normalizedLeft == CaseInsensitiveComparison.ToLower(right);
public static bool ContentEquals(this SourceText text, int position, string value)
{
if (position + value.Length > text.Length)
{
return false;
}
for (var i = 0; i < value.Length; i++)
{
if (text[position + i] != value[i])
{
return false;
}
}
return true;
}
public static int IndexOfNonWhiteSpace(this SourceText text, int start, int length)
{
for (var i = 0; i < length; i++)
{
if (!char.IsWhiteSpace(text[start + i]))
{
return start + i;
}
}
return -1;
}
// 32KB. comes from SourceText char buffer size and less than large object size
internal const int SourceTextLengthThreshold = 32 * 1024 / sizeof(char);
public static void WriteTo(this SourceText sourceText, ObjectWriter writer, CancellationToken cancellationToken)
{
// Source length
var length = sourceText.Length;
writer.WriteInt32(length);
// if source is small, no point on optimizing. just write out string
if (length < SourceTextLengthThreshold)
{
writer.WriteString(sourceText.ToString());
}
else
{
// if bigger, write out as chunks
WriteChunksTo(sourceText, writer, length, cancellationToken);
}
}
private static void WriteChunksTo(SourceText sourceText, ObjectWriter writer, int length, CancellationToken cancellationToken)
{
// chunk size
var buffer = SharedPools.CharArray.Allocate();
writer.WriteInt32(buffer.Length);
// number of chunks
var numberOfChunks = 1 + (length / buffer.Length);
writer.WriteInt32(numberOfChunks);
// write whole chunks
try
{
var offset = 0;
for (var i = 0; i < numberOfChunks; i++)
{
cancellationToken.ThrowIfCancellationRequested();
var count = Math.Min(buffer.Length, length - offset);
if ((i < numberOfChunks - 1) || (count == buffer.Length))
{
// chunks before last chunk or last chunk match buffer size
sourceText.CopyTo(offset, buffer, 0, buffer.Length);
writer.WriteValue(buffer);
}
else if (i == numberOfChunks - 1)
{
// last chunk which size is not buffer size
var tempArray = new char[count];
sourceText.CopyTo(offset, tempArray, 0, tempArray.Length);
writer.WriteValue(tempArray);
}
offset += count;
}
Contract.ThrowIfFalse(offset == length);
}
finally
{
SharedPools.CharArray.Free(buffer);
}
}
public static SourceText ReadFrom(ITextFactoryService textService, ObjectReader reader, Encoding? encoding, SourceHashAlgorithm checksumAlgorithm, CancellationToken cancellationToken)
{
using var textReader = ObjectReaderTextReader.Create(reader);
return textService.CreateText(textReader, encoding, checksumAlgorithm, cancellationToken);
}
private class ObjectReaderTextReader : TextReaderWithLength
{
private readonly ImmutableArray<char[]> _chunks;
private readonly int _chunkSize;
private int _position;
public static TextReader Create(ObjectReader reader)
{
var length = reader.ReadInt32();
if (length < SourceTextLengthThreshold)
{
// small size, read as string
return new StringReader(reader.ReadString());
}
// read as chunks
var builder = ImmutableArray.CreateBuilder<char[]>();
var chunkSize = reader.ReadInt32();
var numberOfChunks = reader.ReadInt32();
var offset = 0;
for (var i = 0; i < numberOfChunks; i++)
{
var currentLine = (char[])reader.ReadValue();
builder.Add(currentLine);
offset += currentLine.Length;
}
Contract.ThrowIfFalse(offset == length);
return new ObjectReaderTextReader(builder.ToImmutable(), chunkSize, length);
}
private ObjectReaderTextReader(ImmutableArray<char[]> chunks, int chunkSize, int length)
: base(length)
{
_chunks = chunks;
_chunkSize = chunkSize;
}
public override int Peek()
{
if (_position >= Length)
{
return -1;
}
return Read(_position);
}
public override int Read()
{
if (_position >= Length)
{
return -1;
}
return Read(_position++);
}
public override int Read(char[] buffer, int index, int count)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if (index < 0 || index >= buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(index));
}
if (count < 0 || (index + count) > buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(count));
}
// check quick bail out
if (count == 0)
{
return 0;
}
// adjust to actual char to read
var totalCharsToRead = Math.Min(count, Length - _position);
count = totalCharsToRead;
var chunkIndex = GetIndexFromPosition(_position);
var chunkStartOffset = GetColumnFromPosition(_position);
while (true)
{
var chunk = _chunks[chunkIndex];
var charsToCopy = Math.Min(chunk.Length - chunkStartOffset, count);
Array.Copy(chunk, chunkStartOffset, buffer, index, charsToCopy);
count -= charsToCopy;
if (count <= 0)
{
break;
}
index += charsToCopy;
chunkStartOffset = 0;
chunkIndex++;
}
_position += totalCharsToRead;
return totalCharsToRead;
}
private int Read(int position)
{
var chunkIndex = GetIndexFromPosition(position);
var chunkColumn = GetColumnFromPosition(position);
return _chunks[chunkIndex][chunkColumn];
}
private int GetIndexFromPosition(int position) => position / _chunkSize;
private int GetColumnFromPosition(int position) => position % _chunkSize;
}
}
}
|