From 8ecff02808deb869b32b206be7105441d3448e15 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Wed, 29 Oct 2025 21:57:40 +0800 Subject: [PATCH 1/7] . --- compiler/src/dotty/tools/repl/JLineTerminal.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/repl/JLineTerminal.scala b/compiler/src/dotty/tools/repl/JLineTerminal.scala index fc193667154d..decf5c418a41 100644 --- a/compiler/src/dotty/tools/repl/JLineTerminal.scala +++ b/compiler/src/dotty/tools/repl/JLineTerminal.scala @@ -32,12 +32,12 @@ class JLineTerminal extends java.io.Closeable { builder.build() private val history = new DefaultHistory - private def blue(str: String)(using Context) = - if (ctx.settings.color.value != "never") Console.BLUE + str + Console.RESET + private def purple(str: String)(using Context) = + if (ctx.settings.color.value != "never") Console.MAGENTA + str + Console.RESET else str - protected def promptStr = "scala" - private def prompt(using Context) = blue(s"\n$promptStr> ") - private def newLinePrompt(using Context) = blue(" | ") + protected def promptStr = "@" + private def prompt(using Context) = purple(s"\n$promptStr ") + private def newLinePrompt(using Context) = purple(" ") /** Blockingly read line from `System.in` * From 4274e90fb3726b365c1b4f245d8a9a84fe14f4cb Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Wed, 29 Oct 2025 23:02:14 +0800 Subject: [PATCH 2/7] . --- .../dotty/tools/dotc/config/CliCommand.scala | 13 ++-- .../tools/dotc/printing/Formatting.scala | 3 +- .../tools/dotc/printing/Highlighting.scala | 72 ------------------- .../tools/dotc/printing/PlainPrinter.scala | 11 +-- .../tools/dotc/printing/ReplPrinter.scala | 2 +- .../dotc/printing/SyntaxHighlighting.scala | 50 ++++++------- .../dotty/tools/dotc/reporting/Message.scala | 7 +- .../dotc/reporting/MessageRendering.scala | 12 ++-- .../dotty/tools/dotc/reporting/messages.scala | 36 +++++----- .../impl/printers/SyntaxHighlight.scala | 27 +++---- 10 files changed, 75 insertions(+), 158 deletions(-) delete mode 100644 compiler/src/dotty/tools/dotc/printing/Highlighting.scala diff --git a/compiler/src/dotty/tools/dotc/config/CliCommand.scala b/compiler/src/dotty/tools/dotc/config/CliCommand.scala index 7f6ea0cece5f..98b6df6f7865 100644 --- a/compiler/src/dotty/tools/dotc/config/CliCommand.scala +++ b/compiler/src/dotty/tools/dotc/config/CliCommand.scala @@ -3,7 +3,7 @@ package config import Settings.* import core.Contexts.* -import printing.Highlighting +import dotty.shaded.fansi import dotty.tools.dotc.util.chaining.* import scala.PartialFunction.cond @@ -154,10 +154,9 @@ trait CliCommand: def apply(texts: List[List[(String, String)]])(using Context): String = StringBuilder().tap(columnate(_, texts)).toString private def columnate(sb: StringBuilder, texts: List[List[(String, String)]])(using Context): Unit = - import Highlighting.* - val colors = Seq(Green(_), Yellow(_), Magenta(_), Cyan(_), Red(_)) + val colors: Seq[String => fansi.Str] = Seq(fansi.Color.Green(_), fansi.Color.Yellow(_), fansi.Color.Magenta(_), fansi.Color.Cyan(_), fansi.Color.Red(_)) val nocolor = texts.length == 1 - def color(index: Int): String => Highlight = if nocolor then NoColor(_) else colors(index % colors.length) + def color(index: Int): String => fansi.Str = if nocolor then (s => fansi.Str(s)) else colors(index % colors.length) val maxCol = ctx.settings.pageWidth.value val field1 = maxField.min(texts.flatten.map(_._1.length).filter(_ < maxField).max) // widest field under maxField val field2 = if field1 + separation + maxField < maxCol then maxCol - field1 - separation else 0 // skinny window -> terminal wrap @@ -173,13 +172,13 @@ trait CliCommand: case i => val (prefix, rest) = fld.splitAt(i) ; prefix :: loopOverField2(rest.trim) text.split("\n").toList.flatMap(loopOverField2).filter(_.nonEmpty).mkString(EOL + "".padLeft(field1) + separator) end formatField2 - def format(first: String, second: String, index: Int, colorPicker: Int => String => Highlight) = - sb.append(colorPicker(index)(formatField1(first)).show) + def format(first: String, second: String, index: Int, colorPicker: Int => String => fansi.Str) = + sb.append(colorPicker(index)(formatField1(first)).render) .append(separator) .append(formatField2(second)) .append(EOL): Unit def fancy(first: String, second: String, index: Int) = format(first, second, index, color) - def plain(first: String, second: String) = format(first, second, 0, _ => NoColor(_)) + def plain(first: String, second: String) = format(first, second, 0, _ => fansi.Str(_)) if heading1.nonEmpty then plain(heading1, heading2) diff --git a/compiler/src/dotty/tools/dotc/printing/Formatting.scala b/compiler/src/dotty/tools/dotc/printing/Formatting.scala index daf4e70ad25a..ff9efda1b9ec 100644 --- a/compiler/src/dotty/tools/dotc/printing/Formatting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Formatting.scala @@ -9,7 +9,6 @@ import Texts.*, Types.*, Flags.*, Symbols.*, Contexts.* import Decorators.* import reporting.Message import util.{DiffUtil, SimpleIdentitySet} -import Highlighting.* object Formatting { @@ -156,6 +155,8 @@ object Formatting { given Show[util.Spans.Span] = ShowAny given Show[tasty.TreeUnpickler#OwnerTree] = ShowAny given Show[typer.ForceDegree.Value] = ShowAny + given Show[dotty.shaded.fansi.Str] with + def show(x: dotty.shaded.fansi.Str) = x.render end Show end ShownDef export ShownDef.{ Show, Shown } diff --git a/compiler/src/dotty/tools/dotc/printing/Highlighting.scala b/compiler/src/dotty/tools/dotc/printing/Highlighting.scala deleted file mode 100644 index c9b3e2a5aa83..000000000000 --- a/compiler/src/dotty/tools/dotc/printing/Highlighting.scala +++ /dev/null @@ -1,72 +0,0 @@ -package dotty.tools -package dotc -package printing - -import scala.collection.mutable -import core.Contexts.* - -object Highlighting { - - abstract class Highlight(private val highlight: String) { - def text: String - - def show(using Context): String = if ctx.useColors then highlight + text + Console.RESET else text - - override def toString: String = - highlight + text + Console.RESET - - def +(other: Highlight)(using Context): HighlightBuffer = - new HighlightBuffer(this) + other - - def +(other: String)(using Context): HighlightBuffer = - new HighlightBuffer(this) + other - } - - abstract class Modifier(private val mod: String, text: String) extends Highlight(Console.RESET) { - override def show(using Context): String = - if (ctx.settings.color.value == "never") "" - else mod + super.show - } - - case class HighlightBuffer(hl: Highlight)(using Context) { - private val buffer = new mutable.ListBuffer[String] - - buffer += hl.show - - def +(other: Highlight): HighlightBuffer = { - buffer += other.show - this - } - - def +(other: String): HighlightBuffer = { - buffer += other - this - } - - override def toString: String = - buffer.mkString - } - - case class NoColor(text: String) extends Highlight(Console.RESET) - - case class Red(text: String) extends Highlight(Console.RED) - case class Blue(text: String) extends Highlight(Console.BLUE) - case class Cyan(text: String) extends Highlight(Console.CYAN) - case class Black(text: String) extends Highlight(Console.BLACK) - case class Green(text: String) extends Highlight(Console.GREEN) - case class White(text: String) extends Highlight(Console.WHITE) - case class Yellow(text: String) extends Highlight(Console.YELLOW) - case class Magenta(text: String) extends Highlight(Console.MAGENTA) - - case class RedB(text: String) extends Highlight(Console.RED_B) - case class BlueB(text: String) extends Highlight(Console.BLUE_B) - case class CyanB(text: String) extends Highlight(Console.CYAN_B) - case class BlackB(text: String) extends Highlight(Console.BLACK_B) - case class GreenB(text: String) extends Highlight(Console.GREEN_B) - case class WhiteB(text: String) extends Highlight(Console.WHITE_B) - case class YellowB(text: String) extends Highlight(Console.YELLOW_B) - case class MagentaB(text: String) extends Highlight(Console.MAGENTA_B) - - case class Bold(text: String) extends Modifier(Console.BOLD, text) - case class Underlined(text: String) extends Modifier(Console.UNDERLINED, text) -} diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 90dfb72ef010..995764b3cd7e 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -839,16 +839,17 @@ class PlainPrinter(_ctx: Context) extends Printer { def plain: PlainPrinter = this - protected def keywordStr(text: String): String = coloredStr(text, SyntaxHighlighting.KeywordColor) - protected def keywordText(text: String): Text = coloredStr(text, SyntaxHighlighting.KeywordColor) + protected def keywordStr(text: String): String = + if (ctx.useColors) SyntaxHighlighting.KeywordColor(text).render else text + protected def keywordText(text: String): Text = keywordStr(text) protected def valDefText(text: Text): Text = coloredText(text, SyntaxHighlighting.ValDefColor) protected def typeText(text: Text): Text = coloredText(text, SyntaxHighlighting.TypeColor) protected def literalText(text: Text): Text = coloredText(text, SyntaxHighlighting.LiteralColor) protected def stringText(text: Text): Text = coloredText(text, SyntaxHighlighting.StringColor) protected def coloredStr(text: String, color: String): String = - if (ctx.useColors) color + text + SyntaxHighlighting.NoColor else text - protected def coloredText(text: Text, color: String): Text = - if (ctx.useColors) color ~ text ~ SyntaxHighlighting.NoColor else text + if (ctx.useColors) color + text + "\u001b[0m" else text + protected def coloredText(text: Text, color: dotty.shaded.fansi.Attrs): Text = + if (ctx.useColors) Str(color(text.toString).render) else text } diff --git a/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala b/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala index f02cbf159224..26eb204528f4 100644 --- a/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala @@ -57,5 +57,5 @@ class ReplPrinter(_ctx: Context) extends RefinedPrinter(_ctx) { // We don't want the colors coming from RefinedPrinter as the REPL uses its // own syntax coloring mechanism. override def coloredStr(text: String, color: String): String = text - override def coloredText(text: Text, color: String): Text = text + override def coloredText(text: Text, color: dotty.shaded.fansi.Attrs): Text = text } diff --git a/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala b/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala index 6c0ff2ffffbe..cd323cc8d871 100644 --- a/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala +++ b/compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala @@ -11,7 +11,8 @@ import dotty.tools.dotc.reporting.Reporter import dotty.tools.dotc.util.Spans.Span import dotty.tools.dotc.util.SourceFile -import java.util.Arrays +import dotty.shaded.fansi +import scala.collection.mutable.ArrayBuffer /** This object provides functions for syntax highlighting in the REPL */ object SyntaxHighlighting { @@ -23,14 +24,14 @@ object SyntaxHighlighting { private inline val debug = true // Keep in sync with SyntaxHighlightingTests - val NoColor: String = Console.RESET - val CommentColor: String = Console.BLUE - val KeywordColor: String = Console.YELLOW - val ValDefColor: String = Console.CYAN - val LiteralColor: String = Console.GREEN - val StringColor: String = Console.GREEN - val TypeColor: String = Console.MAGENTA - val AnnotationColor: String = Console.MAGENTA + val NoColor: fansi.Attrs = fansi.Attrs.Empty + val CommentColor: fansi.Attrs = fansi.Color.Blue + val KeywordColor: fansi.Attrs = fansi.Color.Yellow + val ValDefColor: fansi.Attrs = fansi.Color.Cyan + val LiteralColor: fansi.Attrs = fansi.Color.Green + val StringColor: fansi.Attrs = fansi.Color.Green + val TypeColor: fansi.Attrs = fansi.Color.Magenta + val AnnotationColor: fansi.Attrs = fansi.Color.Magenta def highlight(in: String)(using Context): String = { def freshCtx = ctx.fresh.setReporter(Reporter.NoReporter) @@ -41,18 +42,20 @@ object SyntaxHighlighting { given Context = freshCtx .setCompilationUnit(CompilationUnit(source, mustExist = false)(using freshCtx)) - val colorAt = Array.fill(in.length)(NoColor) + // Buffer to collect all highlight ranges for fansi.Str.overlayAll + val ranges = ArrayBuffer[(fansi.Attrs, Int, Int)]() - def highlightRange(from: Int, to: Int, color: String) = - Arrays.fill(colorAt.asInstanceOf[Array[AnyRef]], from, to, color) + def highlightRange(from: Int, to: Int, attrs: fansi.Attrs) = + if (attrs != fansi.Attrs.Empty) + ranges += ((attrs, from, to)) - def highlightPosition(span: Span, color: String) = if (span.exists) + def highlightPosition(span: Span, attrs: fansi.Attrs) = if (span.exists) if (span.start < 0 || span.end > in.length) { if (debug) println(s"Trying to highlight erroneous position $span. Input size: ${in.length}") } else - highlightRange(span.start, span.end, color) + highlightRange(span.start, span.end, attrs) val scanner = new Scanner(source) while (scanner.token != EOF) { @@ -78,7 +81,7 @@ object SyntaxHighlighting { highlightRange(start, end, KeywordColor) case IDENTIFIER if name == nme.??? => - highlightRange(start, end, Console.RED_B) + highlightRange(start, end, fansi.Back.Red) case IDENTIFIER if name.head.isUpper && name.exists(!_.isUpper) => highlightRange(start, end, KeywordColor) @@ -133,21 +136,8 @@ object SyntaxHighlighting { val trees = parser.blockStatSeq() TreeHighlighter.highlight(trees) - - val highlighted = new StringBuilder() - - for (idx <- colorAt.indices) { - val prev = if (idx == 0) NoColor else colorAt(idx - 1) - val curr = colorAt(idx) - if (curr != prev) - highlighted.append(curr) - highlighted.append(in(idx)) - } - - if (colorAt.last != NoColor) - highlighted.append(NoColor) - - highlighted.toString + // Apply all color ranges at once using fansi.Str.overlayAll + fansi.Str(in).overlayAll(ranges.toSeq).render catch case e: StackOverflowError => in diff --git a/compiler/src/dotty/tools/dotc/reporting/Message.scala b/compiler/src/dotty/tools/dotc/reporting/Message.scala index 32c9bf91f919..922b84eb429d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/Message.scala +++ b/compiler/src/dotty/tools/dotc/reporting/Message.scala @@ -10,6 +10,7 @@ import printing.Formatting.hl import config.SourceVersion import cc.CaptureSet import cc.Capabilities.* +import dotty.shaded.fansi import scala.annotation.threadUnsafe @@ -386,6 +387,10 @@ abstract class Message(val errorId: ErrorMessageID)(using Context) { self => def withoutDisambiguation(): this.type = withDisambiguation(Disambiguation.None) + private def stripFansiColorsIfNecessary(str: String)(using Context): String = + if ctx.useColors then str + else fansi.Str(str).plainText + private def inMessageContext(disambiguate: Disambiguation)(op: Context ?=> String): String = if ctx eq NoContext then op else @@ -397,7 +402,7 @@ abstract class Message(val errorId: ErrorMessageID)(using Context) { self => if !ctx1.property(MessageLimiter).isDefined then ctx1.setProperty(MessageLimiter, ErrorMessageLimiter()) ctx1 - op(using msgContext) + stripFansiColorsIfNecessary(op(using msgContext)) /** The message to report. tags are filtered out */ @threadUnsafe lazy val message: String = diff --git a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala index a306f74858d4..f87e8dba8b67 100644 --- a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala +++ b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala @@ -9,8 +9,8 @@ import java.lang.System.{lineSeparator => EOL} import core.Contexts.* import core.Decorators.* import io.AbstractFile -import printing.Highlighting.{Blue, Red, Yellow} import printing.SyntaxHighlighting +import dotty.shaded.fansi import Diagnostic.* import util.{SourcePosition, NoSourcePosition} import util.Chars.{ LF, CR, FF, SU } @@ -205,8 +205,8 @@ trait MessageRendering { def explanation(m: Message)(using Context): String = { val sb = new StringBuilder( s"""| - |${Blue("Explanation").show} - |${Blue("===========").show}""".stripMargin + |${fansi.Color.Blue("Explanation").render} + |${fansi.Color.Blue("===========").render}""".stripMargin ) sb.append(EOL).append(m.explanation) if (!m.explanation.endsWith(EOL)) sb.append(EOL) @@ -306,9 +306,9 @@ trait MessageRendering { private def hl(str: String)(using Context, Level): String = summon[Level].value match - case interfaces.Diagnostic.ERROR => Red(str).show - case interfaces.Diagnostic.WARNING => Yellow(str).show - case interfaces.Diagnostic.INFO => Blue(str).show + case interfaces.Diagnostic.ERROR => fansi.Color.Red(str).render + case interfaces.Diagnostic.WARNING => fansi.Color.Yellow(str).render + case interfaces.Diagnostic.INFO => fansi.Color.Blue(str).render private def diagnosticLevel(dia: Diagnostic): String = dia match { diff --git a/compiler/src/dotty/tools/dotc/reporting/messages.scala b/compiler/src/dotty/tools/dotc/reporting/messages.scala index e86538eb8110..315573da151d 100644 --- a/compiler/src/dotty/tools/dotc/reporting/messages.scala +++ b/compiler/src/dotty/tools/dotc/reporting/messages.scala @@ -10,7 +10,7 @@ import SymDenotations.SymDenotation import NameKinds.{WildcardParamName, ContextFunctionParamName} import parsing.Scanners.Token import parsing.Tokens -import printing.Highlighting.* +import dotty.shaded.fansi import printing.Formatting import ErrorMessageID.* import ast.Trees @@ -643,9 +643,9 @@ extends SyntaxMsg(UnboundPlaceholderParameterID) { class IllegalStartSimpleExpr(illegalToken: String)(using Context) extends SyntaxMsg(IllegalStartSimpleExprID) { - def msg(using Context) = i"expression expected but ${Red(illegalToken)} found" + def msg(using Context) = i"expression expected but ${fansi.Color.Red(illegalToken)} found" def explain(using Context) = { - i"""|An expression cannot start with ${Red(illegalToken)}.""" + i"""|An expression cannot start with ${fansi.Color.Red(illegalToken)}.""" } } @@ -1583,7 +1583,7 @@ class TypeDoesNotTakeParameters(tpe: Type, params: List[untpd.Tree])(using Conte val ps = if (params.size == 1) s"a type parameter ${params.head}" else s"type parameters ${params.map(_.show).mkString(", ")}" - i"""You specified ${NoColor(ps)} for $tpe, which is not + i"""You specified $ps for $tpe, which is not |declared to take any. |""" } @@ -1745,7 +1745,7 @@ class CannotExtendAnyVal(sym: Symbol)(using Context) def explain(using Context) = if sym.is(Trait) then i"""Only classes (not traits) are allowed to extend ${hl("AnyVal")}, but traits may extend - |${hl("Any")} to become ${Green("\"universal traits\"")} which may only have ${hl("def")} members. + |${hl("Any")} to become ${fansi.Color.Green("\"universal traits\"")} which may only have ${hl("def")} members. |Universal traits can be mixed into classes that extend ${hl("AnyVal")}. |""" else if sym.is(Module) then @@ -2192,13 +2192,13 @@ class UnapplyInvalidReturnType(unapplyResult: Type, unapplyName: Name)(using Con if Feature.migrateTo3 && unapplyName == nme.unapplySeq then "\nYou might want to try to rewrite the extractor to use `unapply` instead." else "" - i"""| ${Red(i"$unapplyResult")} is not a valid result type of an $unapplyName method of an ${Magenta("extractor")}.$addendum""" + i"""| ${fansi.Color.Red(i"$unapplyResult")} is not a valid result type of an $unapplyName method of an ${fansi.Color.Magenta("extractor")}.$addendum""" def explain(using Context) = if (unapplyName.show == "unapply") i""" |To be used as an extractor, an unapply method has to return a type that either: - | - has members ${Magenta("isEmpty: Boolean")} and ${Magenta("get: S")} (usually an ${Green("Option[S]")}) - | - is a ${Green("Boolean")} - | - is a ${Green("Product")} (like a ${Magenta("Tuple2[T1, T2]")}) of arity i with i >= 1, and has members _1 to _i + | - has members ${fansi.Color.Magenta("isEmpty: Boolean")} and ${fansi.Color.Magenta("get: S")} (usually an ${fansi.Color.Green("Option[S]")}) + | - is a ${fansi.Color.Green("Boolean")} + | - is a ${fansi.Color.Green("Product")} (like a ${fansi.Color.Magenta("Tuple2[T1, T2]")}) of arity i with i >= 1, and has members _1 to _i | |See: https://docs.scala-lang.org/scala3/reference/changed-features/pattern-matching.html#fixed-arity-extractors | @@ -2207,35 +2207,35 @@ class UnapplyInvalidReturnType(unapplyResult: Type, unapplyName: Name)(using Con |class A(val i: Int) | |object B { - | def unapply(a: A): ${Green("Option[Int]")} = Some(a.i) + | def unapply(a: A): ${fansi.Color.Green("Option[Int]")} = Some(a.i) |} | |object C { - | def unapply(a: A): ${Green("Boolean")} = a.i == 2 + | def unapply(a: A): ${fansi.Color.Green("Boolean")} = a.i == 2 |} | |object D { - | def unapply(a: A): ${Green("(Int, Int)")} = (a.i, a.i) + | def unapply(a: A): ${fansi.Color.Green("(Int, Int)")} = (a.i, a.i) |} | |object Test { | def test(a: A) = a match { - | ${Magenta("case B(1)")} => 1 - | ${Magenta("case a @ C()")} => 2 - | ${Magenta("case D(3, 3)")} => 3 + | ${fansi.Color.Magenta("case B(1)")} => 1 + | ${fansi.Color.Magenta("case a @ C()")} => 2 + | ${fansi.Color.Magenta("case D(3, 3)")} => 3 | } |} """ else i""" |To be used as an extractor, an unapplySeq method has to return a type which has members - |${Magenta("isEmpty: Boolean")} and ${Magenta("get: S")} where ${Magenta("S <: Seq[V]")} (usually an ${Green("Option[Seq[V]]")}): + |${fansi.Color.Magenta("isEmpty: Boolean")} and ${fansi.Color.Magenta("get: S")} where ${fansi.Color.Magenta("S <: Seq[V]")} (usually an ${fansi.Color.Green("Option[Seq[V]]")}): | |object CharList { - | def unapplySeq(s: String): ${Green("Option[Seq[Char]")} = Some(s.toList) + | def unapplySeq(s: String): ${fansi.Color.Green("Option[Seq[Char]")} = Some(s.toList) | | "example" match { - | ${Magenta("case CharList(c1, c2, c3, c4, _, _, _)")} => + | ${fansi.Color.Magenta("case CharList(c1, c2, c3, c4, _, _, _)")} => | println(s"$$c1,$$c2,$$c3,$$c4") | case _ => | println("Expected *exactly* 7 characters!") diff --git a/compiler/src/scala/quoted/runtime/impl/printers/SyntaxHighlight.scala b/compiler/src/scala/quoted/runtime/impl/printers/SyntaxHighlight.scala index cc3ecc2b153a..5fe8ed4cde92 100644 --- a/compiler/src/scala/quoted/runtime/impl/printers/SyntaxHighlight.scala +++ b/compiler/src/scala/quoted/runtime/impl/printers/SyntaxHighlight.scala @@ -1,6 +1,8 @@ package scala.quoted package runtime.impl.printers +import dotty.shaded.fansi + trait SyntaxHighlight { def highlightKeyword(str: String): String def highlightTypeDef(str: String): String @@ -16,23 +18,14 @@ object SyntaxHighlight { def ANSI: SyntaxHighlight = new SyntaxHighlight { // Keep in sync with SyntaxHighlighting - private val NoColor = Console.RESET - private val CommentColor = Console.BLUE - private val KeywordColor = Console.YELLOW - private val ValDefColor = Console.CYAN - private val LiteralColor = Console.RED - private val StringColor = Console.GREEN - private val TypeColor = Console.MAGENTA - private val AnnotationColor = Console.MAGENTA - - def highlightKeyword(str: String): String = KeywordColor + str + NoColor - def highlightTypeDef(str: String): String = TypeColor + str + NoColor - def highlightLiteral(str: String): String = LiteralColor + str + NoColor - def highlightValDef(str: String): String = ValDefColor + str + NoColor - def highlightOperator(str: String): String = TypeColor + str + NoColor - def highlightAnnotation(str: String): String = AnnotationColor + str + NoColor - def highlightString(str: String): String = StringColor + str + NoColor - def highlightTripleQs: String = Console.RED_B + "???" + NoColor + def highlightKeyword(str: String): String = fansi.Color.Yellow(str).render + def highlightTypeDef(str: String): String = fansi.Color.Magenta(str).render + def highlightLiteral(str: String): String = fansi.Color.Red(str).render + def highlightValDef(str: String): String = fansi.Color.Cyan(str).render + def highlightOperator(str: String): String = fansi.Color.Magenta(str).render + def highlightAnnotation(str: String): String = fansi.Color.Magenta(str).render + def highlightString(str: String): String = fansi.Color.Green(str).render + def highlightTripleQs: String = fansi.Back.Red("???").render } def plain: SyntaxHighlight = new SyntaxHighlight { From df8f20ba887e49bf614fced5b8420600b903b170 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Wed, 29 Oct 2025 23:14:42 +0800 Subject: [PATCH 3/7] . --- .../dotty/tools/dotc/printing/PlainPrinter.scala | 13 +++++++------ .../src/dotty/tools/dotc/printing/ReplPrinter.scala | 2 +- compiler/src/dotty/tools/repl/JLineTerminal.scala | 10 +++++----- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 995764b3cd7e..ffd853725fa3 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -14,6 +14,7 @@ import Variances.varianceSign import util.{Chars, SourcePosition} import scala.util.control.NonFatal import scala.annotation.switch +import dotty.shaded.fansi import config.{Config, Feature} import ast.{tpd, untpd} import cc.* @@ -839,17 +840,17 @@ class PlainPrinter(_ctx: Context) extends Printer { def plain: PlainPrinter = this - protected def keywordStr(text: String): String = - if (ctx.useColors) SyntaxHighlighting.KeywordColor(text).render else text - protected def keywordText(text: String): Text = keywordStr(text) + protected def keywordStr(text: String): String = coloredStr(text, SyntaxHighlighting.KeywordColor) + protected def keywordText(text: String): Text = coloredStr(text, SyntaxHighlighting.KeywordColor) protected def valDefText(text: Text): Text = coloredText(text, SyntaxHighlighting.ValDefColor) protected def typeText(text: Text): Text = coloredText(text, SyntaxHighlighting.TypeColor) protected def literalText(text: Text): Text = coloredText(text, SyntaxHighlighting.LiteralColor) protected def stringText(text: Text): Text = coloredText(text, SyntaxHighlighting.StringColor) - protected def coloredStr(text: String, color: String): String = - if (ctx.useColors) color + text + "\u001b[0m" else text + protected def coloredStr(text: String, color: dotty.shaded.fansi.Attrs): String = + if (ctx.useColors) color(text).render else text protected def coloredText(text: Text, color: dotty.shaded.fansi.Attrs): Text = - if (ctx.useColors) Str(color(text.toString).render) else text + if (ctx.useColors) fansi.Attrs.emitAnsiCodes(0, color.applyMask) ~ text ~ fansi.Attrs.emitAnsiCodes(color.applyMask, 0) + else text } diff --git a/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala b/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala index 26eb204528f4..56e78ff722ed 100644 --- a/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/ReplPrinter.scala @@ -56,6 +56,6 @@ class ReplPrinter(_ctx: Context) extends RefinedPrinter(_ctx) { // We don't want the colors coming from RefinedPrinter as the REPL uses its // own syntax coloring mechanism. - override def coloredStr(text: String, color: String): String = text + override def coloredStr(text: String, color: dotty.shaded.fansi.Attrs): String = text override def coloredText(text: Text, color: dotty.shaded.fansi.Attrs): Text = text } diff --git a/compiler/src/dotty/tools/repl/JLineTerminal.scala b/compiler/src/dotty/tools/repl/JLineTerminal.scala index decf5c418a41..fc193667154d 100644 --- a/compiler/src/dotty/tools/repl/JLineTerminal.scala +++ b/compiler/src/dotty/tools/repl/JLineTerminal.scala @@ -32,12 +32,12 @@ class JLineTerminal extends java.io.Closeable { builder.build() private val history = new DefaultHistory - private def purple(str: String)(using Context) = - if (ctx.settings.color.value != "never") Console.MAGENTA + str + Console.RESET + private def blue(str: String)(using Context) = + if (ctx.settings.color.value != "never") Console.BLUE + str + Console.RESET else str - protected def promptStr = "@" - private def prompt(using Context) = purple(s"\n$promptStr ") - private def newLinePrompt(using Context) = purple(" ") + protected def promptStr = "scala" + private def prompt(using Context) = blue(s"\n$promptStr> ") + private def newLinePrompt(using Context) = blue(" | ") /** Blockingly read line from `System.in` * From 6bbd2df840d0e75be1648d35f38d189a9b464513 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Wed, 29 Oct 2025 23:15:58 +0800 Subject: [PATCH 4/7] . --- compiler/src/dotty/tools/repl/JLineTerminal.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/repl/JLineTerminal.scala b/compiler/src/dotty/tools/repl/JLineTerminal.scala index fc193667154d..4dab03888323 100644 --- a/compiler/src/dotty/tools/repl/JLineTerminal.scala +++ b/compiler/src/dotty/tools/repl/JLineTerminal.scala @@ -33,7 +33,7 @@ class JLineTerminal extends java.io.Closeable { private val history = new DefaultHistory private def blue(str: String)(using Context) = - if (ctx.settings.color.value != "never") Console.BLUE + str + Console.RESET + if (ctx.settings.color.value != "never") fansi.Color.Blue(str).render else str protected def promptStr = "scala" private def prompt(using Context) = blue(s"\n$promptStr> ") From 14c955e3294c910673c7cc844401786d8e4662ce Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Wed, 29 Oct 2025 23:22:31 +0800 Subject: [PATCH 5/7] . --- compiler/src/dotty/tools/dotc/config/CliCommand.scala | 2 +- compiler/src/dotty/tools/repl/JLineTerminal.scala | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/config/CliCommand.scala b/compiler/src/dotty/tools/dotc/config/CliCommand.scala index 98b6df6f7865..5e8b159aa8af 100644 --- a/compiler/src/dotty/tools/dotc/config/CliCommand.scala +++ b/compiler/src/dotty/tools/dotc/config/CliCommand.scala @@ -156,7 +156,7 @@ trait CliCommand: private def columnate(sb: StringBuilder, texts: List[List[(String, String)]])(using Context): Unit = val colors: Seq[String => fansi.Str] = Seq(fansi.Color.Green(_), fansi.Color.Yellow(_), fansi.Color.Magenta(_), fansi.Color.Cyan(_), fansi.Color.Red(_)) val nocolor = texts.length == 1 - def color(index: Int): String => fansi.Str = if nocolor then (s => fansi.Str(s)) else colors(index % colors.length) + def color(index: Int): String => fansi.Str = if nocolor then fansi.Str(_) else colors(index % colors.length) val maxCol = ctx.settings.pageWidth.value val field1 = maxField.min(texts.flatten.map(_._1.length).filter(_ < maxField).max) // widest field under maxField val field2 = if field1 + separation + maxField < maxCol then maxCol - field1 - separation else 0 // skinny window -> terminal wrap diff --git a/compiler/src/dotty/tools/repl/JLineTerminal.scala b/compiler/src/dotty/tools/repl/JLineTerminal.scala index 4dab03888323..6f5c10011fb5 100644 --- a/compiler/src/dotty/tools/repl/JLineTerminal.scala +++ b/compiler/src/dotty/tools/repl/JLineTerminal.scala @@ -2,6 +2,7 @@ package dotty.tools.repl import scala.language.unsafeNulls +import dotty.shaded.fansi import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.parsing.Scanners.Scanner import dotty.tools.dotc.parsing.Tokens.* From 74cc11d7a410713612f16186c550d4eaac35800d Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 30 Oct 2025 07:54:19 +0800 Subject: [PATCH 6/7] . --- .../dotc/printing/SyntaxHighlightingTests.scala | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/compiler/test/dotty/tools/dotc/printing/SyntaxHighlightingTests.scala b/compiler/test/dotty/tools/dotc/printing/SyntaxHighlightingTests.scala index 2a2510a95394..133f8d5aee80 100644 --- a/compiler/test/dotty/tools/dotc/printing/SyntaxHighlightingTests.scala +++ b/compiler/test/dotty/tools/dotc/printing/SyntaxHighlightingTests.scala @@ -2,6 +2,7 @@ package dotty.tools.dotc.printing import scala.language.unsafeNulls +import dotty.shaded.fansi import dotty.tools.DottyTest import org.junit.Assert._ import org.junit.{Ignore, Test} @@ -12,14 +13,15 @@ class SyntaxHighlightingTests extends DottyTest { import SyntaxHighlighting._ private def test(source: String, expected: String): Unit = { + val highlighted = SyntaxHighlighting.highlight(source)(using ctx.withColors) - .replace(NoColor, ">") - .replace(CommentColor, "") + .replace(fansi.Attrs.emitAnsiCodes(NoColor.applyMask, CommentColor.applyMask), " Date: Thu, 30 Oct 2025 09:35:18 +0800 Subject: [PATCH 7/7] . --- .../tools/dotc/reporting/MessageRendering.scala | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala index f87e8dba8b67..9e9be4811657 100644 --- a/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala +++ b/compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala @@ -205,8 +205,8 @@ trait MessageRendering { def explanation(m: Message)(using Context): String = { val sb = new StringBuilder( s"""| - |${fansi.Color.Blue("Explanation").render} - |${fansi.Color.Blue("===========").render}""".stripMargin + |${if ctx.useColors then fansi.Color.Blue("Explanation").render else "Explanation"} + |${if ctx.useColors then fansi.Color.Blue("===========").render else "==========="}""".stripMargin ) sb.append(EOL).append(m.explanation) if (!m.explanation.endsWith(EOL)) sb.append(EOL) @@ -305,10 +305,12 @@ trait MessageRendering { } private def hl(str: String)(using Context, Level): String = - summon[Level].value match - case interfaces.Diagnostic.ERROR => fansi.Color.Red(str).render - case interfaces.Diagnostic.WARNING => fansi.Color.Yellow(str).render - case interfaces.Diagnostic.INFO => fansi.Color.Blue(str).render + if ctx.useColors then + summon[Level].value match + case interfaces.Diagnostic.ERROR => fansi.Color.Red(str).render + case interfaces.Diagnostic.WARNING => fansi.Color.Yellow(str).render + case interfaces.Diagnostic.INFO => fansi.Color.Blue(str).render + else str private def diagnosticLevel(dia: Diagnostic): String = dia match {