public IWhitespaceManager GetOrCreateWhitespaceManager( ITextBuffer buffer, NewlineState initialNewlineState, LeadingWhitespaceState initialLeadingWhitespaceState) { return(buffer.Properties.GetOrCreateSingletonProperty( typeof(IWhitespaceManager), () => new WhitespaceManager(buffer, initialNewlineState, initialLeadingWhitespaceState))); }
/// <summary> /// Normalizes the given buffer to match the given newline state's line endings if they are consistent. /// </summary> /// <returns>True if the buffer was changed. False otherwise.</returns> public static bool NormalizeNewlines(this NewlineState newlineState, ITextBuffer buffer) { // Keep this method in sync with the other NormalizeNewlines overload below. if (!newlineState.HasConsistentLineEndings || !newlineState.InferredLineEnding.HasValue) { // Right now we expect people to overwhelmingly start from project templates, item templates, or cloned code, // which will give them at least one newline in a given document. If they don't then we're taking the easy // route here, to not do anything. That could be improved upon, but we're waiting for user feedback to justify // further work in this area. return(false); } /* Potential optimization, check to see if text contains any newlines that would be replaced, and if not, just return text and avoid allocations */ string newlineString = newlineState.InferredLineEnding.Value.StringFromLineEnding(); return(buffer.NormalizeNewlines(newlineString)); }
public static string NormalizeNewlines(this NewlineState newlineState, string text) { // Keep this method in sync with the other NormalizeNewlines overload above if (!newlineState.HasConsistentLineEndings || !newlineState.InferredLineEnding.HasValue) { // Right now we expect people to overwhelmingly start from project templates, item templates, or cloned code, // which will give them at least one newline in a given document. If they don't then we're taking the easy // route here, to not do anything. That could be improved upon, but we're waiting for user feedback to justify // further work in this area. return(text); } /* Potential optimization, check to see if text contains any newlines that would be replaced, and if not, just return text and avoid allocations */ string newlineString = newlineState.InferredLineEnding.Value.StringFromLineEnding(); // In the perverse case, where we have a string full of "\n\n\n\n\n\n" and the document wants \r\n, we can only ever double the size of the string. var strBuilder = new StringBuilder(text.Length * 2); for (int i = 0; i < text.Length; i++) { switch (text[i]) { case '\r': if (i < (text.Length - 1) && text[i + 1] == '\n') { i++; } goto case '\n'; case '\n': case '\u0085': case '\u2028': case '\u2029': strBuilder.Append(newlineString); break; default: strBuilder.Append(text[i]); break; } } return(strBuilder.ToString()); }
public WhitespaceManager(ITextBuffer documentBuffer, NewlineState newlineState, LeadingWhitespaceState leadingWhitespaceState) { documentBuffer.Changed += this.OnDocumentBufferChanged; NewlineState = newlineState; LeadingWhitespaceState = leadingWhitespaceState; }
internal static StringRebuilder Load(TextReader reader, long fileSize, out NewlineState newlineState, out LeadingWhitespaceState leadingWhitespaceState, out int longestLineLength, int blockSize = 0, int minCompressedBlockSize = TextImageLoader.BlockSize, // Exposed for unit tests bool throwOnInvalidCharacters = false) { newlineState = new NewlineState(); leadingWhitespaceState = new LeadingWhitespaceState(); int currentLineLength = 0; longestLineLength = 0; char thresholdForInvalidCharacters = throwOnInvalidCharacters ? '\u0001' : '\0'; // Basically the only invalid character is \0, if we are looking for invalid characters. bool useCompressedStringRebuilders = (fileSize >= TextModelOptions.CompressedStorageFileSizeThreshold); if (blockSize == 0) { blockSize = useCompressedStringRebuilders ? TextModelOptions.CompressedStoragePageSize : TextImageLoader.BlockSize; } PageManager pageManager = null; char[] buffer; if (useCompressedStringRebuilders) { pageManager = new PageManager(); buffer = new char[blockSize]; } else { buffer = TextImageLoader.AcquireBuffer(blockSize); } StringRebuilder content = StringRebuilderForChars.Empty; try { bool nextCharIsStartOfLine = true; while (true) { int read = TextImageLoader.LoadNextBlock(reader, buffer); if (read == 0) { break; } var lineBreaks = TextImageLoader.ParseBlock( buffer, read, thresholdForInvalidCharacters, ref newlineState, ref leadingWhitespaceState, ref currentLineLength, ref longestLineLength, ref nextCharIsStartOfLine); char[] bufferForStringBuilder = buffer; if (read < (buffer.Length / 2)) { // We read far less characters than buffer so copy the contents to a new buffer and reuse the original buffer. bufferForStringBuilder = new char[read]; Array.Copy(buffer, bufferForStringBuilder, read); } else { // We're using most of buffer so allocate a new block for the next chunk. buffer = new char[blockSize]; } var newContent = (useCompressedStringRebuilders && (read > minCompressedBlockSize)) ? StringRebuilderForCompressedChars.Create(new Page(pageManager, bufferForStringBuilder, read), lineBreaks) : StringRebuilderForChars.Create(bufferForStringBuilder, read, lineBreaks); content = content.Insert(content.Length, newContent); } longestLineLength = Math.Max(longestLineLength, currentLineLength); } finally { if (!useCompressedStringRebuilders) { TextImageLoader.ReleaseBuffer(buffer); } } return(content); }
// Evil performance hack (but we are on a hot path here): // thresholdForInvalidCharacters should be '\u0001' if we are throwing on invalid characters. // should be '\0' if we are not. // (otherwise we need to check both a throwOnInvalidCharacters boolean and that c == 0). private static ILineBreaks ParseBlock(char[] buffer, int length, char thresholdForInvalidCharacters, ref NewlineState newlineState, ref LeadingWhitespaceState leadingWhitespaceState, ref int currentLineLength, ref int longestLineLength, ref bool nextCharIsStartOfLine) { // Note that the lineBreaks created here will (internally) use the pooled list of line breaks. IPooledLineBreaksEditor lineBreaks = LineBreakManager.CreatePooledLineBreakEditor(length); int index = 0; while (index < length) { int breakLength = TextUtilities.LengthOfLineBreak(buffer, index, length); if (breakLength == 0) { char c = buffer[index]; // If we are checking for invalid characters, throw if we encounter a \0 if (c < thresholdForInvalidCharacters) { throw new FileFormatException("File contains NUL characters"); } ++currentLineLength; ++index; if (nextCharIsStartOfLine) { switch (c) { case ' ': leadingWhitespaceState.Increment(LeadingWhitespaceState.LineLeadingCharacter.Space, 1); break; case '\t': leadingWhitespaceState.Increment(LeadingWhitespaceState.LineLeadingCharacter.Tab, 1); break; default: leadingWhitespaceState.Increment(LeadingWhitespaceState.LineLeadingCharacter.Printable, 1); break; } nextCharIsStartOfLine = false; } } else { lineBreaks.Add(index, breakLength); longestLineLength = Math.Max(longestLineLength, currentLineLength); currentLineLength = 0; if (breakLength == 2) { newlineState.Increment(NewlineState.LineEnding.CRLF, 1); } else { switch (buffer[index]) { // This code needs to be kep consistent with TextUtilities.LengthOfLineBreak() case '\r': newlineState.Increment(NewlineState.LineEnding.CR, 1); break; case '\n': newlineState.Increment(NewlineState.LineEnding.LF, 1); break; case '\u0085': newlineState.Increment(NewlineState.LineEnding.NEL, 1); break; case '\u2028': newlineState.Increment(NewlineState.LineEnding.LS, 1); break; case '\u2029': newlineState.Increment(NewlineState.LineEnding.PS, 1); break; default: throw new InvalidOperationException("Unexpected line ending"); } } if (nextCharIsStartOfLine) { leadingWhitespaceState.Increment(LeadingWhitespaceState.LineLeadingCharacter.Empty, 1); } nextCharIsStartOfLine = true; } index += breakLength; } lineBreaks.ReleasePooledLineBreaks(); return(lineBreaks); }