diff options
author | Som Snytt <som.snytt@gmail.com> | 2013-11-15 11:04:09 -0800 |
---|---|---|
committer | Som Snytt <som.snytt@gmail.com> | 2013-11-18 13:39:22 -0800 |
commit | 8f20fa23dbb5b000f0889132b8c6e2acfff096b3 (patch) | |
tree | 8babddcc8944c37d98c5f0d27948a5f1d5440432 /src/repl | |
parent | 02359a09ebb75deee2481d48835d5352b59e1c7e (diff) | |
download | scala-8f20fa23dbb5b000f0889132b8c6e2acfff096b3.tar.gz scala-8f20fa23dbb5b000f0889132b8c6e2acfff096b3.tar.bz2 scala-8f20fa23dbb5b000f0889132b8c6e2acfff096b3.zip |
SI-7969 REPL variable columnar output
Extend column formatting to make columns only as wide as
their widest element. This is similar to what `ls` does.
Given the longest and shortest string, which bound the
min and max column count, compute the layout for those
possible column counts, and choose the minimal row count
and minimal column count.
The junit test is under pending because it uses expecty.
(Edit: updated without expectytations.)
Example that really benefits, witness the skinny columns:
```
scala> math.
BigDecimal PartiallyOrdered cosh rint
BigInt Pi exp round
E ScalaNumber expm1 signum
Equiv ScalaNumericAnyConversions floor sin
Fractional ScalaNumericConversions hypot sinh
IEEEremainder abs log sqrt
Integral acos log10 tan
LowPriorityEquiv asin log1p tanh
LowPriorityOrderingImplicits atan max toDegrees
Numeric atan2 min toRadians
Ordered cbrt package ulp
Ordering ceil pow
PartialOrdering cos random
```
more
Diffstat (limited to 'src/repl')
-rw-r--r-- | src/repl/scala/tools/nsc/interpreter/ConsoleReaderHelper.scala | 72 | ||||
-rw-r--r-- | src/repl/scala/tools/nsc/interpreter/JLineReader.scala | 2 |
2 files changed, 64 insertions, 10 deletions
diff --git a/src/repl/scala/tools/nsc/interpreter/ConsoleReaderHelper.scala b/src/repl/scala/tools/nsc/interpreter/ConsoleReaderHelper.scala index 2206c8a772..d8efcda8b5 100644 --- a/src/repl/scala/tools/nsc/interpreter/ConsoleReaderHelper.scala +++ b/src/repl/scala/tools/nsc/interpreter/ConsoleReaderHelper.scala @@ -63,29 +63,29 @@ trait Tabulator { def width: Int def marginSize: Int - private def fits(items: List[String], width: Int): Boolean = ( + protected def fits(items: Seq[String], width: Int): Boolean = ( (items map (_.length)).sum + (items.length - 1) * marginSize < width ) - def tabulate(items: List[String]): Seq[Seq[String]] = { + def tabulate(items: Seq[String]): Seq[Seq[String]] = ( if (fits(items, width)) Seq(Seq(items mkString " " * marginSize)) else printMultiLineColumns(items) - } - private def printMultiLineColumns(items: List[String]): Seq[Seq[String]] = { + ) + protected def columnize(ss: Seq[String]): Seq[Seq[String]] = ss map (s => Seq(s)) + protected def printMultiLineColumns(items: Seq[String]): Seq[Seq[String]] = { import SimpleMath._ val longest = (items map (_.length)).max - //val shortest = (items map (_.length)).min val columnWidth = longest + marginSize - val maxcols = { + val maxcols = ( if (columnWidth >= width) 1 else 1 max (width / columnWidth) // make sure it doesn't divide to 0 - } + ) val nrows = items.size /% maxcols val ncols = items.size /% nrows val groupSize = ncols val padded = items map (s"%-${columnWidth}s" format _) - val xwise = isAcross || ncols > items.length + val xwise = isAcross || ncols >= items.length val grouped: Seq[Seq[String]] = - if (groupSize == 1) Seq(items) + if (groupSize == 1) columnize(items) else if (xwise) (padded grouped groupSize).toSeq else { val h = 1 max padded.size /% groupSize @@ -98,6 +98,60 @@ trait Tabulator { } } +/** Adjust the column width and number of columns to minimize the row count. */ +trait VariColumnTabulator extends Tabulator { + override protected def printMultiLineColumns(items: Seq[String]): Seq[Seq[String]] = { + import SimpleMath._ + val longest = (items map (_.length)).max + val shortest = (items map (_.length)).min + val fattest = longest + marginSize + val skinny = shortest + marginSize + + // given ncols, calculate nrows and a list of column widths, or none if not possible + // if ncols > items.size, then columnWidths.size == items.size + def layout(ncols: Int): Option[(Int, Seq[Int], Seq[Seq[String]])] = { + val nrows = items.size /% ncols + val xwise = isAcross || ncols >= items.length + def maxima(sss: Seq[Seq[String]]) = + (0 until (ncols min items.size)) map (i => (sss map (ss => ss(i).length)).max) + def resulting(rows: Seq[Seq[String]]) = { + val columnWidths = maxima(rows) map (_ + marginSize) + val linelen = columnWidths.sum + if (linelen <= width) Some((nrows, columnWidths, rows)) + else None + } + if (ncols == 1) resulting(columnize(items)) + else if (xwise) resulting((items grouped ncols).toSeq) + else { + val cols = (items grouped nrows).toList + val rows = for (i <- 0 until nrows) yield + for (j <- 0 until ncols) yield + if (j < cols.size && i < cols(j).size) cols(j)(i) else "" + resulting(rows) + } + } + + if (fattest >= width) { + columnize(items) + } else { + // if every col is widest, we have at least this many cols + val mincols = 1 max (width / fattest) + // if every other col is skinniest, we have at most this many cols + val maxcols = 1 + ((width - fattest) / skinny) + val possibles = (mincols to maxcols).map(n => layout(n)).flatten + val minrows = (possibles map (_._1)).min + + // select the min ncols that results in minrows + val (_, columnWidths, sss) = (possibles find (_._1 == minrows)).get + + // format to column width + sss map (ss => ss.zipWithIndex map { + case (s, i) => s"%-${columnWidths(i)}s" format s + }) + } + } +} + private[interpreter] object SimpleMath { implicit class DivRem(private val i: Int) extends AnyVal { /** i/n + if (i % n != 0) 1 else 0 */ diff --git a/src/repl/scala/tools/nsc/interpreter/JLineReader.scala b/src/repl/scala/tools/nsc/interpreter/JLineReader.scala index e50c7d3e0a..b6e834a1ed 100644 --- a/src/repl/scala/tools/nsc/interpreter/JLineReader.scala +++ b/src/repl/scala/tools/nsc/interpreter/JLineReader.scala @@ -33,7 +33,7 @@ class JLineReader(_completion: => Completion) extends InteractiveReader { } } - class JLineConsoleReader extends ConsoleReader with ConsoleReaderHelper with Tabulator { + class JLineConsoleReader extends ConsoleReader with ConsoleReaderHelper with VariColumnTabulator { val isAcross = interpreter.`package`.isAcross this setPaginationEnabled interpreter.`package`.isPaged |