Skip to content

Commit 5109909

Browse files
Merge pull request #14394 from som-snytt/tweak/color-phases
Use color for showing megaphases
2 parents 123caff + 12e98f5 commit 5109909

File tree

7 files changed

+81
-77
lines changed

7 files changed

+81
-77
lines changed

compiler/src/dotty/tools/dotc/config/CliCommand.scala

Lines changed: 73 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package config
33

44
import Settings._
55
import core.Contexts._
6+
import printing.Highlighting
67

8+
import scala.util.chaining.given
79
import scala.PartialFunction.cond
810

911
trait CliCommand:
@@ -49,59 +51,31 @@ trait CliCommand:
4951
end distill
5052

5153
/** Creates a help message for a subset of options based on cond */
52-
protected def availableOptionsMsg(cond: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String =
53-
val ss = (settings.allSettings filter cond).toList sortBy (_.name)
54-
val maxNameWidth = 30
55-
val nameWidths = ss.map(_.name.length).partition(_ < maxNameWidth)._1
56-
val width = if nameWidths.nonEmpty then nameWidths.max else maxNameWidth
57-
val terminalWidth = settings.pageWidth.value
58-
val (nameWidth, descriptionWidth) = {
59-
val w1 =
60-
if width < maxNameWidth then width
61-
else maxNameWidth
62-
val w2 =
63-
if terminalWidth < w1 + maxNameWidth then 0
64-
else terminalWidth - w1 - 1
65-
(w1, w2)
66-
}
67-
def formatName(name: String) =
68-
if name.length <= nameWidth then ("%-" + nameWidth + "s") format name
69-
else (name + "\n%-" + nameWidth + "s") format ""
70-
def formatDescription(text: String): String =
71-
if descriptionWidth == 0 then text
72-
else if text.length < descriptionWidth then text
73-
else {
74-
val inx = text.substring(0, descriptionWidth).lastIndexOf(" ")
75-
if inx < 0 then text
76-
else
77-
val str = text.substring(0, inx)
78-
s"${str}\n${formatName("")} ${formatDescription(text.substring(inx + 1))}"
79-
}
80-
def formatSetting(name: String, value: String) =
81-
if (value.nonEmpty)
82-
// the format here is helping to make empty padding and put the additional information exactly under the description.
83-
s"\n${formatName("")} $name: $value."
84-
else
85-
""
86-
def helpStr(s: Setting[?]) =
54+
protected def availableOptionsMsg(p: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String =
55+
// result is (Option Name, descrption\ndefault: value\nchoices: x, y, z
56+
def help(s: Setting[?]): (String, String) =
57+
// For now, skip the default values that do not make sense for the end user, such as 'false' for the version command.
8758
def defaultValue = s.default match
8859
case _: Int | _: String => s.default.toString
89-
case _ =>
90-
// For now, skip the default values that do not make sense for the end user.
91-
// For example 'false' for the version command.
92-
""
93-
s"${formatName(s.name)} ${formatDescription(shortHelp(s))}${formatSetting("Default", defaultValue)}${formatSetting("Choices", s.legalChoices)}"
94-
ss.map(helpStr).mkString("", "\n", s"\n${formatName("@<file>")} ${formatDescription("A text file containing compiler arguments (options and source files).")}\n")
60+
case _ => ""
61+
val info = List(shortHelp(s), if defaultValue.nonEmpty then s"Default $defaultValue" else "", if s.legalChoices.nonEmpty then s"Choices ${s.legalChoices}" else "")
62+
(s.name, info.filter(_.nonEmpty).mkString("\n"))
63+
end help
64+
65+
val ss = settings.allSettings.filter(p).toList.sortBy(_.name)
66+
val formatter = Columnator("", "", maxField = 30)
67+
val fresh = ContextBase().initialCtx.fresh.setSettings(summon[SettingsState])
68+
formatter(List(ss.map(help) :+ ("@<file>", "A text file containing compiler arguments (options and source files).")))(using fresh)
9569
end availableOptionsMsg
9670

9771
protected def shortUsage: String = s"Usage: $cmdName <options> <source files>"
9872

9973
protected def createUsageMsg(label: String, shouldExplain: Boolean, cond: Setting[?] => Boolean)(using settings: ConcreteSettings)(using SettingsState): String =
10074
val prefix = List(
10175
Some(shortUsage),
102-
Some(explainAdvanced) filter (_ => shouldExplain),
76+
Some(explainAdvanced).filter(_ => shouldExplain),
10377
Some(label + " options include:")
104-
).flatten mkString "\n"
78+
).flatten.mkString("\n")
10579

10680
prefix + "\n" + availableOptionsMsg(cond)
10781

@@ -136,31 +110,10 @@ trait CliCommand:
136110
createUsageMsg("Possible private", shouldExplain = true, isPrivate)
137111

138112
/** Used for the formatted output of -Xshow-phases */
139-
protected def phasesMessage(using ctx: Context): String =
140-
113+
protected def phasesMessage(using Context): String =
141114
val phases = new Compiler().phases
142-
val nameLimit = 25
143-
val maxCol = ctx.settings.pageWidth.value
144-
val maxName = phases.flatten.map(_.phaseName.length).max
145-
val width = maxName.min(nameLimit)
146-
val maxDesc = maxCol - (width + 6)
147-
val fmt = s"%${width}.${width}s %.${maxDesc}s%n"
148-
149-
val sb = new StringBuilder
150-
sb ++= fmt.format("phase name", "description")
151-
sb ++= fmt.format("----------", "-----------")
152-
153-
phases.foreach {
154-
case List(single) =>
155-
sb ++= fmt.format(single.phaseName, single.description)
156-
case Nil => ()
157-
case more =>
158-
sb ++= fmt.format(s"{", "")
159-
more.foreach { mini => sb ++= fmt.format(mini.phaseName, mini.description) }
160-
sb ++= fmt.format(s"}", "")
161-
}
162-
sb.mkString
163-
115+
val formatter = Columnator("phase name", "description", maxField = 25)
116+
formatter(phases.map(mega => mega.map(p => (p.phaseName, p.description))))
164117

165118
/** Provide usage feedback on argument summary, assuming that all settings
166119
* are already applied in context.
@@ -188,3 +141,56 @@ trait CliCommand:
188141

189142
extension [T](setting: Setting[T])
190143
protected def value(using ss: SettingsState): T = setting.valueIn(ss)
144+
145+
extension (s: String)
146+
def padLeft(width: Int): String = String.format(s"%${width}s", s)
147+
148+
// Formatting for -help and -Vphases in two columns, handling long field1 and wrapping long field2
149+
class Columnator(heading1: String, heading2: String, maxField: Int, separation: Int = 2):
150+
def apply(texts: List[List[(String, String)]])(using Context): String = StringBuilder().tap(columnate(_, texts)).toString
151+
152+
private def columnate(sb: StringBuilder, texts: List[List[(String, String)]])(using Context): Unit =
153+
import Highlighting.*
154+
val colors = Seq(Green(_), Yellow(_), Magenta(_), Cyan(_), Red(_))
155+
val nocolor = texts.length == 1
156+
def color(index: Int): String => Highlight = if nocolor then NoColor(_) else colors(index % colors.length)
157+
val maxCol = ctx.settings.pageWidth.value
158+
val field1 = maxField.min(texts.flatten.map(_._1.length).filter(_ < maxField).max) // widest field under maxField
159+
val field2 = if field1 + separation + maxField < maxCol then maxCol - field1 - separation else 0 // skinny window -> terminal wrap
160+
val separator = " " * separation
161+
val EOL = "\n"
162+
def formatField1(text: String): String = if text.length <= field1 then text.padLeft(field1) else text + EOL + "".padLeft(field1)
163+
def formatField2(text: String): String =
164+
def loopOverField2(fld: String): List[String] =
165+
if field2 == 0 || fld.length <= field2 then List(fld)
166+
else
167+
fld.lastIndexOf(" ", field2) match
168+
case -1 => List(fld)
169+
case i => val (prefix, rest) = fld.splitAt(i) ; prefix :: loopOverField2(rest.trim)
170+
text.split("\n").toList.flatMap(loopOverField2).filter(_.nonEmpty).mkString(EOL + "".padLeft(field1) + separator)
171+
end formatField2
172+
def format(first: String, second: String, index: Int, colorPicker: Int => String => Highlight) =
173+
sb.append(colorPicker(index)(formatField1(first)).show)
174+
.append(separator)
175+
.append(formatField2(second))
176+
.append(EOL): Unit
177+
def fancy(first: String, second: String, index: Int) = format(first, second, index, color)
178+
def plain(first: String, second: String) = format(first, second, 0, _ => NoColor(_))
179+
180+
if heading1.nonEmpty then
181+
plain(heading1, heading2)
182+
plain("-" * heading1.length, "-" * heading2.length)
183+
184+
def emit(index: Int)(textPair: (String, String)): Unit = fancy(textPair._1, textPair._2, index)
185+
def group(index: Int)(body: Int => Unit): Unit =
186+
if !ctx.useColors then plain(s"{", "")
187+
body(index)
188+
if !ctx.useColors then plain(s"}", "")
189+
190+
texts.zipWithIndex.foreach { (text, index) =>
191+
text match
192+
case List(single) => emit(index)(single)
193+
case Nil =>
194+
case mega => group(index)(i => mega.foreach(emit(i)))
195+
}
196+
end Columnator

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ private sealed trait VerboseSettings:
134134
self: SettingGroup =>
135135
val Vhelp: Setting[Boolean] = BooleanSetting("-V", "Print a synopsis of verbose options.")
136136
val Xprint: Setting[List[String]] = PhasesSetting("-Vprint", "Print out program after", aliases = List("-Xprint"))
137+
val XshowPhases: Setting[Boolean] = BooleanSetting("-Vphases", "List compiler phases.", aliases = List("-Xshow-phases"))
137138

138139
/** -W "Warnings" settings
139140
*/
@@ -215,7 +216,6 @@ private sealed trait XSettings:
215216
val XprintInline: Setting[Boolean] = BooleanSetting("-Xprint-inline", "Show where inlined code comes from.")
216217
val XprintSuspension: Setting[Boolean] = BooleanSetting("-Xprint-suspension", "Show when code is suspended until macros are compiled.")
217218
val Xprompt: Setting[Boolean] = BooleanSetting("-Xprompt", "Display a prompt after each error (debugging option).")
218-
val XshowPhases: Setting[Boolean] = BooleanSetting("-Xshow-phases", "Print all compiler phases.")
219219
val XreplDisableDisplay: Setting[Boolean] = BooleanSetting("-Xrepl-disable-display", "Do not display definitions in REPL.")
220220
val XverifySignatures: Setting[Boolean] = BooleanSetting("-Xverify-signatures", "Verify generic signatures in generated bytecode.")
221221
val XignoreScala2Macros: Setting[Boolean] = BooleanSetting("-Xignore-scala2-macros", "Ignore errors when compiling code that calls Scala2 macros, these will fail at runtime.")

compiler/src/dotty/tools/dotc/printing/Highlighting.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ object Highlighting {
1010
abstract class Highlight(private val highlight: String) {
1111
def text: String
1212

13-
def show(using Context): String =
14-
if (ctx.settings.color.value == "never") text
15-
else highlight + text + Console.RESET
13+
def show(using Context): String = if ctx.useColors then highlight + text + Console.RESET else text
1614

1715
override def toString: String =
1816
highlight + text + Console.RESET

compiler/src/dotty/tools/dotc/transform/InterceptedMethods.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import dotty.tools.dotc.transform.MegaPhase.MiniPhase
1212

1313
object InterceptedMethods {
1414
val name: String = "intercepted"
15-
val description: String = "handling of `==`, `|=`, `getClass` methods"
15+
val description: String = "rewrite universal `!=`, `##` methods"
1616
}
1717

1818
/** Replace member references as follows:

compiler/src/dotty/tools/dotc/transform/LiftTry.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,4 +85,4 @@ class LiftTry extends MiniPhase with IdentityDenotTransformer { thisPhase =>
8585
}
8686
object LiftTry:
8787
val name = "liftTry"
88-
val description: String = "Lifts try's that might be executed on non-empty expression stacks"
88+
val description: String = "lift any try that might be executed on a non-empty expression stack"

compiler/src/dotty/tools/dotc/transform/UninitializedDefs.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,6 @@ class UninitializedDefs extends MiniPhase:
4343
end UninitializedDefs
4444

4545
object UninitializedDefs:
46-
val name: String = "uninitializedDefs"
47-
val description: String = "replaces `compiletime.uninitialized` by `_`"
46+
val name: String = "uninitialized"
47+
val description: String = "eliminates `compiletime.uninitialized`"
4848
end UninitializedDefs

compiler/src/dotty/tools/dotc/transform/localopt/StringInterpolatorOpt.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,5 +158,5 @@ class StringInterpolatorOpt extends MiniPhase:
158158
case _ => tree
159159

160160
object StringInterpolatorOpt:
161-
val name: String = "stringInterpolatorOpt"
162-
val description: String = "optimize s, f and raw string interpolators"
161+
val name: String = "interpolators"
162+
val description: String = "optimize s, f, and raw string interpolators"

0 commit comments

Comments
 (0)