From e1aee77066eb00e28ea6737cf8a57ae22413e8e5 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 2 Oct 2025 09:47:39 -0600 Subject: [PATCH 1/7] . --- .../src/dotty/tools/repl/JLineTerminal.scala | 4 ++ .../src/dotty/tools/repl/ReplDriver.scala | 38 +++++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/repl/JLineTerminal.scala b/compiler/src/dotty/tools/repl/JLineTerminal.scala index 2680704d326e..fc193667154d 100644 --- a/compiler/src/dotty/tools/repl/JLineTerminal.scala +++ b/compiler/src/dotty/tools/repl/JLineTerminal.scala @@ -82,6 +82,10 @@ class JLineTerminal extends java.io.Closeable { def close(): Unit = terminal.close() + /** Register a signal handler and return the previous handler */ + def handle(signal: org.jline.terminal.Terminal.Signal, handler: org.jline.terminal.Terminal.SignalHandler): org.jline.terminal.Terminal.SignalHandler = + terminal.handle(signal, handler) + /** Provide syntax highlighting */ private class Highlighter(using Context) extends reader.Highlighter { def highlight(reader: LineReader, buffer: String): AttributedString = { diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index befb3de9a941..0fd5baab5eb9 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -175,6 +175,10 @@ class ReplDriver(settings: Array[String], s"""Welcome to Scala $simpleVersionString ($javaVersion, Java $javaVmName). |Type in expressions for evaluation. Or try :help.""".stripMargin) + // Track the time of last Ctrl-C + var lastCtrlCTime: Long = 0L + val ctrlCWindowMs = 1000L // 1 second window for double Ctrl-C + /** Blockingly read a line, getting back a parse result */ def readLine()(using state: State): ParseResult = { given Context = state.context @@ -211,16 +215,44 @@ class ReplDriver(settings: Array[String], val line = terminal.readLine(completer) ParseResult(line) } catch { - case _: EndOfFileException | - _: UserInterruptException => // Ctrl+D or Ctrl+C + case _: EndOfFileException => // Ctrl+D Quit + case _: UserInterruptException => // Ctrl+C at prompt - clear and continue + SigKill } } @tailrec def loop(using state: State)(): State = { val res = readLine() if (res == Quit) state - else loop(using interpret(res))() + else if (res == SigKill) { + // Ctrl-C pressed at prompt - just continue with same state (line is cleared by JLine) + loop(using state)() + } else { + // Set up interrupt handler for command execution + val thread = Thread.currentThread() + val signalHandler = terminal.handle( + org.jline.terminal.Terminal.Signal.INT, + (sig: org.jline.terminal.Terminal.Signal) => { + val now = System.currentTimeMillis() + if (now - lastCtrlCTime < ctrlCWindowMs) { + // Second Ctrl-C within window - interrupt the thread + out.println("\nTerminating REPL...") + thread.interrupt() + System.exit(130) // Standard exit code for SIGINT + } else { + // First Ctrl-C - warn user + lastCtrlCTime = now + out.println("\nPress Ctrl-C again to terminate the process") + } + } + ) + + val newState = interpret(res) + // Restore previous handler + terminal.handle(org.jline.terminal.Terminal.Signal.INT, signalHandler) + loop(using newState)() + } } try runBody { loop() } From 120dca2408aaa3f846875a06da99ffadd8913ff0 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 2 Oct 2025 10:02:56 -0600 Subject: [PATCH 2/7] . --- .../src/dotty/tools/repl/ReplDriver.scala | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 0fd5baab5eb9..b973c6fa542d 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -175,10 +175,6 @@ class ReplDriver(settings: Array[String], s"""Welcome to Scala $simpleVersionString ($javaVersion, Java $javaVmName). |Type in expressions for evaluation. Or try :help.""".stripMargin) - // Track the time of last Ctrl-C - var lastCtrlCTime: Long = 0L - val ctrlCWindowMs = 1000L // 1 second window for double Ctrl-C - /** Blockingly read a line, getting back a parse result */ def readLine()(using state: State): ParseResult = { given Context = state.context @@ -223,27 +219,27 @@ class ReplDriver(settings: Array[String], } @tailrec def loop(using state: State)(): State = { + val res = readLine() if (res == Quit) state - else if (res == SigKill) { - // Ctrl-C pressed at prompt - just continue with same state (line is cleared by JLine) - loop(using state)() - } else { + // Ctrl-C pressed at prompt - just continue with same state (line is cleared by JLine) + else if (res == SigKill) loop(using state)() + else { // Set up interrupt handler for command execution + var firstCtrlCEntered = false val thread = Thread.currentThread() val signalHandler = terminal.handle( org.jline.terminal.Terminal.Signal.INT, (sig: org.jline.terminal.Terminal.Signal) => { val now = System.currentTimeMillis() - if (now - lastCtrlCTime < ctrlCWindowMs) { - // Second Ctrl-C within window - interrupt the thread - out.println("\nTerminating REPL...") + if (!firstCtrlCEntered) { + firstCtrlCEntered = true thread.interrupt() + out.println("\nInterrupting running thread, Ctrl-C again to terminate the process") + }else { + out.println("\nTerminating REPL...") + System.exit(130) // Standard exit code for SIGINT - } else { - // First Ctrl-C - warn user - lastCtrlCTime = now - out.println("\nPress Ctrl-C again to terminate the process") } } ) From 452fc5fb74e0d543c221c8bd6767974abaec148a Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 2 Oct 2025 10:19:04 -0600 Subject: [PATCH 3/7] . --- compiler/src/dotty/tools/repl/ReplDriver.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index b973c6fa542d..3dea02fe72f0 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -228,7 +228,7 @@ class ReplDriver(settings: Array[String], // Set up interrupt handler for command execution var firstCtrlCEntered = false val thread = Thread.currentThread() - val signalHandler = terminal.handle( + val previousSignalHandler = terminal.handle( org.jline.terminal.Terminal.Signal.INT, (sig: org.jline.terminal.Terminal.Signal) => { val now = System.currentTimeMillis() @@ -238,15 +238,16 @@ class ReplDriver(settings: Array[String], out.println("\nInterrupting running thread, Ctrl-C again to terminate the process") }else { out.println("\nTerminating REPL...") - System.exit(130) // Standard exit code for SIGINT } } ) - val newState = interpret(res) - // Restore previous handler - terminal.handle(org.jline.terminal.Terminal.Signal.INT, signalHandler) + val newState = + try interpret(res) + // Restore previous handler + finally terminal.handle(org.jline.terminal.Terminal.Signal.INT, previousSignalHandler) + loop(using newState)() } } From c2d4554e7418e967e0b68840e702081592760d7a Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 2 Oct 2025 10:23:13 -0600 Subject: [PATCH 4/7] . --- compiler/src/dotty/tools/repl/ReplDriver.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 3dea02fe72f0..27d2f616e3a8 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -237,7 +237,7 @@ class ReplDriver(settings: Array[String], thread.interrupt() out.println("\nInterrupting running thread, Ctrl-C again to terminate the process") }else { - out.println("\nTerminating REPL...") + out.println("\nTerminating REPL Process...") System.exit(130) // Standard exit code for SIGINT } } From 38cc2b3814cf5d3dc68e7fc8cc208bd06f4daed3 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 2 Oct 2025 10:23:21 -0600 Subject: [PATCH 5/7] . --- compiler/src/dotty/tools/repl/ReplDriver.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 27d2f616e3a8..5b67614fda12 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -235,7 +235,7 @@ class ReplDriver(settings: Array[String], if (!firstCtrlCEntered) { firstCtrlCEntered = true thread.interrupt() - out.println("\nInterrupting running thread, Ctrl-C again to terminate the process") + out.println("\nInterrupting running thread, Ctrl-C again to terminate the REPL Process") }else { out.println("\nTerminating REPL Process...") System.exit(130) // Standard exit code for SIGINT From 9381fe1040d3af95dc14e82eb9a75bc29df29c2a Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 2 Oct 2025 10:24:05 -0600 Subject: [PATCH 6/7] . --- compiler/src/dotty/tools/repl/ReplDriver.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index 5b67614fda12..e42773a916ac 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -231,7 +231,6 @@ class ReplDriver(settings: Array[String], val previousSignalHandler = terminal.handle( org.jline.terminal.Terminal.Signal.INT, (sig: org.jline.terminal.Terminal.Signal) => { - val now = System.currentTimeMillis() if (!firstCtrlCEntered) { firstCtrlCEntered = true thread.interrupt() From f6654b78247f7f547645840fce1f1d0114d6676a Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 9 Oct 2025 17:41:24 +0100 Subject: [PATCH 7/7] Update compiler/src/dotty/tools/repl/ReplDriver.scala MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Oliver Bračevac --- compiler/src/dotty/tools/repl/ReplDriver.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/repl/ReplDriver.scala b/compiler/src/dotty/tools/repl/ReplDriver.scala index e42773a916ac..ffa1b648446d 100644 --- a/compiler/src/dotty/tools/repl/ReplDriver.scala +++ b/compiler/src/dotty/tools/repl/ReplDriver.scala @@ -235,7 +235,7 @@ class ReplDriver(settings: Array[String], firstCtrlCEntered = true thread.interrupt() out.println("\nInterrupting running thread, Ctrl-C again to terminate the REPL Process") - }else { + } else { out.println("\nTerminating REPL Process...") System.exit(130) // Standard exit code for SIGINT }