IndentingPrintWriter.kt

/*
 * Copyright 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.wear.watchface

import android.util.Printer
import java.io.PrintWriter
import java.io.Writer

/**
 * Lightweight wrapper around [java.io.PrintWriter] that automatically indents newlines based
 * on internal state.
 *
 * Delays writing indent until first actual write on a newline, enabling indent modification after
 * newline.
 */
internal class IndentingPrintWriter(
    writer: Writer,
    private val singleIndent: String = "\t"
) : Printer {
    private val writer: PrintWriter = PrintWriter(writer)

    /** Mutable version of current indent  */
    private val indentBuilder = StringBuilder()

    /** Cache of current [indentBuilder] value  */
    private var currentIndent: CharArray? = null

    /**
     * Flag indicating if we're currently sitting on an empty line, and that next write should be
     * prefixed with the current indent.
     */
    private var emptyLine = true

    /** Increases the indentation level for future lines.  */
    fun increaseIndent() {
        indentBuilder.append(singleIndent)
        currentIndent = null
    }

    /** Decreases the indentation level for future lines.  */
    fun decreaseIndent() {
        indentBuilder.delete(0, singleIndent.length)
        currentIndent = null
    }

    /** Prints `string`, followed by a newline.  */
    // Printer
    override fun println(string: String) {
        print(string)
        print("\n")
    }

    /** Prints `string`, or `"null"`  */
    fun print(string: String?) {
        val str = string ?: "null"
        write(str, 0, str.length)
    }

    /** Ensures that all pending data is sent out to the target  */
    fun flush() {
        writer.flush()
    }

    private fun write(string: String, offset: Int, count: Int) {
        val bufferEnd = offset + count
        var lineStart = offset
        var lineEnd = offset

        // March through incoming buffer looking for newlines
        while (lineEnd < bufferEnd) {
            val ch = string[lineEnd++]
            if (ch == '\n') {
                maybeWriteIndent()
                writer.write(string, lineStart, lineEnd - lineStart)
                lineStart = lineEnd
                emptyLine = true
            }
        }
        if (lineStart != lineEnd) {
            maybeWriteIndent()
            writer.write(string, lineStart, lineEnd - lineStart)
        }
    }

    private fun maybeWriteIndent() {
        if (emptyLine) {
            emptyLine = false
            if (indentBuilder.isNotEmpty()) {
                if (currentIndent == null) {
                    currentIndent = indentBuilder.toString().toCharArray()
                }
                writer.write(currentIndent, 0, currentIndent!!.size)
            }
        }
    }
}