aboutsummaryrefslogblamecommitdiff
path: root/src/dotty/tools/dotc/util/Text.scala
blob: 251274371e6258fe6f16d1df80077dfe8c075953 (plain) (tree)
1
2
3
4
5
6
7
8
9





                                   
                       

                                  





                                                     
                                             

     



                                                     
 


                                                 
                    







                                                  

     


                                       












                                                                    
                                                                         
 
                                                        

                                          



                                                                                   
                                      
     





                                                               




















                                                                                




                                                          
                                                                




                                                     
               
                          
                                      





                                     
                                        
                                
                             


                 
                        



                                     




                                                 
                           

                                                        


               

                                                                 

                                       
                                  
     
                                                                  





                                                



                                                        


                                                     
package dotty.tools.dotc.util

import language.implicitConversions

object Texts {

  abstract class Text {

    protected def indentMargin = 2

    def relems: List[Text]

    def isEmpty: Boolean = this match {
      case Str(s) => s.isEmpty
      case Fluid(relems) => relems forall (_.isEmpty)
      case Vertical(relems) => relems.isEmpty
    }

    def isVertical = isInstanceOf[Vertical]
    def isClosed = isVertical || isInstanceOf[Closed]
    def isFluid = isInstanceOf[Fluid]
    def isSplittable = isFluid && !isClosed

    def close = new Closed(relems)

    def remaining(width: Int): Int = this match {
      case Str(s) =>
        width - s.length
      case Fluid(Nil) =>
        width
      case Fluid(last :: prevs) =>
        val r = last remaining width
        if (r < 0) r else Fluid(prevs) remaining r
      case Vertical(_) =>
        -1
    }

    def lastLine: String = this match {
      case Str(s) => s
      case _ => relems.head.lastLine
    }

    def appendToLastLine(that: Text): Text = that match {
      case Str(s2) =>
        this match {
          case Str(s1) => Str(s1 + s2)
          case Fluid(Str(s1) :: prev) => Fluid(Str(s1 + s2) :: prev)
        }
      case Fluid(relems) =>
        (this /: relems.reverse)(_ appendToLastLine _)
    }

    private def appendIndented(that: Text)(width: Int): Text =
      Vertical(that.layout(width - indentMargin).indented :: this.relems)

    private def append(width: Int)(that: Text): Text = {
      if (this.isEmpty) that.layout(width)
      else if (that.isEmpty) this
      else if (that.isVertical) appendIndented(that)(width)
      else if (this.isVertical) Fluid(that.layout(width) :: this.relems)
      else if (that.remaining(width - lastLine.length) >= 0) appendToLastLine(that)
      else if (that.isSplittable) (this /: that.relems.reverse)(_.append(width)(_))
      else appendIndented(that)(width)
    }

    def layout(width: Int): Text = this match {
      case Str(_) =>
        this
      case Fluid(relems) =>
        ((Str(""): Text) /: relems.reverse)(_.append(width)(_))
      case Vertical(relems) =>
        Vertical(relems map (_ layout width))
    }

    def map(f: String => String): Text = this match {
      case Str(s) => Str(f(s))
      case Fluid(relems) => Fluid(relems map (_ map f))
      case Vertical(relems) => Vertical(relems map (_ map f))
    }

    def stripPrefix(pre: String): Text = this match {
      case Str(s) =>
        if (s.startsWith(pre)) s drop pre.length else s
      case Fluid(relems) =>
        val elems = relems.reverse
        val head = elems.head.stripPrefix(pre)
        if (head eq elems.head) this else Fluid((head :: elems.tail).reverse)
      case Vertical(relems) =>
        val elems = relems.reverse
        val head = elems.head.stripPrefix(pre)
        if (head eq elems.head) this else Vertical((head :: elems.tail).reverse)
    }

    private def indented: Text = this match {
      case Str(s) => Str((" " * indentMargin) + s)
      case Fluid(relems) => Fluid(relems map (_.indented))
      case Vertical(relems) => Vertical(relems map (_.indented))
    }

    def print(sb: StringBuilder): Unit = this match {
      case Str(s) =>
        sb.append(s)
      case _ =>
        var follow = false
        for (elem <- relems.reverse) {
          if (follow) sb.append("\n")
          elem.print(sb)
          follow = true
        }
    }

    def mkString(width: Int): String = {
      val sb = new StringBuilder
      layout(width).print(sb)
      sb.toString
    }

    def ~ (that: Text) =
      if (this.isEmpty) that
      else if (that.isEmpty) this
      else Fluid(that :: this :: Nil)

    def ~~ (that: Text) =
      if (this.isEmpty) that
      else if (that.isEmpty) this
      else Fluid(that :: Str(" ") :: this :: Nil)

    def over (that: Text) =
      if (this.isVertical) Vertical(that :: this.relems)
      else Vertical(that :: this :: Nil)
  }

  object Text {
    def apply(): Text = Str("")
    def apply(xs: Traversable[Text], sep: String = " "): Text = {
      val ys = xs filterNot (_.isEmpty)
      if (ys.isEmpty) Str("")
      else ys reduce (_ ~ sep ~ _)
    }
    def lines(xs: Traversable[Text]) = Vertical(xs.toList.reverse)
  }

  case class Str(s: String) extends Text {
    override def relems: List[Text] = List(this)
  }

  case class Vertical(relems: List[Text]) extends Text
  case class Fluid(relems: List[Text]) extends Text

  class Closed(relems: List[Text]) extends Fluid(relems)

  implicit def stringToText(s: String): Text = Str(s)
}