From d58b302a97774900fcfa25104fbd7f46b3394cdf Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 16 Nov 2018 23:05:51 -0800 Subject: [PATCH 1/4] Fix prompt for task progress --- build.sbt | 2 +- .../main/scala/sbt/internal/util/LineReader.scala | 15 +++++++++++---- .../src/main/scala/sbt/internal/Aggregation.scala | 6 ++---- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/build.sbt b/build.sbt index 0bcabf00a..5f3ee5142 100644 --- a/build.sbt +++ b/build.sbt @@ -216,7 +216,7 @@ val completeProj = (project in file("internal") / "util-complete") exclude[DirectMissingMethodProblem]("sbt.internal.util.complete.History.this"), ), ) - .configure(addSbtIO, addSbtUtilControl) + .configure(addSbtIO, addSbtUtilControl, addSbtUtilLogging) // A logic with restricted negation as failure for a unique, stable model val logicProj = (project in file("internal") / "util-logic") diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala b/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala index 0a22b2843..3681cbc03 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala @@ -64,16 +64,23 @@ abstract class JLine extends LineReader { } private[this] def handleMultilinePrompt(prompt: String): String = { - val lines = """\r?\n""".r.split(prompt) - lines.length match { - case 0 | 1 => prompt - case _ => + val lines0 = """\r?\n""".r.split(prompt) + lines0.length match { + case 0 | 1 => handleProgress(prompt) + case _ => + val lines = lines0.toList map handleProgress // Workaround for regression jline/jline2#205 reader.getOutput.write(lines.init.mkString("\n") + "\n") lines.last } } + private[this] def handleProgress(prompt: String): String = { + import ConsoleAppender._ + if (showProgress) s"$ScrollUp$DeleteLine" + prompt + else prompt + } + private[this] def resume(): Unit = { jline.TerminalFactory.reset JLine.terminal.init diff --git a/main/src/main/scala/sbt/internal/Aggregation.scala b/main/src/main/scala/sbt/internal/Aggregation.scala index 833738549..83b3ce6c2 100644 --- a/main/src/main/scala/sbt/internal/Aggregation.scala +++ b/main/src/main/scala/sbt/internal/Aggregation.scala @@ -77,8 +77,6 @@ object Aggregation { if (show.taskValues) printSettings(r, show.print) } if (show.success) printSuccess(start, stop, extracted, success, log) - // wait for async logger to catch up - Thread.sleep(100) } def timedRun[T]( @@ -131,9 +129,9 @@ object Aggregation { if (get(showSuccess)) { if (get(showTiming)) { val msg = timingString(start, stop, structure.data, currentRef) - if (success) log.success(msg + "\n") else log.error(msg + "\n") + if (success) log.success(msg) else log.error(msg) } else if (success) - log.success("" + "\n") + log.success("") } } From b00dcb1b9d56a576bdd675b1c52e6c3ce6f1185e Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Fri, 16 Nov 2018 23:06:36 -0800 Subject: [PATCH 2/4] Fix task progress blank line behavior Run readyLog lazily. --- .../scala/sbt/internal/TaskProgress.scala | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/main/src/main/scala/sbt/internal/TaskProgress.scala b/main/src/main/scala/sbt/internal/TaskProgress.scala index 641f3a932..04e792510 100644 --- a/main/src/main/scala/sbt/internal/TaskProgress.scala +++ b/main/src/main/scala/sbt/internal/TaskProgress.scala @@ -29,6 +29,7 @@ private[sbt] final class TaskProgress(currentRef: ProjectRef) extends ExecutePro private[this] val calledBy = new ConcurrentHashMap[Task[_], Task[_]] private[this] val anonOwners = new ConcurrentHashMap[Task[_], Task[_]] private[this] val isReady = new AtomicBoolean(false) + private[this] val isLogReady = new AtomicBoolean(false) private[this] val isAllCompleted = new AtomicBoolean(false) override def initial: Unit = () @@ -75,7 +76,6 @@ private[sbt] final class TaskProgress(currentRef: ProjectRef) extends ExecutePro Thread.sleep(500) } } - readyLog() while (!isAllCompleted.get) { blocking { report() @@ -86,13 +86,16 @@ private[sbt] final class TaskProgress(currentRef: ProjectRef) extends ExecutePro private[this] val console = ConsoleOut.systemOut private[this] def readyLog(): Unit = { - console.println("") - console.println("") - console.println("") - console.println("") - console.println("") - console.println("") - console.print(CursorUp5) + if (isLogReady.get) () + else { + console.println("") + console.println("") + console.println("") + console.println("") + console.println("") + console.print(CursorUp5) + isLogReady.set(true) + } } private[this] val stopReportTask = @@ -100,6 +103,7 @@ private[sbt] final class TaskProgress(currentRef: ProjectRef) extends ExecutePro private[this] def report(): Unit = console.lockObject.synchronized { val currentTasks = activeTasks.asScala.toList def report0: Unit = { + readyLog() console.print(s"$CursorDown1") currentTasks foreach { case (task, start) => From 44074983bc1d98ca49e94b3874ca7a28f397b76f Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sun, 18 Nov 2018 12:12:38 -0500 Subject: [PATCH 3/4] util 1.3.0-M3 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index d46c1de56..0535f269c 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -10,7 +10,7 @@ object Dependencies { // sbt modules private val ioVersion = "1.3.0-M3" - private val utilVersion = "1.3.0-M2" + private val utilVersion = "1.3.0-M3" private val lmVersion = sys.props.get("sbt.build.lm.version") match { case Some(version) => version From 9858bc29fbd382b45d63cbcace2429a09be61479 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Thu, 29 Nov 2018 04:40:40 -0500 Subject: [PATCH 4/4] more tweaking on task progress move the cursor up after the user enters command. --- .../scala/sbt/internal/util/LineReader.scala | 10 ++- .../scala/sbt/internal/TaskProgress.scala | 78 ++++++++++++------- 2 files changed, 60 insertions(+), 28 deletions(-) diff --git a/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala b/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala index 3681cbc03..0e9c0ed41 100644 --- a/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala +++ b/internal/util-complete/src/main/scala/sbt/internal/util/LineReader.scala @@ -55,12 +55,20 @@ abstract class JLine extends LineReader { else readLineDirectRaw(prompt, mask) + private[this] val console = ConsoleOut.systemOut private[this] def readLineDirectRaw(prompt: String, mask: Option[Char]): Option[String] = { val newprompt = handleMultilinePrompt(prompt) - mask match { + val result = mask match { case Some(m) => Option(reader.readLine(newprompt, m)) case None => Option(reader.readLine(newprompt)) } + + // since the task progress scrolls the logs upward, and expects the cursors to be on + // the last text line, this moves the cursor up to complensate for user hitting return. + val CursorUp1 = s"\u001B[A" + if (ConsoleAppender.showProgress) console.print(CursorUp1) + else () + result } private[this] def handleMultilinePrompt(prompt: String): String = { diff --git a/main/src/main/scala/sbt/internal/TaskProgress.scala b/main/src/main/scala/sbt/internal/TaskProgress.scala index 04e792510..6e4913750 100644 --- a/main/src/main/scala/sbt/internal/TaskProgress.scala +++ b/main/src/main/scala/sbt/internal/TaskProgress.scala @@ -11,7 +11,7 @@ package internal import sbt.internal.util.{ RMap, ConsoleOut } import java.util.concurrent.ConcurrentHashMap import scala.concurrent.{ blocking, Future, ExecutionContext } -import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.{ AtomicBoolean, AtomicInteger } import scala.collection.JavaConverters._ import scala.collection.concurrent.TrieMap import TaskProgress._ @@ -29,7 +29,7 @@ private[sbt] final class TaskProgress(currentRef: ProjectRef) extends ExecutePro private[this] val calledBy = new ConcurrentHashMap[Task[_], Task[_]] private[this] val anonOwners = new ConcurrentHashMap[Task[_], Task[_]] private[this] val isReady = new AtomicBoolean(false) - private[this] val isLogReady = new AtomicBoolean(false) + private[this] val lastTaskCount = new AtomicInteger(0) private[this] val isAllCompleted = new AtomicBoolean(false) override def initial: Unit = () @@ -56,8 +56,9 @@ private[sbt] final class TaskProgress(currentRef: ProjectRef) extends ExecutePro } override def workFinished[A](task: Task[A], result: Either[Task[A], Result[A]]): Unit = { + val start = activeTasks.get(task) + timings.put(task, System.nanoTime - start) activeTasks.remove(task) - timings.put(task, System.nanoTime - activeTasks.get(task)) // we need this to infer anonymous task names result.left.foreach { t => calledBy.put(t, task) @@ -65,9 +66,6 @@ private[sbt] final class TaskProgress(currentRef: ProjectRef) extends ExecutePro } override def completed[A](state: Unit, task: Task[A], result: Result[A]): Unit = () - override def allCompleted(state: Unit, results: RMap[Task, Result]): Unit = { - isAllCompleted.set(true) - } import ExecutionContext.Implicits._ Future { @@ -85,38 +83,65 @@ private[sbt] final class TaskProgress(currentRef: ProjectRef) extends ExecutePro } private[this] val console = ConsoleOut.systemOut - private[this] def readyLog(): Unit = { - if (isLogReady.get) () - else { - console.println("") - console.println("") - console.println("") - console.println("") - console.println("") - console.print(CursorUp5) - isLogReady.set(true) - } + override def allCompleted(state: Unit, results: RMap[Task, Result]): Unit = { + isAllCompleted.set(true) + // completionReport() } - - private[this] val stopReportTask = + private[this] val skipReportTasks = Set("run", "bgRun", "fgRun", "scala", "console", "consoleProject") private[this] def report(): Unit = console.lockObject.synchronized { val currentTasks = activeTasks.asScala.toList - def report0: Unit = { - readyLog() + val ltc = lastTaskCount.get + val currentTasksCount = currentTasks.size + def report0(): Unit = { console.print(s"$CursorDown1") currentTasks foreach { case (task, start) => val elapsed = (System.nanoTime - start) / 1000000000L console.println(s"$DeleteLine | => ${taskName(task)} ${elapsed}s") } - console.print(cursorUp(currentTasks.size + 1)) + if (ltc > currentTasksCount) deleteConsoleLines(ltc - currentTasksCount) + else () + console.print(cursorUp(math.max(currentTasksCount, ltc) + 1)) } - val isStop = currentTasks + if (containsSkipTasks(currentTasks)) () + else report0() + lastTaskCount.set(currentTasksCount) + } + + // todo: use logger instead of console + // private[this] def completionReport(): Unit = console.lockObject.synchronized { + // val completedTasks = timings.asScala.toList + // val notableTasks = completedTasks + // .filter({ + // case (_, time: Long) => time >= 1000000000L * 10L + // }) + // .sortBy({ + // case (_, time: Long) => -time + // }) + // .take(5) + // def report0(): Unit = { + // console.print(s"$CursorDown1") + // console.println(s"$DeleteLine notable completed tasks:") + // notableTasks foreach { + // case (task, time) => + // val elapsed = time / 1000000000L + // console.println(s"$DeleteLine | => ${taskName(task)} ${elapsed}s") + // } + // } + // if (containsSkipTasks(notableTasks) || notableTasks.isEmpty) () + // else report0() + // } + + private[this] def containsSkipTasks(tasks: List[(Task[_], Long)]): Boolean = + tasks .map({ case (t, _) => taskName(t) }) - .exists(n => stopReportTask.exists(m => n.endsWith("/ " + m))) - if (isStop) () - else report0 + .exists(n => skipReportTasks.exists(m => n.endsWith("/ " + m))) + + private[this] def deleteConsoleLines(n: Int): Unit = { + (1 to n) foreach { _ => + console.println(s"$DeleteLine") + } } private[this] val taskNameCache = TrieMap.empty[Task[_], String] @@ -134,7 +159,6 @@ private[sbt] final class TaskProgress(currentRef: ProjectRef) extends ExecutePro private[sbt] object TaskProgress { final val DeleteLine = "\u001B[2K" - final val CursorUp5 = cursorUp(5) def cursorUp(n: Int): String = s"\u001B[${n}A" def cursorDown(n: Int): String = s"\u001B[${n}B" final val CursorDown1 = cursorDown(1)