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

ammonite.compiler.SyntaxHighlighting.scala Maven / Gradle / Ivy

package ammonite.compiler

// Originally adapted from
// https://github.com/lampepfl/dotty/blob/3.0.0-M3/
//   compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala

import dotty.tools.dotc
import dotc.CompilationUnit
import dotc.ast.untpd
import dotc.core.Contexts._
import dotc.core.StdNames._
import dotc.parsing.Parsers.Parser
import dotc.parsing.Scanners.Scanner
import dotc.parsing.Tokens._
import dotc.reporting.Reporter
import dotc.util.Spans.Span
import dotc.util.SourceFile

import java.util.Arrays

/** This object provides functions for syntax highlighting in the REPL */
class SyntaxHighlighting(
                          noAttrs: fansi.Attrs,
                          commentAttrs: fansi.Attrs,
                          keywordAttrs: fansi.Attrs,
                          valDefAttrs: fansi.Attrs,
                          literalAttrs: fansi.Attrs,
                          typeAttrs: fansi.Attrs,
                          annotationAttrs: fansi.Attrs,
                          notImplementedAttrs: fansi.Attrs
                        ) {

  def highlight(in: String)(using Context): String = {
    def freshCtx = ctx.fresh.setReporter(Reporter.NoReporter)
    if (in.isEmpty || ctx.settings.color.value == "never") in
    else {
      val source = SourceFile.virtual("", in)

      given Context = freshCtx
        .setCompilationUnit(CompilationUnit(source, mustExist = false)(using freshCtx))

      val colors = Array.fill(in.length)(0L)

      def highlightRange(from: Int, to: Int, attr: fansi.Attrs) =
        Arrays.fill(colors, from, to, attr.applyMask)

      def highlightPosition(span: Span, attr: fansi.Attrs) =
        if (span.exists && span.start >= 0 && span.end <= in.length)
          highlightRange(span.start, span.end, attr)

      val scanner = new Scanner(source)
      while (scanner.token != EOF) {
        val start = scanner.offset
        val token = scanner.token
        val name = scanner.name
        val isSoftModifier = scanner.isSoftModifierInModifierPosition
        scanner.nextToken()
        val end = scanner.lastOffset

        // Branch order is important. For example,
        // `true` is at the same time a keyword and a literal
        token match {
          case _ if literalTokens.contains(token) =>
            highlightRange(start, end, literalAttrs)

          case STRINGPART =>
            // String interpolation parts include `$` but
            // we don't highlight it, hence the `-1`
            highlightRange(start, end - 1, literalAttrs)

          case _ if alphaKeywords.contains(token) || isSoftModifier =>
            highlightRange(start, end, keywordAttrs)

          case IDENTIFIER if name == nme.??? =>
            highlightRange(start, end, notImplementedAttrs)

          case _ =>
        }
      }

      for {
        comment <- scanner.comments
        span = comment.span
      } highlightPosition(span, commentAttrs)

      object TreeHighlighter extends untpd.UntypedTreeTraverser {
        import untpd._

        def ignored(tree: NameTree) = {
          val name = tree.name.toTermName
          // trees named  and  have weird positions
          name == nme.ERROR || name == nme.CONSTRUCTOR
        }

        def highlightAnnotations(tree: MemberDef): Unit =
          for (annotation <- tree.mods.annotations)
            highlightPosition(annotation.span, annotationAttrs)

        def highlight(trees: List[Tree])(using Context): Unit =
          trees.foreach(traverse)

        def traverse(tree: Tree)(using Context): Unit = {
          tree match {
            case tree: NameTree if ignored(tree) =>
              ()
            case tree: ValOrDefDef =>
              highlightAnnotations(tree)
              highlightPosition(tree.nameSpan, valDefAttrs)
            case tree: MemberDef /* ModuleDef | TypeDef */ =>
              highlightAnnotations(tree)
              highlightPosition(tree.nameSpan, typeAttrs)
            case tree: Ident if tree.isType =>
              highlightPosition(tree.span, typeAttrs)
            case _: TypTree =>
              highlightPosition(tree.span, typeAttrs)
            case _ =>
          }
          traverseChildren(tree)
        }
      }

      val parser = new DottyParser(source)
      val trees = parser.blockStatSeq()
      TreeHighlighter.highlight(trees)

      // if (colorAt.last != NoColor)
      //   highlighted.append(NoColor)

      fansi.Str.fromArrays(in.toCharArray, colors).render
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy