@@ -3,7 +3,9 @@ package config
33
44import Settings ._
55import core .Contexts ._
6+ import printing .Highlighting
67
8+ import scala .util .chaining .given
79import scala .PartialFunction .cond
810
911trait 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
0 commit comments