com.softwaremill.clippy.Highlighter.scala Maven / Gradle / Ivy
package com.softwaremill.clippy
import fastparse.all._
import scalaparse.Scala._
import scalaparse.syntax.Identifiers._
/**
* Copied from https://github.com/lihaoyi/Ammonite/blob/master/amm/repl/src/main/scala/ammonite/repl/Highlighter.scala
*/
object Highlighter {
object BackTicked {
def unapplySeq(s: Any): Option[List[String]] = {
"`([^`]+)`".r.unapplySeq(s.toString)
}
}
def flattenIndices(
boundedIndices: Seq[(Int, fansi.Attrs, Boolean)],
buffer: Vector[Char]
) = {
boundedIndices
.sliding(2)
.map{
case Seq((s, c1, _), (e, c2, _)) =>
assert(e >= s, s"s: $s e: $e")
c1(fansi.Str(buffer.slice(s, e), errorMode = fansi.ErrorMode.Sanitize))
}.reduce(_ ++ _).render.toVector
}
def defaultHighlight(
buffer: Vector[Char],
comment: fansi.Attrs,
`type`: fansi.Attrs,
literal: fansi.Attrs,
keyword: fansi.Attrs,
reset: fansi.Attrs
) = {
val boundedIndices = defaultHighlightIndices(buffer, comment, `type`, literal, keyword, reset)
flattenIndices(boundedIndices, buffer)
}
def defaultHighlightIndices(
buffer: Vector[Char],
comment: fansi.Attrs,
`type`: fansi.Attrs,
literal: fansi.Attrs,
keyword: fansi.Attrs,
reset: fansi.Attrs
) = Highlighter.highlightIndices(
Parsers.Splitter,
buffer,
{
case Literals.Expr.Interp | Literals.Pat.Interp => reset
case Literals.Comment => comment
case ExprLiteral => literal
case TypeId => `type`
case BackTicked(body) if alphaKeywords.contains(body) => keyword
},
reset
)
def highlightIndices[T](
parser: Parser[_],
buffer: Vector[Char],
ruleColors: PartialFunction[Parser[_], T],
endColor: T
): Seq[(Int, T, Boolean)] = {
val indices = {
var indices = collection.mutable.Buffer((0, endColor, false))
var done = false
val input = buffer.mkString
parser.parse(input, instrument = (rule, idx, res) => {
for (color <- ruleColors.lift(rule)) {
val closeColor = indices.last._2
val startIndex = indices.length
indices += ((idx, color, true))
res() match {
case s: Parsed.Success[_] =>
val prev = indices(startIndex - 1)._1
if (idx < prev && s.index <= prev) {
indices.remove(startIndex, indices.length - startIndex)
}
while (idx < indices.last._1 && s.index <= indices.last._1) {
indices.remove(indices.length - 1)
}
indices += ((s.index, closeColor, false))
if (s.index == buffer.length) done = true
case f: Parsed.Failure if f.index == buffer.length
&& (WL ~ End).parse(input, idx).isInstanceOf[Parsed.Failure] =>
// EOF, stop all further parsing
done = true
case _ => // hard failure, or parsed nothing. Discard all progress
indices.remove(startIndex, indices.length - startIndex)
}
}
})
indices
}
// Make sure there's an index right at the start and right at the end! This
// resets the colors at the snippet's end so they don't bleed into later output
indices ++ Seq((999999999, endColor, false))
}
def highlight(
parser: Parser[_],
buffer: Vector[Char],
ruleColors: PartialFunction[Parser[_], fansi.Attrs],
endColor: fansi.Attrs
) = {
val boundedIndices = highlightIndices(parser, buffer, ruleColors, endColor)
flattenIndices(boundedIndices, buffer)
}
}
object Parsers {
import fastparse.noApi._
import scalaparse.Scala._
import WhitespaceApi._
val Prelude = P((Annot ~ OneNLMax).rep ~ (Mod ~/ Pass).rep)
val Statement =
P(scalaparse.Scala.TopPkgSeq | scalaparse.Scala.Import | Prelude ~ BlockDef | StatCtx.Expr)
def StatementBlock(blockSep: P0) =
P(Semis.? ~ (!blockSep ~ Statement ~~ WS ~~ (Semis | End)).!.repX)
val Splitter = P(StatementBlock(Fail) ~ WL ~ End)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy