aboutsummaryrefslogblamecommitdiff
path: root/src/dotty/tools/dotc/printing/Texts.scala
blob: db81cab7afaaba0de0f191e7af2b025345ebe3df (plain) (tree)
1
2
3
4
5
6
7
8
9
10

                        
                            
                                   


              
                       

                                  





                                                     
                                             

     



                                                     
 


                                                 
                    







                                                  

     


                                       






                                                                    
                                                     





                                                              
                                                                         
 
                                                        

                                          



                                                                                   
                                      
     





                                                               




















                                                                                




                                                          
                                                                




                                                     
               
                          
                                      





                                     
                                        
                                
                             


                 
                        



                                     




                                                 
                           

                                                        
 
                                                             


               

                         
                               



                                                                 
                                                                 





                                         
     

                                                        
                                                                  





                                                



                                                        

                                                     
 
package dotty.tools.dotc
package printing
import core.Contexts.Context
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) => Fluid(that :: relems)
        }
      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)

    def provided(pred: Boolean) = if (pred) this else Str("")
  }

  object Text {

    /** The empty text */
    def apply(): Text = Str("")

    /** A concatenation of elements in `xs` and interspersed with
     *  separator strings `sep`.
     */
    def apply(xs: Traversable[Text], sep: String = " "): Text = {
      if (sep == "\n") lines(xs)
      else {
        val ys = xs filterNot (_.isEmpty)
        if (ys.isEmpty) Str("")
        else ys reduce (_ ~ sep ~ _)
      }
    }

    /** The given texts `xs`, each on a separate line */
    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)
}