Merge branch 'develop' into network-flush

This commit is contained in:
Ethan Atkins 2020-10-24 16:47:56 -07:00 committed by GitHub
commit fd0fb12e06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 113 additions and 27 deletions

View File

@ -495,8 +495,10 @@ trait Appender extends AutoCloseable {
// the output may have unwanted colors but it would still be legible. This should
// only be relevant if the log message string itself contains ansi escape sequences
// other than color codes which is very unlikely.
val toWrite = if (!ansiCodesSupported) {
if (useFormat) EscHelpers.stripMoves(msg) else EscHelpers.removeEscapeSequences(msg)
val toWrite = if (!ansiCodesSupported || !useFormat && msg.getBytes.contains(27.toByte)) {
val (bytes, len) =
EscHelpers.strip(msg.getBytes, stripAnsi = !ansiCodesSupported, stripColor = !useFormat)
new String(bytes, 0, len)
} else msg
out.println(toWrite)
}

View File

@ -126,32 +126,76 @@ object EscHelpers {
}
index
}
def stripMoves(s: String): String = {
val bytes = s.getBytes
/**
* Strips ansi escape and color codes from an input string.
*
* @param bytes the input bytes
* @param stripAnsi toggles whether or not to remove general ansi escape codes
* @param stripColor toggles whether or not to remove ansi color codes
* @return a string with the escape and color codes removed depending on the input
* parameter along with the length of the output string (which may be smaller than
* the returned array)
*/
def strip(bytes: Array[Byte], stripAnsi: Boolean, stripColor: Boolean): (Array[Byte], Int) = {
val res = Array.fill[Byte](bytes.length)(0)
var i = 0
var index = 0
var lastEscapeIndex = -1
var state = 0
def set(b: Byte) = {
res(index) = b
index += 1
}
var limit = 0
val digit = new ArrayBuffer[Byte]
var leftDigit = -1
var escIndex = -1
bytes.foreach { b =>
set(b)
if (index < res.length) res(index) = b
index += 1
limit = math.max(limit, index)
if (state == 0) escIndex = -1
b match {
case 27 =>
escIndex = index - 1
state = esc
lastEscapeIndex = math.max(0, index)
case b if b == '[' && state == esc => state = csi
case 'm' => state = 0
case b if state == csi && (b < 48 || b >= 58) && b != ';' =>
case b if (state == esc || state == csi) && b >= 48 && b < 58 =>
state = csi
digit += b
case '[' if state == esc => state = csi
case 8 =>
state = 0
index = math.max(0, lastEscapeIndex - 1)
case b =>
index = math.max(index - 1, 0)
case b if state == csi =>
leftDigit = Try(new String(digit.toArray).toInt).getOrElse(0)
state = 0
b.toChar match {
case 'h' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'J' | 'K' =>
if (stripAnsi) index = math.max(escIndex, 0)
case 'm' => if (stripColor) index = escIndex
case ';' | 's' | 'u' | '?' => state = csi
case b =>
}
digit.clear()
case b if state == esc => state = 0
case b =>
}
}
new String(res, 0, index)
(res, index)
}
@deprecated("use EscHelpers.strip", "1.4.2")
def stripMoves(s: String): String = {
val (bytes, len) = strip(s.getBytes, stripAnsi = true, stripColor = false)
new String(bytes, 0, len)
}
/**
* Removes the ansi escape sequences from a string and makes a best attempt at
* calculating any ansi moves by hand. For example, if the string contains
* a backspace character followed by a character, the output string would
* replace the character preceding the backspaces with the character proceding it.
* This is in contrast to `strip` which just removes all ansi codes entirely.
*
* @param s the input string
* @return a string containing the original characters of the input stream with
* the ansi escape codes removed.
*/
def stripColorsAndMoves(s: String): String = {
val bytes = s.getBytes
val res = Array.fill[Byte](bytes.length)(0)
@ -174,6 +218,7 @@ object EscHelpers {
leftDigit = Try(new String(digit.toArray).toInt).getOrElse(0)
state = 0
b.toChar match {
case 'h' => index = math.max(index - 1, 0)
case 'D' => index = math.max(index - leftDigit, 0)
case 'C' => index = math.min(limit, math.min(index + leftDigit, res.length - 1))
case 'K' | 'J' =>
@ -190,6 +235,7 @@ object EscHelpers {
index += 1
limit = math.max(limit, index)
}
(res, limit)
new String(res, 0, limit)
}

View File

@ -124,7 +124,12 @@ private[sbt] object JLine3 {
override val output: OutputStream = new OutputStream {
override def write(b: Int): Unit = write(Array[Byte](b.toByte))
override def write(b: Array[Byte]): Unit = if (!closed.get) term.withPrintStream { ps =>
ps.write(b)
val (toWrite, len) = if (b.contains(27.toByte)) {
if (!term.isAnsiSupported || !term.isColorEnabled) {
EscHelpers.strip(b, !term.isAnsiSupported, !term.isColorEnabled)
} else (b, b.length)
} else (b, b.length)
if (len == toWrite.length) ps.write(toWrite) else ps.write(toWrite, 0, len)
term.prompt match {
case a: Prompt.AskUser => a.write(b)
case _ =>

View File

@ -930,7 +930,14 @@ object Terminal {
}
override def flush(): Unit = combinedOutputStream.flush()
}
private def doWrite(bytes: Array[Byte]): Unit = withPrintStream { ps =>
private def doWrite(rawBytes: Array[Byte]): Unit = withPrintStream { ps =>
val (toWrite, len) =
if (rawBytes.contains(27.toByte)) {
if (!isAnsiSupported || !isColorEnabled)
EscHelpers.strip(rawBytes, stripAnsi = !isAnsiSupported, stripColor = !isColorEnabled)
else (rawBytes, rawBytes.length)
} else (rawBytes, rawBytes.length)
val bytes = if (len < toWrite.length) toWrite.take(len) else toWrite
progressState.write(TerminalImpl.this, bytes, ps, hasProgress.get && !rawMode.get)
}
override private[sbt] val printStream: PrintStream = new LinePrintStream(outputStream)

View File

@ -45,23 +45,25 @@ class CleanStringSpec extends FlatSpec {
}
it should "remove moves in string with only moves" in {
val original =
new String(Array[Byte](27, 91, 50, 75, 27, 91, 51, 65, 27, 91, 49, 48, 48, 48, 68))
assert(EscHelpers.stripMoves(original) == "")
Array[Byte](27, 91, 50, 75, 27, 91, 51, 65, 27, 91, 49, 48, 48, 48, 68)
val (bytes, len) = EscHelpers.strip(original, stripAnsi = true, stripColor = true)
assert(len == 0)
}
it should "remove moves in string with moves and letters" in {
val original = new String(
val original =
Array[Byte](27, 91, 50, 75, 27, 91, 51, 65) ++ "foo".getBytes ++ Array[Byte](27, 91, 49, 48,
48, 48, 68)
)
assert(EscHelpers.stripMoves(original) == "foo")
val (bytes, len) = EscHelpers.strip(original, stripAnsi = true, stripColor = true)
assert(new String(bytes, 0, len) == "foo")
}
it should "preserve colors" in {
val original = new String(
val original =
Array[Byte](27, 91, 49, 48, 48, 48, 68, 27, 91, 48, 74, 102, 111, 111, 27, 91, 51, 54, 109,
62, 32, 27, 91, 48, 109)
) // this is taken from an sbt prompt that looks like "foo> " with the > rendered blue
// this is taken from an sbt prompt that looks like "foo> " with the > rendered blue
val colorArrow = new String(Array[Byte](27, 91, 51, 54, 109, 62))
assert(EscHelpers.stripMoves(original) == "foo" + colorArrow + " " + scala.Console.RESET)
val (bytes, len) = EscHelpers.strip(original, stripAnsi = true, stripColor = false)
assert(new String(bytes, 0, len) == "foo" + colorArrow + " " + scala.Console.RESET)
}
it should "remove unusual escape characters" in {
val original = new String(
@ -70,4 +72,28 @@ class CleanStringSpec extends FlatSpec {
)
assert(EscHelpers.stripColorsAndMoves(original).isEmpty)
}
it should "remove bracketed paste csi" in {
// taken from a test project prompt
val original =
Array[Byte](27, 91, 63, 50, 48, 48, 52, 104, 115, 98, 116, 58, 114, 101, 112, 114, 111, 62,
32)
val (bytes, len) = EscHelpers.strip(original, stripAnsi = true, stripColor = false)
assert(new String(bytes, 0, len) == "sbt:repro> ")
}
it should "strip colors" in {
// taken from utest output
val original =
Array[Byte](91, 105, 110, 102, 111, 93, 32, 27, 91, 51, 50, 109, 43, 27, 91, 51, 57, 109, 32,
99, 111, 109, 46, 97, 99, 109, 101, 46, 67, 111, 121, 111, 116, 101, 84, 101, 115, 116, 46,
109, 97, 107, 101, 84, 114, 97, 112, 32, 27, 91, 50, 109, 57, 109, 115, 27, 91, 48, 109, 32,
32, 27, 91, 48, 74, 10)
val (bytes, len) = EscHelpers.strip(original, stripAnsi = false, stripColor = true)
val expected = "[info] + com.acme.CoyoteTest.makeTrap 9ms " +
new String(Array[Byte](27, 91, 48, 74, 10))
assert(new String(bytes, 0, len) == expected)
val (bytes2, len2) = EscHelpers.strip(original, stripAnsi = true, stripColor = true)
val expected2 = "[info] + com.acme.CoyoteTest.makeTrap 9ms \n"
assert(new String(bytes2, 0, len2) == expected2)
}
}