| File: IndentedTextWriter.cs | Web Access |
| Project: Microsoft.Interop.SourceGeneration.csproj (Microsoft.Interop.SourceGeneration) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Runtime.CompilerServices; using System.Text; namespace Microsoft.Interop; /// <summary>Writes indented source text without constructing a syntax tree.</summary> public sealed class IndentedTextWriter { private const string NewLine = "\r\n"; private const int IndentationSize = 4; private readonly StringBuilder _builder = new(); private int _indent; private bool _pendingCarriageReturn; /// <summary>Gets the number of characters written.</summary> public int Length => _builder.Length; /// <summary>Gets or sets the indentation level.</summary> public int Indent { get => _indent; set { Debug.Assert(value >= 0); _indent = value; } } /// <summary>Clears the text after all indented blocks have been closed.</summary> public void Clear() { Debug.Assert(Indent == 0); _builder.Clear(); _pendingCarriageReturn = false; } /// <summary>Writes text, indenting nonempty lines and normalizing line endings.</summary> /// <param name="content">The text to write.</param> public void Write(string? content) { if (string.IsNullOrEmpty(content)) { return; } int start = _pendingCarriageReturn && content[0] == '\n' ? 1 : 0; _pendingCarriageReturn = false; while (start < content.Length) { int end = start; while (end < content.Length && content[end] is not ('\r' or '\n')) { end++; } if (end != start) { WriteIndentation(); _builder.Append(content, start, end - start); } if (end == content.Length) { break; } _builder.Append(NewLine); _pendingCarriageReturn = content[end] == '\r'; start = end + 1; if (start < content.Length) { if (_pendingCarriageReturn && content[start] == '\n') { start++; } _pendingCarriageReturn = false; } } } /// <summary>Writes a character.</summary> /// <param name="value">The character to write.</param> public void Write(char value) { if (value == '\n' && _pendingCarriageReturn) { _pendingCarriageReturn = false; return; } _pendingCarriageReturn = value == '\r'; if (value is '\r' or '\n') { _builder.Append(NewLine); } else { WriteIndentation(); _builder.Append(value); } } /// <summary>Writes interpolated text directly through its handler.</summary> /// <param name="handler">The handler that writes the interpolation.</param> public void Write([InterpolatedStringHandlerArgument("")] ref WriteInterpolatedStringHandler handler) { } /// <summary>Writes a line ending without adding trailing indentation.</summary> public void WriteLine() { _builder.Append(NewLine); _pendingCarriageReturn = false; } /// <summary>Writes text followed by a line ending.</summary> /// <param name="content">The text to write.</param> public void WriteLine(string? content) { Write(content); WriteLine(); } /// <summary>Writes a character followed by a line ending.</summary> /// <param name="value">The character to write.</param> public void WriteLine(char value) { Write(value); WriteLine(); } /// <summary>Writes interpolated text followed by a line ending.</summary> /// <param name="handler">The handler that writes the interpolation.</param> public void WriteLine([InterpolatedStringHandlerArgument("")] ref WriteInterpolatedStringHandler handler) { WriteLine(); } /// <summary>Opens an indented block that is closed when the returned scope is disposed.</summary> /// <returns>The scope that closes the block.</returns> public Block WriteBlock() { WriteLine('{'); Indent++; return new Block(this); } /// <inheritdoc/> public override string ToString() => _builder.ToString(); private void WriteIndentation() { if (_builder.Length == 0 || _builder[_builder.Length - 1] == '\n') { _builder.Append(' ', checked(Indent * IndentationSize)); } } /// <summary>Represents an open indented block.</summary> public struct Block : IDisposable { private IndentedTextWriter? _writer; internal Block(IndentedTextWriter writer) { _writer = writer; } /// <inheritdoc/> public void Dispose() { IndentedTextWriter? writer = _writer; _writer = null; if (writer is not null) { writer.Indent--; writer.WriteLine('}'); } } } /// <summary>Writes interpolated values using invariant formatting.</summary> [EditorBrowsable(EditorBrowsableState.Never)] [InterpolatedStringHandler] public readonly ref struct WriteInterpolatedStringHandler { private readonly IndentedTextWriter _writer; /// <summary>Initializes a handler for compiler-generated interpolation calls.</summary> /// <param name="literalLength">The number of literal characters.</param> /// <param name="formattedCount">The number of formatted values.</param> /// <param name="writer">The destination writer.</param> public WriteInterpolatedStringHandler(int literalLength, int formattedCount, IndentedTextWriter writer) { _writer = writer; } /// <summary>Writes a literal part of the interpolation.</summary> /// <param name="value">The text to write.</param> public void AppendLiteral(string value) => _writer.Write(value); /// <summary>Writes a string value.</summary> /// <param name="value">The value to write.</param> public void AppendFormatted(string? value) => _writer.Write(value); /// <summary>Writes a value using invariant formatting.</summary> /// <typeparam name="T">The value type.</typeparam> /// <param name="value">The value to write.</param> public void AppendFormatted<T>(T value) => AppendFormatted(value, format: null); /// <summary>Writes a value using the specified invariant format.</summary> /// <typeparam name="T">The value type.</typeparam> /// <param name="value">The value to write.</param> /// <param name="format">The format to apply.</param> public void AppendFormatted<T>(T value, string? format) { _writer.Write(value is IFormattable formattable ? formattable.ToString(format, CultureInfo.InvariantCulture) : value?.ToString()); } } }