All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.andyglow.tree.Renderer.scala Maven / Gradle / Ivy

package com.github.andyglow.tree

object Renderer {

  /** Basically like mkString, but for nested iterators. Used whenever you want to put stuff "in between" the elements of the larger iterator
    */
  def joinIter[T](it0: Iterator[Iterator[T]], joiner: => Iterator[T]) = {
    new Util.ConcatIterator(it0, () => joiner)
  }

  val openParen                   = "("
  val closeParen                  = ")"
  val commaSpace                  = ", "
  val newLine                     = "\n"
  val commaNewLine                = ",\n"
  private[this] val cachedIndents = Array.tabulate(64)(n => " " * n)
  def indent(n: Int)              = if (n < 64) cachedIndents(n) else " " * n
}

class Renderer(maxWidth: Int, indentStep: Int) {

  def rec(x: Tree, leftOffset: Int, indentCount: Int): Result = x match {
    case Tree.Apply(prefix, body) =>
      val nonEmpty = body.hasNext
      // Render children and buffer them until you fill up a single line,
      // or you run out of children.
      //
      // Even before rendering any children, the indentation, prefix
      // and the two open/close parens already take up a few characters
      var totalHorizontalWidth    = leftOffset + prefix.length + 2
      val buffer                  = collection.mutable.Buffer.empty[collection.Seq[String]]
      var lastChildIter           = Iterator[String]()
      var childCompletedLineCount = 0
      while (body.hasNext && totalHorizontalWidth <= maxWidth && childCompletedLineCount == 0) {

        val child    = body.next()
        val childRes = rec(child, (indentCount + 1) * indentStep, indentCount + 1)

        val childBuffer = collection.mutable.Buffer.empty[String]
        while (childRes.iter.hasNext && totalHorizontalWidth < maxWidth) {
          val next = childRes.iter.next()
          childBuffer += next
          totalHorizontalWidth += next.length
        }

        if (body.hasNext) {
          totalHorizontalWidth += 2
        }

        if (!childRes.iter.hasNext) {
          childCompletedLineCount = childCompletedLineCount + childRes.completedLineCount
        } else {
          lastChildIter = childRes.iter

        }

        buffer += childBuffer.toSeq
      }

      def applyHeader = Iterator(prefix, Renderer.openParen)

      val indentPlusOne = Renderer.indent((indentCount + 1) * indentStep)

      def separator = Iterator(Renderer.commaNewLine, indentPlusOne)

      if (
        totalHorizontalWidth <= maxWidth &&
        childCompletedLineCount == 0 &&
        !lastChildIter.hasNext
      ) {
        val iter = Util.concat(
          () => applyHeader,
          () =>
            Renderer.joinIter(
              buffer.iterator.map(_.iterator),
              Iterator(Renderer.commaSpace)
            ),
          () => Iterator(Renderer.closeParen)
        )

        val length: Int = buffer.iterator.map(_.iterator.map(_.length).sum).sum
        new Result(iter, 0, length)
      } else if (!nonEmpty && totalHorizontalWidth > maxWidth) {
        val iter = Util.concat(
          () => applyHeader,
          () =>
            Iterator(
              Renderer.newLine,
              Renderer.indent(indentCount * indentStep),
              Renderer.closeParen
            )
        )

        val length: Int = buffer.iterator.map(_.iterator.map(_.length).sum).sum
        new Result(iter, 0, length)
      } else {
        def bufferedFragments = Renderer.joinIter(
          for ((v, i) <- buffer.iterator.zipWithIndex) yield {
            if (i < buffer.length - 1) v.iterator
            else v.iterator ++ lastChildIter
          },
          separator
        )

        def nonBufferedFragments = Renderer.joinIter(
          body.map(c => rec(c, (indentCount + 1) * indentStep, indentCount + 1).iter),
          separator
        )

        def allFragments =
          if (buffer.isEmpty) nonBufferedFragments
          else if (!body.hasNext) bufferedFragments
          else Renderer.joinIter(Iterator(bufferedFragments, nonBufferedFragments), separator)

        def iter = Util.concat(
          () => applyHeader,
          () => Iterator(Renderer.newLine, indentPlusOne),
          () => allFragments,
          () =>
            Iterator(
              Renderer.newLine,
              Renderer.indent(indentCount * indentStep),
              Renderer.closeParen
            )
        )

        new Result(iter, childCompletedLineCount + 2, indentCount * indentStep + 1)
      }

    case Tree.Infix(lhs, op, rhs) =>
      rec(lhs, leftOffset, indentCount).flatMap { (lhsNewline, lhsLastLineLength) =>
        Result.fromString(" " + op + " ").flatMap((_, _) => rec(rhs, lhsLastLineLength, indentCount))
      }

    case t: Tree.Lazy =>
      lazy val str = t.body0(
        Tree.Ctx(
          maxWidth,
          leftOffset,
          indentCount,
          indentStep
        )
      )
      new Truncated(str, maxWidth, height = 99999999).toResult

    case t: Tree.Lit => Result.fromString(t.body)

    case Tree.KV(k, v) =>
      val prefix = s"$k = "
      Result
        .fromString(prefix)
        .flatMap((_, _) => rec(v, leftOffset + prefix.length, indentCount))

  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy