Skip to content

Commit

Permalink
improve handling of windows multiline prompt (#4053)
Browse files Browse the repository at this point in the history
This generally tries to avoid clearing and re-rendering parts of the UI
unnecessarily, which which reduce flicker in slower terminals like
Windows
  • Loading branch information
lihaoyi authored Dec 1, 2024
1 parent 9be9754 commit e793d31
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 281 deletions.
4 changes: 3 additions & 1 deletion build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,9 @@ trait MillStableScalaModule extends MillPublishScalaModule with Mima {
),
ProblemFilter.exclude[Problem](
"mill.scalalib.RunModule#RunnerImpl.*"
),
ProblemFilter.exclude[Problem](
"mill.util.PromptLogger#*"
)
)
def mimaPreviousVersions: T[Seq[String]] = Settings.mimaBaseVersions
Expand Down Expand Up @@ -691,7 +694,6 @@ implicit object DepSegment extends Cross.ToSegments[Dep]({ dep =>
*/
object dummy extends Cross[DependencyFetchDummy](dummyDeps)
trait DependencyFetchDummy extends ScalaModule with Cross.Module[Dep] {

def scalaVersion = Deps.scalaVersion
def compileIvyDeps = Agg(crossValue)
}
5 changes: 3 additions & 2 deletions dist/package.mill
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,9 @@ object `package` extends RootModule with build.MillPublishJavaModule {
)
}

val batExt = if (scala.util.Properties.isWin) ".bat" else ""
val DefaultLocalMillReleasePath =
s"target/mill-release${if (scala.util.Properties.isWin) ".bat" else ""}"
s"target/mill-release$batExt"

/**
* Build and install Mill locally.
Expand All @@ -240,7 +241,7 @@ object `package` extends RootModule with build.MillPublishJavaModule {

def installLocalCache() = Task.Command {
val path = installLocalTask(
Task.Anon((os.home / ".cache" / "mill" / "download" / build.millVersion()).toString())
Task.Anon((os.home / ".cache" / "mill" / "download" / (build.millVersion() + batExt)).toString())
)()
Task.log.outputStream.println(path.toString())
PathRef(path)
Expand Down
9 changes: 5 additions & 4 deletions main/client/src/mill/main/client/ProxyStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ public Pumper(InputStream src, OutputStream destOut, OutputStream destErr) {

public void preRead(InputStream src) {}

public void preWrite(byte[] buffer, int length) {}
public void write(OutputStream dest, byte[] buffer, int length) throws IOException {
dest.write(buffer, 0, length);
}

public void run() {

Expand Down Expand Up @@ -152,13 +154,12 @@ public void run() {

if (delta != -1) {
synchronized (synchronizer) {
this.preWrite(buffer, offset);
switch (stream) {
case ProxyStream.OUT:
destOut.write(buffer, 0, offset);
this.write(destOut, buffer, offset);
break;
case ProxyStream.ERR:
destErr.write(buffer, 0, offset);
this.write(destErr, buffer, offset);
break;
}
}
Expand Down
34 changes: 17 additions & 17 deletions main/util/src/mill/util/PromptLogger.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@ package mill.util

import mill.api.SystemStreams
import mill.main.client.ProxyStream
import mill.util.PromptLoggerUtil.{
Status,
clearScreenToEndBytes,
defaultTermHeight,
defaultTermWidth,
renderPrompt
}
import mill.util.PromptLoggerUtil.{Status, defaultTermHeight, defaultTermWidth, renderPrompt}
import pprint.Util.literalize

import java.io._
Expand Down Expand Up @@ -90,8 +84,8 @@ private[mill] class PromptLogger(
)

def refreshPrompt(ending: Boolean = false): Unit = synchronized {
promptLineState.updatePrompt(ending)
streamManager.refreshPrompt()
val updated = promptLineState.updatePrompt(ending)
if (updated) streamManager.refreshPrompt()
}

if (enableTicker && autoUpdate) promptUpdaterThread.start()
Expand Down Expand Up @@ -289,16 +283,21 @@ private[mill] object PromptLogger {
}
}

override def preWrite(buf: Array[Byte], end: Int): Unit = {
// Before any write, make sure we clear the terminal of any prompt that was
// written earlier and not yet cleared, so the following output can be written
// to a clean section of the terminal

override def write(dest: OutputStream, buf: Array[Byte], end: Int): Unit = {
lastCharWritten = buf(end - 1).toChar
if (interactive() && !paused() && promptShown) {
systemStreams0.err.write(clearScreenToEndBytes)
promptShown = false
}

// Clear each line as they are drawn, rather than relying on clearing
// the entire screen before each batch of writes, to try and reduce the
// amount of terminal flickering in slow terminals (e.g. windows)
// https://stackoverflow.com/questions/71452837/how-to-reduce-flicker-in-terminal-re-drawing
dest.write(
new String(buf, 0, end)
.replaceAll("(\r\n|\n)", AnsiNav.clearLine(0) + "$1")
.getBytes
)
}
}

Expand Down Expand Up @@ -338,7 +337,7 @@ private[mill] object PromptLogger {

def getCurrentPrompt() = currentPromptBytes

def updatePrompt(ending: Boolean = false): Unit = {
def updatePrompt(ending: Boolean = false): Boolean = {
val now = currentTimeMillis()
for (k <- statuses.keySet) {
val removedTime = statuses(k).beginTransitionTime
Expand Down Expand Up @@ -367,8 +366,9 @@ private[mill] object PromptLogger {
ending = ending
)

val oldPromptBytes = currentPromptBytes
currentPromptBytes = renderPromptWrapped(currentPromptLines, interactive, ending).getBytes

!java.util.Arrays.equals(oldPromptBytes, currentPromptBytes)
}

def clearStatuses(): Unit = { statuses.clear() }
Expand Down
4 changes: 3 additions & 1 deletion main/util/src/mill/util/PromptLoggerUtil.scala
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ private object PromptLoggerUtil {
if (ending) "\n"
else AnsiNav.left(9999) + AnsiNav.up(currentPromptLines.length - 1)

AnsiNav.clearScreen(0) + currentPromptLines.mkString("\n") + backUp
currentPromptLines.map(_ + AnsiNav.clearLine(0)).mkString("\n") +
AnsiNav.clearScreen(0) +
backUp
}
}

Expand Down
Loading

0 comments on commit e793d31

Please sign in to comment.