[2.x] fix: lastGrep ignores ANSI escape sequences when matching (#8726)

**Problem**
When `last` output includes ANSI escape sequences (e.g. colored `[error]` lines), `lastGrep <pattern>` was matching against the raw string. The pattern could fail to match because it was compared to text like `\u001B[31merror\u001B[0m` instead of `error`, or matching was inconsistent.

**Solution**
- Strip ANSI from each line before running the regex, using `EscHelpers.stripColorsAndMoves` from `sbt.internal.util`.
- **`Output.lastGrep`** (keys and file overloads): lines from the last run are stripped, then the pattern is applied to the stripped text; matching and printed lines are based on visible text.
- **`Output.grep`**: each line is stripped before `showMatches(pattern)` so the pattern is applied only to visible content.
This commit is contained in:
PandaMan 2026-02-10 00:13:46 -05:00 committed by GitHub
parent edd7061f15
commit 6d94d6db61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 49 additions and 3 deletions

View File

@ -19,6 +19,7 @@ import Aggregation.{ KeyValue, Values }
import Types.idFun
import Highlight.{ bold, showMatches }
import annotation.tailrec
import sbt.internal.util.EscHelpers
import sbt.io.IO
@ -43,7 +44,12 @@ object Output {
printLines: Seq[String] => Unit
)(using display: Show[ScopedKey[?]]): Unit = {
val pattern = Pattern.compile(patternString)
val lines = flatLines(lastLines(keys, streams))(_ flatMap showMatches(pattern))
val lines = flatLines(lastLines(keys, streams)) { rawLines =>
rawLines.flatMap { line =>
val stripped = EscHelpers.stripColorsAndMoves(line)
showMatches(pattern)(stripped)
}
}
printLines(lines)
}
@ -55,8 +61,13 @@ object Output {
): Unit =
printLines(grep(tailLines(file, tailDelim), patternString))
def grep(lines: Seq[String], patternString: String): Seq[String] =
lines.flatMap(showMatches(Pattern.compile(patternString)))
def grep(lines: Seq[String], patternString: String): Seq[String] = {
val pattern = Pattern.compile(patternString)
lines.flatMap { line =>
val stripped = EscHelpers.stripColorsAndMoves(line)
showMatches(pattern)(stripped)
}
}
def flatLines(outputs: Values[Seq[String]])(f: Seq[String] => Seq[String])(using
display: Show[ScopedKey[?]]

View File

@ -0,0 +1,35 @@
/*
* sbt
* Copyright 2023, Scala center
* Copyright 2011 - 2022, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal
import scala.Console.{ RED, RESET }
import verify.BasicTestSuite
import sbt.internal.Output.grep
object OutputSpec extends BasicTestSuite {
test(
"grep should match pattern against visible text when lines contain ANSI escape sequences (#4840)"
) {
// Line with ANSI color around "error" - user searching for "error" should find it (strip before match)
val lineWithAnsi = s"${RED}error${RESET}: something failed"
val lines = Seq("info: ok", lineWithAnsi, "warn: deprecated")
val result = grep(lines, "error")
assert(result.size == 1, s"expected 1 match, got ${result.size}: $result")
// Pattern matched the visible "error" (ANSI was stripped before matching); result may have highlight from showMatches
assert(result.head.contains("error"), s"result should contain 'error': ${result.head}")
}
test("grep should not match when pattern appears only inside ANSI sequence") {
// Line where "error" is not in the visible text (only in escape code - unrealistic but ensures we strip first)
val lines = Seq("info: ok", "something failed")
val result = grep(lines, "error")
assert(result.isEmpty, s"expected no match, got: $result")
}
}