Summary
AppOptions.ClearConsole defaults to true whenever scala-cli is detected, so every
DFHDL run launched via scala run prints ESC c (RIS, full terminal reset) before the
welcome banner. On an interactive terminal this clears the screen and scrollback on
every compile. In captured or non-TTY output it corrupts the first line, which surfaces
as a stray c prefix:
cWelcome to DFiant HDL (DFHDL) v0.22.0+39-03afffb1-SNAPSHOT !!!
Source
lib/src/main/scala/dfhdl/options/AppOptions.scala:34
object ClearConsole:
given ClearConsole = if (metalsIsRunning || scala_cliIsRunning) true else false
lib/src/main/scala/dfhdl/app/DFApp.scala:451
def run(commandArgs: Array[String]): Unit =
if (appOptions.clearConsole) print("�c")
internals/src/main/scala/dfhdl/internals/helpers.scala:410
lazy val scala_cliIsRunning: Boolean = getShellCommand.exists(_.contains(".scala-build"))
The detection is of scala-cli, not of an interactive terminal, so the reset fires
regardless of whether anything can meaningfully be "cleared".
Reproduction
Any scala-cli invocation of a DFHDL design reproduces it. Minimal case:
//> src/AndGate.scala
import dfhdl.*
class AndGate extends EDDesign:
val a, b = Bit <> IN
val o = Bit <> OUT
o <> a && b
end AndGate
scala run src/ --scala 3.8.4 \
--dep "io.github.dfianthdl::dfhdl::$VERSION" \
--compiler-plugin "io.github.dfianthdl:::dfhdl-plugin::$VERSION" \
-- commit | cat -A | head -3
The banner line is prefixed with a raw ESC (^[):
^[cWelcome to DFiant HDL (DFHDL) v0.22.0+39-03afffb1-SNAPSHOT !!!$
Impact
Found while running DFHDL non-interactively from an automated agent harness, where
scala run is the standard compile path.
- Captured logs are corrupted. Anything that reads DFHDL output as text sees a stray
c glued to the banner. Log parsers keying on the banner line fail.
- Interactive scrollback is destroyed. RIS is a full reset, not a clear: on a real
terminal every compile discards the history above it, including the output of the
command the user just ran.
- It cannot be avoided from the outside. The reset is printed before the command line
is parsed, so no flag passed on that command line can suppress it.
Suggested fix
Gate the reset on stdout actually being an interactive terminal, in addition to the
existing tool detection. Something along the lines of:
given ClearConsole =
(metalsIsRunning || scala_cliIsRunning) && System.console() != null
System.console() returns null when stdout is redirected or piped, which is precisely
the case where the reset does harm and no good. That preserves the current behaviour for
an interactive scala-cli or Metals session while leaving captured output clean.
A more conservative alternative is to keep the current default but honour an environment
variable (say DFHDL_NO_CLEAR_CONSOLE), though that still leaves the default wrong for
every non-TTY consumer unless they know to set it.
Environment
- DFHDL
0.22.0+39-03afffb1-SNAPSHOT (built from training at 03afffb1)
- Scala 3.8.4, scala-cli 1.11.0, JDK 21
- Linux, non-interactive shell
Summary
AppOptions.ClearConsoledefaults totruewhenever scala-cli is detected, so everyDFHDL run launched via
scala runprintsESC c(RIS, full terminal reset) before thewelcome banner. On an interactive terminal this clears the screen and scrollback on
every compile. In captured or non-TTY output it corrupts the first line, which surfaces
as a stray
cprefix:Source
lib/src/main/scala/dfhdl/options/AppOptions.scala:34lib/src/main/scala/dfhdl/app/DFApp.scala:451internals/src/main/scala/dfhdl/internals/helpers.scala:410The detection is of scala-cli, not of an interactive terminal, so the reset fires
regardless of whether anything can meaningfully be "cleared".
Reproduction
Any scala-cli invocation of a DFHDL design reproduces it. Minimal case:
The banner line is prefixed with a raw ESC (
^[):Impact
Found while running DFHDL non-interactively from an automated agent harness, where
scala runis the standard compile path.cglued to the banner. Log parsers keying on the banner line fail.terminal every compile discards the history above it, including the output of the
command the user just ran.
is parsed, so no flag passed on that command line can suppress it.
Suggested fix
Gate the reset on stdout actually being an interactive terminal, in addition to the
existing tool detection. Something along the lines of:
System.console()returnsnullwhen stdout is redirected or piped, which is preciselythe case where the reset does harm and no good. That preserves the current behaviour for
an interactive scala-cli or Metals session while leaving captured output clean.
A more conservative alternative is to keep the current default but honour an environment
variable (say
DFHDL_NO_CLEAR_CONSOLE), though that still leaves the default wrong forevery non-TTY consumer unless they know to set it.
Environment
0.22.0+39-03afffb1-SNAPSHOT(built fromtrainingat03afffb1)