mirror of https://github.com/sbt/sbt.git
move super shell rendering to ConsoleAppender
Ref https://github.com/sbt/sbt/issues/4583 This moves the super shell rendering to ConsoleAppender with several improvements. Instead of scrolling up, supershell is now changed to normal scrolling down, with more traditional cursor position. Before printing out the logs, last known progress reports are wiped out. In addition, there's now 5 lines of blank lines to accomodate for `println(...)` by tasks.
This commit is contained in:
parent
98ec0075f4
commit
e28e052b5b
|
|
@ -124,6 +124,8 @@ lazy val utilLogging = (project in internalPath / "util-logging")
|
|||
exclude[ReversedMissingMethodProblem]("sbt.internal.util.ConsoleOut.flush"),
|
||||
// This affects Scala 2.11 only it seems, so it's ok?
|
||||
exclude[InheritedNewAbstractMethodProblem]("sbt.internal.util.codec.JsonProtocol.LogOptionFormat"),
|
||||
exclude[InheritedNewAbstractMethodProblem]("sbt.internal.util.codec.JsonProtocol.ProgressItemFormat"),
|
||||
exclude[InheritedNewAbstractMethodProblem]("sbt.internal.util.codec.JsonProtocol.ProgressEventFormat"),
|
||||
),
|
||||
)
|
||||
.configure(addSbtIO)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.internal.util
|
||||
/** used by super shell */
|
||||
final class ProgressEvent private (
|
||||
val level: String,
|
||||
val items: Vector[sbt.internal.util.ProgressItem],
|
||||
val lastTaskCount: Option[Int],
|
||||
channelName: Option[String],
|
||||
execId: Option[String]) extends sbt.internal.util.AbstractEntry(channelName, execId) with Serializable {
|
||||
|
||||
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case x: ProgressEvent => (this.level == x.level) && (this.items == x.items) && (this.lastTaskCount == x.lastTaskCount) && (this.channelName == x.channelName) && (this.execId == x.execId)
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
37 * (37 * (37 * (37 * (37 * (37 * (17 + "sbt.internal.util.ProgressEvent".##) + level.##) + items.##) + lastTaskCount.##) + channelName.##) + execId.##)
|
||||
}
|
||||
override def toString: String = {
|
||||
"ProgressEvent(" + level + ", " + items + ", " + lastTaskCount + ", " + channelName + ", " + execId + ")"
|
||||
}
|
||||
private[this] def copy(level: String = level, items: Vector[sbt.internal.util.ProgressItem] = items, lastTaskCount: Option[Int] = lastTaskCount, channelName: Option[String] = channelName, execId: Option[String] = execId): ProgressEvent = {
|
||||
new ProgressEvent(level, items, lastTaskCount, channelName, execId)
|
||||
}
|
||||
def withLevel(level: String): ProgressEvent = {
|
||||
copy(level = level)
|
||||
}
|
||||
def withItems(items: Vector[sbt.internal.util.ProgressItem]): ProgressEvent = {
|
||||
copy(items = items)
|
||||
}
|
||||
def withLastTaskCount(lastTaskCount: Option[Int]): ProgressEvent = {
|
||||
copy(lastTaskCount = lastTaskCount)
|
||||
}
|
||||
def withLastTaskCount(lastTaskCount: Int): ProgressEvent = {
|
||||
copy(lastTaskCount = Option(lastTaskCount))
|
||||
}
|
||||
def withChannelName(channelName: Option[String]): ProgressEvent = {
|
||||
copy(channelName = channelName)
|
||||
}
|
||||
def withChannelName(channelName: String): ProgressEvent = {
|
||||
copy(channelName = Option(channelName))
|
||||
}
|
||||
def withExecId(execId: Option[String]): ProgressEvent = {
|
||||
copy(execId = execId)
|
||||
}
|
||||
def withExecId(execId: String): ProgressEvent = {
|
||||
copy(execId = Option(execId))
|
||||
}
|
||||
}
|
||||
object ProgressEvent {
|
||||
|
||||
def apply(level: String, items: Vector[sbt.internal.util.ProgressItem], lastTaskCount: Option[Int], channelName: Option[String], execId: Option[String]): ProgressEvent = new ProgressEvent(level, items, lastTaskCount, channelName, execId)
|
||||
def apply(level: String, items: Vector[sbt.internal.util.ProgressItem], lastTaskCount: Int, channelName: String, execId: String): ProgressEvent = new ProgressEvent(level, items, Option(lastTaskCount), Option(channelName), Option(execId))
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.internal.util
|
||||
/**
|
||||
* used by super shell
|
||||
* @param name name of a task
|
||||
* @param elapsedMicros current elapsed time in micro seconds
|
||||
*/
|
||||
final class ProgressItem private (
|
||||
val name: String,
|
||||
val elapsedMicros: Long) extends Serializable {
|
||||
|
||||
|
||||
|
||||
override def equals(o: Any): Boolean = o match {
|
||||
case x: ProgressItem => (this.name == x.name) && (this.elapsedMicros == x.elapsedMicros)
|
||||
case _ => false
|
||||
}
|
||||
override def hashCode: Int = {
|
||||
37 * (37 * (37 * (17 + "sbt.internal.util.ProgressItem".##) + name.##) + elapsedMicros.##)
|
||||
}
|
||||
override def toString: String = {
|
||||
"ProgressItem(" + name + ", " + elapsedMicros + ")"
|
||||
}
|
||||
private[this] def copy(name: String = name, elapsedMicros: Long = elapsedMicros): ProgressItem = {
|
||||
new ProgressItem(name, elapsedMicros)
|
||||
}
|
||||
def withName(name: String): ProgressItem = {
|
||||
copy(name = name)
|
||||
}
|
||||
def withElapsedMicros(elapsedMicros: Long): ProgressItem = {
|
||||
copy(elapsedMicros = elapsedMicros)
|
||||
}
|
||||
}
|
||||
object ProgressItem {
|
||||
|
||||
def apply(name: String, elapsedMicros: Long): ProgressItem = new ProgressItem(name, elapsedMicros)
|
||||
}
|
||||
|
|
@ -6,6 +6,6 @@
|
|||
package sbt.internal.util.codec
|
||||
|
||||
import _root_.sjsonnew.JsonFormat
|
||||
trait AbstractEntryFormats { self: sjsonnew.BasicJsonProtocol with sbt.internal.util.codec.StringEventFormats with sbt.internal.util.codec.TraceEventFormats =>
|
||||
implicit lazy val AbstractEntryFormat: JsonFormat[sbt.internal.util.AbstractEntry] = flatUnionFormat2[sbt.internal.util.AbstractEntry, sbt.internal.util.StringEvent, sbt.internal.util.TraceEvent]("type")
|
||||
trait AbstractEntryFormats { self: sjsonnew.BasicJsonProtocol with sbt.internal.util.codec.StringEventFormats with sbt.internal.util.codec.TraceEventFormats with sbt.internal.util.codec.ProgressItemFormats with sbt.internal.util.codec.ProgressEventFormats =>
|
||||
implicit lazy val AbstractEntryFormat: JsonFormat[sbt.internal.util.AbstractEntry] = flatUnionFormat3[sbt.internal.util.AbstractEntry, sbt.internal.util.StringEvent, sbt.internal.util.TraceEvent, sbt.internal.util.ProgressEvent]("type")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ package sbt.internal.util.codec
|
|||
trait JsonProtocol extends sjsonnew.BasicJsonProtocol
|
||||
with sbt.internal.util.codec.StringEventFormats
|
||||
with sbt.internal.util.codec.TraceEventFormats
|
||||
with sbt.internal.util.codec.ProgressItemFormats
|
||||
with sbt.internal.util.codec.ProgressEventFormats
|
||||
with sbt.internal.util.codec.AbstractEntryFormats
|
||||
with sbt.internal.util.codec.SuccessEventFormats
|
||||
with sbt.internal.util.codec.LogOptionFormats
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.internal.util.codec
|
||||
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
|
||||
trait ProgressEventFormats { self: sbt.internal.util.codec.ProgressItemFormats with sjsonnew.BasicJsonProtocol =>
|
||||
implicit lazy val ProgressEventFormat: JsonFormat[sbt.internal.util.ProgressEvent] = new JsonFormat[sbt.internal.util.ProgressEvent] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.util.ProgressEvent = {
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
unbuilder.beginObject(js)
|
||||
val level = unbuilder.readField[String]("level")
|
||||
val items = unbuilder.readField[Vector[sbt.internal.util.ProgressItem]]("items")
|
||||
val lastTaskCount = unbuilder.readField[Option[Int]]("lastTaskCount")
|
||||
val channelName = unbuilder.readField[Option[String]]("channelName")
|
||||
val execId = unbuilder.readField[Option[String]]("execId")
|
||||
unbuilder.endObject()
|
||||
sbt.internal.util.ProgressEvent(level, items, lastTaskCount, channelName, execId)
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
}
|
||||
override def write[J](obj: sbt.internal.util.ProgressEvent, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
builder.addField("level", obj.level)
|
||||
builder.addField("items", obj.items)
|
||||
builder.addField("lastTaskCount", obj.lastTaskCount)
|
||||
builder.addField("channelName", obj.channelName)
|
||||
builder.addField("execId", obj.execId)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.internal.util.codec
|
||||
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
|
||||
trait ProgressItemFormats { self: sjsonnew.BasicJsonProtocol =>
|
||||
implicit lazy val ProgressItemFormat: JsonFormat[sbt.internal.util.ProgressItem] = new JsonFormat[sbt.internal.util.ProgressItem] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.util.ProgressItem = {
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
unbuilder.beginObject(js)
|
||||
val name = unbuilder.readField[String]("name")
|
||||
val elapsedMicros = unbuilder.readField[Long]("elapsedMicros")
|
||||
unbuilder.endObject()
|
||||
sbt.internal.util.ProgressItem(name, elapsedMicros)
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
}
|
||||
override def write[J](obj: sbt.internal.util.ProgressItem, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
builder.addField("name", obj.name)
|
||||
builder.addField("elapsedMicros", obj.elapsedMicros)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt.internal.util.codec
|
||||
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
|
||||
trait TaskProgressFormats { self: sjsonnew.BasicJsonProtocol =>
|
||||
implicit lazy val TaskProgressFormat: JsonFormat[sbt.internal.util.TaskProgress] = new JsonFormat[sbt.internal.util.TaskProgress] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.util.TaskProgress = {
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
unbuilder.beginObject(js)
|
||||
val name = unbuilder.readField[String]("name")
|
||||
val elapsedMicros = unbuilder.readField[Option[Long]]("elapsedMicros")
|
||||
unbuilder.endObject()
|
||||
sbt.internal.util.TaskProgress(name, elapsedMicros)
|
||||
case None =>
|
||||
deserializationError("Expected JsObject but found None")
|
||||
}
|
||||
}
|
||||
override def write[J](obj: sbt.internal.util.TaskProgress, builder: Builder[J]): Unit = {
|
||||
builder.beginObject()
|
||||
builder.addField("name", obj.name)
|
||||
builder.addField("elapsedMicros", obj.elapsedMicros)
|
||||
builder.endObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,23 @@ type TraceEvent implements sbt.internal.util.AbstractEntry {
|
|||
execId: String
|
||||
}
|
||||
|
||||
## used by super shell
|
||||
type ProgressEvent implements sbt.internal.util.AbstractEntry {
|
||||
level: String!
|
||||
items: [sbt.internal.util.ProgressItem]
|
||||
lastTaskCount: Int
|
||||
channelName: String
|
||||
execId: String
|
||||
}
|
||||
|
||||
## used by super shell
|
||||
type ProgressItem {
|
||||
## name of a task
|
||||
name: String!
|
||||
## current elapsed time in micro seconds
|
||||
elapsedMicros: Long!
|
||||
}
|
||||
|
||||
type SuccessEvent {
|
||||
message: String!
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,18 +96,19 @@ class ConsoleLogger private[ConsoleLogger] (
|
|||
}
|
||||
|
||||
object ConsoleAppender {
|
||||
private[sbt] final val ScrollUp = "\u001B[S"
|
||||
private[sbt] def cursorUp(n: Int): String = s"\u001B[${n}A"
|
||||
private[sbt] def cursorDown(n: Int): String = s"\u001B[${n}B"
|
||||
private[sbt] def scrollUp(n: Int): String = s"\u001B[${n}S"
|
||||
private[sbt] final val DeleteLine = "\u001B[2K"
|
||||
private[sbt] final val CursorLeft1000 = "\u001B[1000D"
|
||||
private[sbt] final val CursorDown1 = cursorDown(1)
|
||||
private[this] val widthHolder: AtomicInteger = new AtomicInteger
|
||||
private[sbt] def terminalWidth = widthHolder.get
|
||||
private[sbt] def setTerminalWidth(n: Int): Unit = widthHolder.set(n)
|
||||
private[this] val showProgressHolder: AtomicBoolean = new AtomicBoolean(false)
|
||||
def setShowProgress(b: Boolean): Unit = showProgressHolder.set(b)
|
||||
def showProgress: Boolean = showProgressHolder.get
|
||||
private[sbt] val lastTaskCount = new AtomicInteger(0)
|
||||
|
||||
/** Hide stack trace altogether. */
|
||||
val noSuppressedMessage = (_: SuppressedTraceContext) => None
|
||||
|
|
@ -454,23 +455,18 @@ class ConsoleAppender private[ConsoleAppender] (
|
|||
appendLog(SUCCESS_LABEL_COLOR, Level.SuccessLabel, SUCCESS_MESSAGE_COLOR, message)
|
||||
}
|
||||
|
||||
// leave some blank lines for tasks that might use println(...)
|
||||
private val blankZone = 5
|
||||
private def write(msg: String): Unit = {
|
||||
if (!useFormat || !ansiCodesSupported) {
|
||||
out.println(EscHelpers.removeEscapeSequences(msg))
|
||||
} else if (ConsoleAppender.showProgress) {
|
||||
val textLength = msg.length - 5
|
||||
val scrollNum =
|
||||
if (ConsoleAppender.terminalWidth == 0) 1
|
||||
else (textLength / ConsoleAppender.terminalWidth) + 1
|
||||
if (scrollNum > 1) {
|
||||
out.print(s"${cursorDown(1)}$DeleteLine" * (scrollNum - 1) + s"${cursorUp(scrollNum - 1)}")
|
||||
val clearNum = lastTaskCount.get + blankZone
|
||||
if (clearNum > 1) {
|
||||
deleteConsoleLines(clearNum)
|
||||
out.print(s"${cursorUp(clearNum)}")
|
||||
}
|
||||
out.print(
|
||||
s"$ScrollUp$DeleteLine$msg${CursorLeft1000}" + (
|
||||
if (scrollNum <= 1) ""
|
||||
else scrollUp(scrollNum - 1)
|
||||
)
|
||||
)
|
||||
out.println(msg)
|
||||
out.flush()
|
||||
} else {
|
||||
out.println(msg)
|
||||
|
|
@ -497,19 +493,50 @@ class ConsoleAppender private[ConsoleAppender] (
|
|||
codec.showLines(te).toVector foreach { appendLog(Level.Error, _) }
|
||||
}
|
||||
|
||||
private def appendProgressEvent(pe: ProgressEvent): Unit =
|
||||
if (ConsoleAppender.showProgress) {
|
||||
out.lockObject.synchronized {
|
||||
deleteConsoleLines(blankZone)
|
||||
val currentTasksCount = pe.items.size
|
||||
val ltc = pe.lastTaskCount.getOrElse(0)
|
||||
val sorted = pe.items.sortBy(_.name).sortBy(x => -x.elapsedMicros)
|
||||
sorted foreach { item =>
|
||||
val elapsed = item.elapsedMicros / 1000000L
|
||||
out.println(s"$DeleteLine | => ${item.name} ${elapsed}s")
|
||||
}
|
||||
if (ltc > currentTasksCount) deleteConsoleLines(ltc - currentTasksCount)
|
||||
else ()
|
||||
out.print(cursorUp(math.max(currentTasksCount, ltc) + blankZone))
|
||||
out.flush()
|
||||
lastTaskCount.set(ltc)
|
||||
}
|
||||
} else ()
|
||||
|
||||
private def deleteConsoleLines(n: Int): Unit = {
|
||||
(1 to n) foreach { _ =>
|
||||
out.println(DeleteLine)
|
||||
}
|
||||
}
|
||||
|
||||
private def appendMessageContent(level: Level.Value, o: AnyRef): Unit = {
|
||||
def appendEvent(oe: ObjectEvent[_]): Unit = {
|
||||
val contentType = oe.contentType
|
||||
if (contentType == "sbt.internal.util.TraceEvent") {
|
||||
appendTraceEvent(oe.message.asInstanceOf[TraceEvent])
|
||||
} else
|
||||
LogExchange.stringCodec[AnyRef](contentType) match {
|
||||
case Some(codec) if contentType == "sbt.internal.util.SuccessEvent" =>
|
||||
codec.showLines(oe.message.asInstanceOf[AnyRef]).toVector foreach { success(_) }
|
||||
case Some(codec) =>
|
||||
codec.showLines(oe.message.asInstanceOf[AnyRef]).toVector foreach (appendLog(level, _))
|
||||
case _ => appendLog(level, oe.message.toString)
|
||||
}
|
||||
contentType match {
|
||||
case "sbt.internal.util.TraceEvent" => appendTraceEvent(oe.message.asInstanceOf[TraceEvent])
|
||||
case "sbt.internal.util.ProgressEvent" =>
|
||||
appendProgressEvent(oe.message.asInstanceOf[ProgressEvent])
|
||||
case _ =>
|
||||
LogExchange.stringCodec[AnyRef](contentType) match {
|
||||
case Some(codec) if contentType == "sbt.internal.util.SuccessEvent" =>
|
||||
codec.showLines(oe.message.asInstanceOf[AnyRef]).toVector foreach { success(_) }
|
||||
case Some(codec) =>
|
||||
codec.showLines(oe.message.asInstanceOf[AnyRef]).toVector foreach (appendLog(
|
||||
level,
|
||||
_
|
||||
))
|
||||
case _ => appendLog(level, oe.message.toString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
o match {
|
||||
|
|
|
|||
Loading…
Reference in New Issue