Skip to content

Commit 363a145

Browse files
committed
Two annoying REPL things made less annoying:
* ctrl-C will no longer kill the repl unless you hit it again * ctrl-Z will no longer make the repl useless because of jline In the service of the first I wrote signal handling code, which we can put to use in other ways as well. No review.
1 parent b7fcc7c commit 363a145

File tree

4 files changed

+126
-5
lines changed

4 files changed

+126
-5
lines changed

src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala

+8-4
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,24 @@
55

66
package scala.tools.nsc
77
package interpreter
8+
9+
import java.io.IOException
10+
import java.nio.channels.ClosedByInterruptException
811
import scala.util.control.Exception._
12+
import InteractiveReader._
913

1014
/** Reads lines from an input stream */
1115
trait InteractiveReader {
12-
import InteractiveReader._
13-
import java.io.IOException
1416

1517
protected def readOneLine(prompt: String): String
1618
val interactive: Boolean
19+
def init(): Unit = ()
1720

1821
def readLine(prompt: String): String = {
1922
def handler: Catcher[String] = {
20-
case e: IOException if restartSystemCall(e) => readLine(prompt)
23+
case e: ClosedByInterruptException => error("Reader closed by interrupt.")
24+
// Terminal has to be re-initialized after SIGSTP or up arrow etc. stop working.
25+
case e: IOException if restartSystemCall(e) => init() ; readLine(prompt)
2126
}
2227
catching(handler) { readOneLine(prompt) }
2328
}
@@ -34,7 +39,6 @@ trait InteractiveReader {
3439
Properties.isMac && (e.getMessage == msgEINTR)
3540
}
3641

37-
3842
object InteractiveReader {
3943
val msgEINTR = "Interrupted system call"
4044
def createDefault(): InteractiveReader = createDefault(null)

src/compiler/scala/tools/nsc/interpreter/JLineReader.scala

+11-1
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,23 @@ package interpreter
88

99
import java.io.File
1010
import jline.{ ConsoleReader, ArgumentCompletor, History => JHistory }
11+
import scala.tools.util.SignalManager
1112

1213
/** Reads from the console using JLine */
1314
class JLineReader(interpreter: Interpreter) extends InteractiveReader {
1415
def this() = this(null)
1516

16-
override lazy val history = Some(History(consoleReader))
17+
override lazy val history = Some(History(consoleReader))
1718
override lazy val completion = Option(interpreter) map (x => new Completion(x))
19+
override def init() = consoleReader.getTerminal().initializeTerminal()
20+
21+
/** Requires two interrupt signals within three seconds
22+
* of one another to initiate exit.
23+
*/
24+
SignalManager.requireInterval(3, SignalManager.INT) {
25+
case true => Console.println("\nPress ctrl-C again to exit.")
26+
case false => System.exit(1)
27+
}
1828

1929
val consoleReader = {
2030
val r = new jline.ConsoleReader()

src/compiler/scala/tools/nsc/io/package.scala

+10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package scala.tools.nsc
77

88
import java.util.concurrent.{ Future, Callable, Executors }
9+
import java.util.{ Timer, TimerTask }
910

1011
package object io {
1112
def runnable(body: => Unit): Runnable = new Runnable { override def run() = body }
@@ -24,4 +25,13 @@ package object io {
2425
thread.start
2526
thread
2627
}
28+
29+
// Set a timer to execute the given code.
30+
def timer(seconds: Int)(body: => Unit): Timer = {
31+
val alarm = new Timer(true) // daemon
32+
val tt = new TimerTask { def run() = body }
33+
34+
alarm.schedule(tt, seconds * 1000)
35+
alarm
36+
}
2737
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/* NSC -- new Scala compiler
2+
* Copyright 2005-2010 LAMP/EPFL
3+
* @author Paul Phillips
4+
*/
5+
6+
package scala.tools
7+
package util
8+
9+
import sun.misc.{ Signal, SignalHandler }
10+
import SignalHandler._
11+
import nsc.io.timer
12+
13+
/** Unofficial signal handling code. According to sun it's unsupported,
14+
* but it's too useful not to take advantage of. Degrade gracefully.
15+
*/
16+
class SignalManager {
17+
def apply(name: String): SignalWrapper =
18+
try { new SignalWrapper(new Signal(name)) }
19+
catch { case x: IllegalArgumentException => new SignalError(x.getMessage) }
20+
21+
class ChainedHandler(prev: SignalHandler, current: SignalHandler) extends SignalHandler {
22+
def handle(sig: Signal): Unit = {
23+
current handle sig
24+
if (prev != SIG_DFL && prev != SIG_IGN)
25+
prev handle sig
26+
}
27+
}
28+
class SignalWrapper(val signal: Signal) {
29+
def name = signal.getName
30+
def add(body: => Unit) = {
31+
val handler = new SignalHandler { def handle(sig: Signal) = body }
32+
val prev = Signal.handle(signal, handler)
33+
34+
new ChainedHandler(prev, handler)
35+
}
36+
override def toString = "SIG" + name
37+
}
38+
class SignalError(message: String) extends SignalWrapper(null) {
39+
override def toString = message
40+
}
41+
}
42+
43+
object SignalManager extends SignalManager {
44+
private implicit def mkSignalWrapper(name: String): SignalWrapper = this(name)
45+
46+
def HUP: SignalWrapper = "HUP"
47+
def INT: SignalWrapper = "INT"
48+
def QUIT: SignalWrapper = "QUIT"
49+
def ILL: SignalWrapper = "ILL"
50+
def TRAP: SignalWrapper = "TRAP"
51+
def ABRT: SignalWrapper = "ABRT"
52+
def EMT: SignalWrapper = "EMT"
53+
def FPE: SignalWrapper = "FPE"
54+
def KILL: SignalWrapper = "KILL"
55+
def BUS: SignalWrapper = "BUS"
56+
def SEGV: SignalWrapper = "SEGV"
57+
def SYS: SignalWrapper = "SYS"
58+
def PIPE: SignalWrapper = "PIPE"
59+
def ALRM: SignalWrapper = "ALRM"
60+
def TERM: SignalWrapper = "TERM"
61+
def URG: SignalWrapper = "URG"
62+
def STOP: SignalWrapper = "STOP"
63+
def TSTP: SignalWrapper = "TSTP"
64+
def CONT: SignalWrapper = "CONT"
65+
def CHLD: SignalWrapper = "CHLD"
66+
def TTIN: SignalWrapper = "TTIN"
67+
def TTOU: SignalWrapper = "TTOU"
68+
def IO: SignalWrapper = "IO"
69+
def XCPU: SignalWrapper = "XCPU"
70+
def XFSZ: SignalWrapper = "XFSZ"
71+
def VTALRM: SignalWrapper = "VTALRM"
72+
def PROF: SignalWrapper = "PROF"
73+
def WINCH: SignalWrapper = "WINCH"
74+
def INFO: SignalWrapper = "INFO"
75+
def USR1: SignalWrapper = "USR1"
76+
def USR2: SignalWrapper = "USR2"
77+
78+
/** Given a number of seconds, a signal, and a function: sets up a handler which upon
79+
* receiving the signal once, calls the function with argument true, and if the
80+
* signal is received again within the allowed time, calls it with argument false.
81+
* (Otherwise it calls it with true and starts the timer over again.)
82+
*/
83+
def requireInterval(seconds: Int, wrapper: SignalWrapper)(fn: Boolean => Unit) = {
84+
var received = false
85+
wrapper add {
86+
if (received) fn(false)
87+
else {
88+
received = true
89+
fn(true)
90+
timer(seconds)(received = false)
91+
}
92+
}
93+
}
94+
}
95+
96+
97+

0 commit comments

Comments
 (0)