Skip to content

Use color for showing megaphases #14394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 73 additions & 67 deletions compiler/src/dotty/tools/dotc/config/CliCommand.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -49,59 +51,31 @@ 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("@<file>")} ${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) :+ ("@<file>", "A text file containing compiler arguments (options and source files).")))(using fresh)
end availableOptionsMsg

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

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)

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def formatField1(text: String): String = if text.length <= field1 then text.padLeft(field1) else text + EOL + "".padLeft(field1)
def formatField1(text: String): String = if text.length <= field1 then text.padLeft(field1) else text + "\n" + "".padLeft(field1)

We had some issues with EOL and Windows in the past. The "\n" seems to be what we should use by default.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have followed the issue, but usually if I'm on cygwin I want the correct line separator. I'll try to come up with a use case slash test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to come up with a use case slash test.
Maybe if I start using cygwin again on a regular basis...

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
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this change made?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is alignment with scala 2.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alias means -Xshow-phases is still supported. I've considered it would be nice if help text showed aliases, perhaps under -verbose -help.


/** -W "Warnings" settings
*/
Expand Down Expand Up @@ -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.")
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/printing/Highlighting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/transform/LiftTry.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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"