diff --git a/compiler/src/dotty/tools/dotc/config/CliCommand.scala b/compiler/src/dotty/tools/dotc/config/CliCommand.scala index dc503705d81b..0d696722fe4b 100644 --- a/compiler/src/dotty/tools/dotc/config/CliCommand.scala +++ b/compiler/src/dotty/tools/dotc/config/CliCommand.scala @@ -3,7 +3,9 @@ package config import Settings._ import core.Contexts._ +import printing.Highlighting +import scala.util.chaining.given import scala.PartialFunction.cond trait CliCommand: @@ -49,49 +51,21 @@ trait CliCommand: end distill /** Creates a help message for a subset of options based on cond */ - protected def availableOptionsMsg(cond: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String = - val ss = (settings.allSettings filter cond).toList sortBy (_.name) - val maxNameWidth = 30 - val nameWidths = ss.map(_.name.length).partition(_ < maxNameWidth)._1 - val width = if nameWidths.nonEmpty then nameWidths.max else maxNameWidth - val terminalWidth = settings.pageWidth.value - val (nameWidth, descriptionWidth) = { - val w1 = - if width < maxNameWidth then width - else maxNameWidth - val w2 = - if terminalWidth < w1 + maxNameWidth then 0 - else terminalWidth - w1 - 1 - (w1, w2) - } - def formatName(name: String) = - if name.length <= nameWidth then ("%-" + nameWidth + "s") format name - else (name + "\n%-" + nameWidth + "s") format "" - def formatDescription(text: String): String = - if descriptionWidth == 0 then text - else if text.length < descriptionWidth then text - else { - val inx = text.substring(0, descriptionWidth).lastIndexOf(" ") - if inx < 0 then text - else - val str = text.substring(0, inx) - s"${str}\n${formatName("")} ${formatDescription(text.substring(inx + 1))}" - } - def formatSetting(name: String, value: String) = - if (value.nonEmpty) - // the format here is helping to make empty padding and put the additional information exactly under the description. - s"\n${formatName("")} $name: $value." - else - "" - def helpStr(s: Setting[?]) = + protected def availableOptionsMsg(p: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String = + // result is (Option Name, descrption\ndefault: value\nchoices: x, y, z + def help(s: Setting[?]): (String, String) = + // For now, skip the default values that do not make sense for the end user, such as 'false' for the version command. def defaultValue = s.default match case _: Int | _: String => s.default.toString - case _ => - // For now, skip the default values that do not make sense for the end user. - // For example 'false' for the version command. - "" - s"${formatName(s.name)} ${formatDescription(shortHelp(s))}${formatSetting("Default", defaultValue)}${formatSetting("Choices", s.legalChoices)}" - ss.map(helpStr).mkString("", "\n", s"\n${formatName("@")} ${formatDescription("A text file containing compiler arguments (options and source files).")}\n") + case _ => "" + val info = List(shortHelp(s), if defaultValue.nonEmpty then s"Default $defaultValue" else "", if s.legalChoices.nonEmpty then s"Choices ${s.legalChoices}" else "") + (s.name, info.filter(_.nonEmpty).mkString("\n")) + end help + + val ss = settings.allSettings.filter(p).toList.sortBy(_.name) + val formatter = Columnator("", "", maxField = 30) + val fresh = ContextBase().initialCtx.fresh.setSettings(summon[SettingsState]) + formatter(List(ss.map(help) :+ ("@", "A text file containing compiler arguments (options and source files).")))(using fresh) end availableOptionsMsg protected def shortUsage: String = s"Usage: $cmdName " @@ -99,9 +73,9 @@ trait CliCommand: protected def createUsageMsg(label: String, shouldExplain: Boolean, cond: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String = val prefix = List( Some(shortUsage), - Some(explainAdvanced) filter (_ => shouldExplain), + Some(explainAdvanced).filter(_ => shouldExplain), Some(label + " options include:") - ).flatten mkString "\n" + ).flatten.mkString("\n") prefix + "\n" + availableOptionsMsg(cond) @@ -136,31 +110,10 @@ trait CliCommand: createUsageMsg("Possible private", shouldExplain = true, isPrivate) /** Used for the formatted output of -Xshow-phases */ - protected def phasesMessage(using ctx: Context): String = - + protected def phasesMessage(using Context): String = val phases = new Compiler().phases - val nameLimit = 25 - val maxCol = ctx.settings.pageWidth.value - val maxName = phases.flatten.map(_.phaseName.length).max - val width = maxName.min(nameLimit) - val maxDesc = maxCol - (width + 6) - val fmt = s"%${width}.${width}s %.${maxDesc}s%n" - - val sb = new StringBuilder - sb ++= fmt.format("phase name", "description") - sb ++= fmt.format("----------", "-----------") - - phases.foreach { - case List(single) => - sb ++= fmt.format(single.phaseName, single.description) - case Nil => () - case more => - sb ++= fmt.format(s"{", "") - more.foreach { mini => sb ++= fmt.format(mini.phaseName, mini.description) } - sb ++= fmt.format(s"}", "") - } - sb.mkString - + val formatter = Columnator("phase name", "description", maxField = 25) + formatter(phases.map(mega => mega.map(p => (p.phaseName, p.description)))) /** Provide usage feedback on argument summary, assuming that all settings * are already applied in context. @@ -188,3 +141,56 @@ trait CliCommand: extension [T](setting: Setting[T]) protected def value(using ss: SettingsState): T = setting.valueIn(ss) + + extension (s: String) + def padLeft(width: Int): String = String.format(s"%${width}s", s) + + // Formatting for -help and -Vphases in two columns, handling long field1 and wrapping long field2 + class Columnator(heading1: String, heading2: String, maxField: Int, separation: Int = 2): + 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 nocolor = texts.length == 1 + def color(index: Int): String => Highlight = if nocolor then NoColor(_) 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 + val separator = " " * separation + val EOL = "\n" + def formatField1(text: String): String = if text.length <= field1 then text.padLeft(field1) else text + EOL + "".padLeft(field1) + def formatField2(text: String): String = + def loopOverField2(fld: String): List[String] = + if field2 == 0 || fld.length <= field2 then List(fld) + else + fld.lastIndexOf(" ", field2) match + case -1 => List(fld) + 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) + .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(_)) + + if heading1.nonEmpty then + plain(heading1, heading2) + plain("-" * heading1.length, "-" * heading2.length) + + def emit(index: Int)(textPair: (String, String)): Unit = fancy(textPair._1, textPair._2, index) + def group(index: Int)(body: Int => Unit): Unit = + if !ctx.useColors then plain(s"{", "") + body(index) + if !ctx.useColors then plain(s"}", "") + + texts.zipWithIndex.foreach { (text, index) => + text match + case List(single) => emit(index)(single) + case Nil => + case mega => group(index)(i => mega.foreach(emit(i))) + } + end Columnator diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index 4dccad86e98c..8794620bdcf0 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -134,6 +134,7 @@ private sealed trait VerboseSettings: self: SettingGroup => val Vhelp: Setting[Boolean] = BooleanSetting("-V", "Print a synopsis of verbose options.") val Xprint: Setting[List[String]] = PhasesSetting("-Vprint", "Print out program after", aliases = List("-Xprint")) + val XshowPhases: Setting[Boolean] = BooleanSetting("-Vphases", "List compiler phases.", aliases = List("-Xshow-phases")) /** -W "Warnings" settings */ @@ -215,7 +216,6 @@ private sealed trait XSettings: val XprintInline: Setting[Boolean] = BooleanSetting("-Xprint-inline", "Show where inlined code comes from.") val XprintSuspension: Setting[Boolean] = BooleanSetting("-Xprint-suspension", "Show when code is suspended until macros are compiled.") val Xprompt: Setting[Boolean] = BooleanSetting("-Xprompt", "Display a prompt after each error (debugging option).") - val XshowPhases: Setting[Boolean] = BooleanSetting("-Xshow-phases", "Print all compiler phases.") val XreplDisableDisplay: Setting[Boolean] = BooleanSetting("-Xrepl-disable-display", "Do not display definitions in REPL.") val XverifySignatures: Setting[Boolean] = BooleanSetting("-Xverify-signatures", "Verify generic signatures in generated bytecode.") val XignoreScala2Macros: Setting[Boolean] = BooleanSetting("-Xignore-scala2-macros", "Ignore errors when compiling code that calls Scala2 macros, these will fail at runtime.") diff --git a/compiler/src/dotty/tools/dotc/printing/Highlighting.scala b/compiler/src/dotty/tools/dotc/printing/Highlighting.scala index f5d6597fc0f7..ceb5afdea750 100644 --- a/compiler/src/dotty/tools/dotc/printing/Highlighting.scala +++ b/compiler/src/dotty/tools/dotc/printing/Highlighting.scala @@ -10,9 +10,7 @@ object Highlighting { abstract class Highlight(private val highlight: String) { def text: String - def show(using Context): String = - if (ctx.settings.color.value == "never") text - else highlight + text + Console.RESET + def show(using Context): String = if ctx.useColors then highlight + text + Console.RESET else text override def toString: String = highlight + text + Console.RESET diff --git a/compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala b/compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala index a97d8884625a..ad068b84c041 100644 --- a/compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala +++ b/compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala @@ -12,7 +12,7 @@ import dotty.tools.dotc.transform.MegaPhase.MiniPhase object InterceptedMethods { val name: String = "intercepted" - val description: String = "handling of `==`, `|=`, `getClass` methods" + val description: String = "rewrite universal `!=`, `##` methods" } /** Replace member references as follows: diff --git a/compiler/src/dotty/tools/dotc/transform/LiftTry.scala b/compiler/src/dotty/tools/dotc/transform/LiftTry.scala index f70e6a38fcf5..6acb1013d509 100644 --- a/compiler/src/dotty/tools/dotc/transform/LiftTry.scala +++ b/compiler/src/dotty/tools/dotc/transform/LiftTry.scala @@ -85,4 +85,4 @@ class LiftTry extends MiniPhase with IdentityDenotTransformer { thisPhase => } object LiftTry: val name = "liftTry" - val description: String = "Lifts try's that might be executed on non-empty expression stacks" + val description: String = "lift any try that might be executed on a non-empty expression stack" diff --git a/compiler/src/dotty/tools/dotc/transform/UninitializedDefs.scala b/compiler/src/dotty/tools/dotc/transform/UninitializedDefs.scala index 9f796cc1abe7..a7ccaa19d90a 100644 --- a/compiler/src/dotty/tools/dotc/transform/UninitializedDefs.scala +++ b/compiler/src/dotty/tools/dotc/transform/UninitializedDefs.scala @@ -43,6 +43,6 @@ class UninitializedDefs extends MiniPhase: end UninitializedDefs object UninitializedDefs: - val name: String = "uninitializedDefs" - val description: String = "replaces `compiletime.uninitialized` by `_`" + val name: String = "uninitialized" + val description: String = "eliminates `compiletime.uninitialized`" end UninitializedDefs diff --git a/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala b/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala index 79c09fdf6606..667f9fdb2779 100644 --- a/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala +++ b/compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala @@ -158,5 +158,5 @@ class StringInterpolatorOpt extends MiniPhase: case _ => tree object StringInterpolatorOpt: - val name: String = "stringInterpolatorOpt" - val description: String = "optimize s, f and raw string interpolators" + val name: String = "interpolators" + val description: String = "optimize s, f, and raw string interpolators"