summaryrefslogtreecommitdiff
path: root/api/src/main/scala/twist/stages/IndentHandler.scala
blob: ea396e78ee5349f285dff5ed1aad14926dea95a1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package twist.stages

import acyclic.file
/**
 * Implements the offside-rule for Twirlite fragments.
 *
 * This stage walks over the whitespace before each line of the Twirlite,
 * augmenting it with the corresponding open-{ and close-} such that an
 * un-modified Twirl parser can then parse it.
 *
 * The rule is simple: any line which starts with an expression potentially
 * opens a new block. Any text after the end of the expression is inside the
 * block, as well as any text on subsequent lines which are indented more
 * deeply.
 */
object IndentHandler extends (String => String){
  def noBraceLine(remainder: String) = {
    !remainder.trim.headOption.exists("{(".toSeq.contains)
  }

  def successRemainder(r: Parser.ParseResult[_]) = r match {
    case Parser.Error(_, _) => None
    case Parser.Success(_, input) => Some(input.source().take(input.offset()))
  }

  def apply(input: String): String = {

    val lines = "" :: input.lines.toList ::: List("")
    val tails = lines.tails.takeWhile(_ != Nil)

    def getIndent(rest: List[String]): (Int, String) = rest match {
      case Nil => (0, "")
      case x if x.head.trim() != "" => (x.head.takeWhile(_ == ' ').length, x.head)
      case _ => getIndent(rest.tail)
    }
    val min = input.lines
      .map(_.indexWhere(_ != ' '))
      .filter(_ != -1)
      .min

    val linesOut = tails.foldLeft((min :: Nil, Seq[String]())){ (x, tail) =>
      val (stack, text) = x
      val spacedCurrent :: next = tail
      val (spaces, current) = spacedCurrent.splitAt(
        math.max(spacedCurrent.indexWhere(_ != ' '), 0)
      )

//      println("index " + math.max(spacedCurrent.indexWhere(_ != ' '), 0))
//      println(spaces, current)

      val declRemainder = successRemainder(Parser.parse(current.trim, _.templateDeclaration()))

      val exprRemainder = successRemainder(Parser.parse(current.trim, _.expression()))


      /**
       * Whether or not the line starts with some sort of indent-start-marker, and
       * if it does what the remainder of the line contains
       */
      val headerSplit: Option[(String, String)] = {
        if (current.startsWith("@import ")) None
        else
          declRemainder.orElse(exprRemainder)
                       .map(r => (r, current.trim.drop(r.length)))
      }

      val (nextIndent, nextLine) = getIndent(next)
      val indent = spaces.length
      val nextIsElse = nextLine.drop(nextIndent).take("@else ".length).trim.startsWith("@else")

      val elseCorrection = if (nextIsElse) 1 else 0

      val delta =
        if (nextIndent > indent && headerSplit.map(_._2).exists(noBraceLine)) 1
        else -stack.takeWhile(_ > nextIndent).length

      val baseLine: String = headerSplit match {
        case None => current
        case Some((before, after)) =>

          val newFirst =
            if (!before.startsWith("@else")) before
            else before.dropRight("@else".length)+ "} else"

          if (delta > 0 && noBraceLine(after)) newFirst + "{" + after
          else if (delta <= 0 && noBraceLine(after) && after.trim != "") newFirst + "{" + after + "}" * (1 - elseCorrection)
          else current


      }

      val closing = "}" * (-delta - elseCorrection)

      val newStack =
        if (delta > 0) nextIndent :: stack
        else stack.drop(-delta)
//      println(stack.toString.padTo(15, ' ') + current.padTo(15, ' ') + indent + "\t" + nextIndent + "\t" + delta + "\t" + headerSplit.toString.padTo(15, ' ') + (baseLine + closing))
      (newStack, text :+ (spaces + baseLine + closing))
    }

    val res = linesOut._2.mkString("\n")
//    println(res)
    res
  }
}