@@ -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 :
@@ -52,7 +54,7 @@ trait CliCommand:
5254 protected def availableOptionsMsg (cond : Setting [? ] => Boolean )(using settings : ConcreteSettings )(using SettingsState ): String =
5355 val ss = (settings.allSettings filter cond).toList sortBy (_.name)
5456 val maxNameWidth = 30
55- val nameWidths = ss.map(_.name.length).partition (_ < maxNameWidth)._1
57+ val nameWidths = ss.map(_.name.length).filter (_ < maxNameWidth)
5658 val width = if nameWidths.nonEmpty then nameWidths.max else maxNameWidth
5759 val terminalWidth = settings.pageWidth.value
5860 val (nameWidth, descriptionWidth) = {
@@ -136,39 +138,10 @@ trait CliCommand:
136138 createUsageMsg(" Possible private" , shouldExplain = true , isPrivate)
137139
138140 /** Used for the formatted output of -Xshow-phases */
139- protected def phasesMessage (using ctx : Context ): String =
140-
141- import scala .io .AnsiColor .*
142- val colors = Array (GREEN , YELLOW , /* BLUE,*/ MAGENTA , CYAN , RED )
141+ protected def phasesMessage (using Context ): String =
143142 val phases = new Compiler ().phases
144- val nameLimit = 25
145- val maxCol = ctx.settings.pageWidth.value
146- val maxName = phases.flatten.map(_.phaseName.length).max
147- val width = maxName.min(nameLimit)
148- val maxDesc = maxCol - (width + 6 )
149- val colorSlot = if ctx.useColors then GREEN .length.toString else " 0"
150- val fmt = s " %. ${colorSlot}s% ${width}. ${width}s%. ${colorSlot}s %. ${maxDesc}s%n "
151- def plain (name : String , description : String ) = fmt.format(" " , name, " " , description)
152-
153- val sb = new StringBuilder
154- sb ++= plain(" phase name" , " description" )
155- sb ++= plain(" ----------" , " -----------" )
156-
157- def color (index : Int ): String = colors(index % colors.length)
158- def emit (index : Int )(phase : core.Phases .Phase ): Unit = sb ++= fmt.format(color(index), phase.phaseName, RESET , phase.description)
159- def group (index : Int )(body : Int => Unit ): Unit =
160- if ! ctx.useColors then sb ++= plain(s " { " , " " )
161- body(index)
162- if ! ctx.useColors then sb ++= plain(s " } " , " " )
163-
164- phases.zipWithIndex.foreach { (phase, index) =>
165- phase match
166- case List (single) => emit(index)(single)
167- case Nil =>
168- case mega => group(index)(i => mega.foreach(emit(i)))
169- }
170- sb.mkString
171-
143+ val formatter = Columnator (" phase name" , " description" , maxField = 25 , separation = 2 )
144+ formatter(phases.map(mega => mega.map(p => (p.phaseName, p.description))))
172145
173146 /** Provide usage feedback on argument summary, assuming that all settings
174147 * are already applied in context.
@@ -196,3 +169,55 @@ trait CliCommand:
196169
197170 extension [T ](setting : Setting [T ])
198171 protected def value (using ss : SettingsState ): T = setting.valueIn(ss)
172+
173+ extension (s : String )
174+ def padLeft (width : Int ): String = StringBuilder ().tap(_.append(" " * (width - s.length)).append(s)).toString
175+
176+ // Formatting for -help and -Vphases in two columns, handling long field1 and wrapping long field2
177+ class Columnator (heading1 : String , heading2 : String , maxField : Int , separation : Int = 1 ):
178+ def apply (texts : List [List [(String , String )]])(using Context ): String = StringBuilder ().tap(columnate(_, texts)).toString
179+
180+ private def columnate (sb : StringBuilder , texts : List [List [(String , String )]])(using Context ): Unit =
181+ import scala .util .Properties .{lineSeparator => EOL }
182+ import Highlighting .*
183+ val colors = Seq (Green (_), Yellow (_), Magenta (_), Cyan (_), Red (_))
184+ val nocolor = texts.length == 1
185+ def color (index : Int ): String => Highlight = if nocolor then NoColor (_) else colors(index % colors.length)
186+ val maxCol = ctx.settings.pageWidth.value
187+ val field1 = maxField.min(texts.flatten.map(_._1.length).filter(_ < maxField).max) // widest field under maxField
188+ val field2 = if field1 + separation + maxField < maxCol then maxCol - field1 - separation else 0 // skinny window -> terminal wrap
189+ val separator = " " * separation
190+ def formatField1 (text : String ): String = if text.length <= field1 then text.padLeft(field1) else EOL + " " .padLeft(field1)
191+ def formatField2 (text : String ): String =
192+ if field2 == 0 || text.length <= field2 then text
193+ else
194+ text.lastIndexOf(" " , field2) match
195+ case - 1 => text
196+ case i =>
197+ val (prefix, rest) = text.splitAt(i)
198+ s " ${prefix}${EOL }${formatField1(" " )}${separator}${formatField2(rest.trim)}"
199+ def format (first : String , second : String , index : Int , colorPicker : Int => String => Highlight ) =
200+ sb.append(colorPicker(index)(formatField1(first)).show)
201+ .append(separator)
202+ .append(formatField2(second))
203+ .append(EOL ): Unit
204+ def fancy (first : String , second : String , index : Int ) = format(first, second, index, color)
205+ def plain (first : String , second : String ) = format(first, second, 0 , _ => NoColor (_))
206+
207+ if heading1.nonEmpty then
208+ plain(heading1, heading2)
209+ plain(" -" * heading1.length, " -" * heading2.length)
210+
211+ def emit (index : Int )(textPair : (String , String )): Unit = fancy(textPair._1, textPair._2, index)
212+ def group (index : Int )(body : Int => Unit ): Unit =
213+ if ! ctx.useColors then plain(s " { " , " " )
214+ body(index)
215+ if ! ctx.useColors then plain(s " } " , " " )
216+
217+ texts.zipWithIndex.foreach { (text, index) =>
218+ text match
219+ case List (single) => emit(index)(single)
220+ case Nil =>
221+ case mega => group(index)(i => mega.foreach(emit(i)))
222+ }
223+ end Columnator
0 commit comments