Skip to content

Commit

Permalink
Fix ANSI color sequences that apply to multiple lines in the terminal…
Browse files Browse the repository at this point in the history
… logs (#3797)

Previously the `linePrefix` would end with `RESET` and wipe out the
colors after each line; now we parse each line and look at the ending
color and print that at the start of the next line. There'll be some
overhead, but `fansi.Str` is pretty fast so hopefully it's OK

Tested manually, we can see the multi-line ansi colors that were
problematic earlier now work, both preserving color, going from color to
non-color and color to non-color

<img width="976" alt="Screenshot 2024-10-21 at 6 52 15 PM"
src="https://github.com/user-attachments/assets/3e21eb71-9e17-419c-8c23-194b959bfa44">


Fixes #3793
  • Loading branch information
lihaoyi authored Oct 21, 2024
1 parent c0b6f18 commit b8217f3
Showing 1 changed file with 15 additions and 0 deletions.
15 changes: 15 additions & 0 deletions main/util/src/mill/util/LinePrefixOutputStream.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,31 @@ class LinePrefixOutputStream(
private[this] val linePrefixNonEmpty = linePrefixBytes.length != 0
private[this] var isNewLine = true
val buffer = new ByteArrayOutputStream()

// Make sure we preserve the end-of-line ANSI colors every time we write out the buffer, and
// re-apply them after every line prefix. This helps ensure the line prefix color/resets does
// not muck up the rendering of color sequences that affect multiple lines in the terminal
private var endOfLastLineColor: Long = 0
override def write(b: Array[Byte]): Unit = write(b, 0, b.length)
private[this] def writeLinePrefixIfNecessary(): Unit = {
if (isNewLine && linePrefixNonEmpty) {
isNewLine = false
buffer.write(linePrefixBytes)
if (linePrefixNonEmpty)
buffer.write(fansi.Attrs.emitAnsiCodes(0, endOfLastLineColor).getBytes())
}
}

def writeOutBuffer(): Unit = {
if (buffer.size() > 0) reportPrefix()

if (linePrefixNonEmpty) {
val bufferString = buffer.toString
if (bufferString.length > 0) {
val s = fansi.Str.apply(bufferString, errorMode = fansi.ErrorMode.Sanitize)
endOfLastLineColor = s.getColor(s.length - 1)
}
}
out.synchronized { buffer.writeTo(out) }
buffer.reset()
}
Expand Down

0 comments on commit b8217f3

Please sign in to comment.