summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2010-11-17 01:00:57 +0000
committerPaul Phillips <paulp@improving.org>2010-11-17 01:00:57 +0000
commit363a1456f671323b35dcacf2c8b8eb39180b8a53 (patch)
tree553913c9a121fa5595a95d40e59c5d9f6944d64c
parentb7fcc7c73e41b326fe4d85d81c49c50fa954c990 (diff)
downloadscala-363a1456f671323b35dcacf2c8b8eb39180b8a53.tar.gz
scala-363a1456f671323b35dcacf2c8b8eb39180b8a53.tar.bz2
scala-363a1456f671323b35dcacf2c8b8eb39180b8a53.zip
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.
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala12
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/JLineReader.scala12
-rw-r--r--src/compiler/scala/tools/nsc/io/package.scala10
-rw-r--r--src/compiler/scala/tools/util/SignalManager.scala97
4 files changed, 126 insertions, 5 deletions
diff --git a/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala b/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala
index 74b3c6ee48..7e4abd1d76 100644
--- a/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala
@@ -5,19 +5,24 @@
package scala.tools.nsc
package interpreter
+
+import java.io.IOException
+import java.nio.channels.ClosedByInterruptException
import scala.util.control.Exception._
+import InteractiveReader._
/** Reads lines from an input stream */
trait InteractiveReader {
- import InteractiveReader._
- import java.io.IOException
protected def readOneLine(prompt: String): String
val interactive: Boolean
+ def init(): Unit = ()
def readLine(prompt: String): String = {
def handler: Catcher[String] = {
- case e: IOException if restartSystemCall(e) => readLine(prompt)
+ case e: ClosedByInterruptException => error("Reader closed by interrupt.")
+ // Terminal has to be re-initialized after SIGSTP or up arrow etc. stop working.
+ case e: IOException if restartSystemCall(e) => init() ; readLine(prompt)
}
catching(handler) { readOneLine(prompt) }
}
@@ -34,7 +39,6 @@ trait InteractiveReader {
Properties.isMac && (e.getMessage == msgEINTR)
}
-
object InteractiveReader {
val msgEINTR = "Interrupted system call"
def createDefault(): InteractiveReader = createDefault(null)
diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala
index ab11a53d43..2fe436fd2f 100644
--- a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala
@@ -8,13 +8,23 @@ package interpreter
import java.io.File
import jline.{ ConsoleReader, ArgumentCompletor, History => JHistory }
+import scala.tools.util.SignalManager
/** Reads from the console using JLine */
class JLineReader(interpreter: Interpreter) extends InteractiveReader {
def this() = this(null)
- override lazy val history = Some(History(consoleReader))
+ override lazy val history = Some(History(consoleReader))
override lazy val completion = Option(interpreter) map (x => new Completion(x))
+ override def init() = consoleReader.getTerminal().initializeTerminal()
+
+ /** Requires two interrupt signals within three seconds
+ * of one another to initiate exit.
+ */
+ SignalManager.requireInterval(3, SignalManager.INT) {
+ case true => Console.println("\nPress ctrl-C again to exit.")
+ case false => System.exit(1)
+ }
val consoleReader = {
val r = new jline.ConsoleReader()
diff --git a/src/compiler/scala/tools/nsc/io/package.scala b/src/compiler/scala/tools/nsc/io/package.scala
index 2016fb5518..1d484a6c0e 100644
--- a/src/compiler/scala/tools/nsc/io/package.scala
+++ b/src/compiler/scala/tools/nsc/io/package.scala
@@ -6,6 +6,7 @@
package scala.tools.nsc
import java.util.concurrent.{ Future, Callable, Executors }
+import java.util.{ Timer, TimerTask }
package object io {
def runnable(body: => Unit): Runnable = new Runnable { override def run() = body }
@@ -24,4 +25,13 @@ package object io {
thread.start
thread
}
+
+ // Set a timer to execute the given code.
+ def timer(seconds: Int)(body: => Unit): Timer = {
+ val alarm = new Timer(true) // daemon
+ val tt = new TimerTask { def run() = body }
+
+ alarm.schedule(tt, seconds * 1000)
+ alarm
+ }
} \ No newline at end of file
diff --git a/src/compiler/scala/tools/util/SignalManager.scala b/src/compiler/scala/tools/util/SignalManager.scala
new file mode 100644
index 0000000000..a6513ecb50
--- /dev/null
+++ b/src/compiler/scala/tools/util/SignalManager.scala
@@ -0,0 +1,97 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools
+package util
+
+import sun.misc.{ Signal, SignalHandler }
+import SignalHandler._
+import nsc.io.timer
+
+/** Unofficial signal handling code. According to sun it's unsupported,
+ * but it's too useful not to take advantage of. Degrade gracefully.
+ */
+class SignalManager {
+ def apply(name: String): SignalWrapper =
+ try { new SignalWrapper(new Signal(name)) }
+ catch { case x: IllegalArgumentException => new SignalError(x.getMessage) }
+
+ class ChainedHandler(prev: SignalHandler, current: SignalHandler) extends SignalHandler {
+ def handle(sig: Signal): Unit = {
+ current handle sig
+ if (prev != SIG_DFL && prev != SIG_IGN)
+ prev handle sig
+ }
+ }
+ class SignalWrapper(val signal: Signal) {
+ def name = signal.getName
+ def add(body: => Unit) = {
+ val handler = new SignalHandler { def handle(sig: Signal) = body }
+ val prev = Signal.handle(signal, handler)
+
+ new ChainedHandler(prev, handler)
+ }
+ override def toString = "SIG" + name
+ }
+ class SignalError(message: String) extends SignalWrapper(null) {
+ override def toString = message
+ }
+}
+
+object SignalManager extends SignalManager {
+ private implicit def mkSignalWrapper(name: String): SignalWrapper = this(name)
+
+ def HUP: SignalWrapper = "HUP"
+ def INT: SignalWrapper = "INT"
+ def QUIT: SignalWrapper = "QUIT"
+ def ILL: SignalWrapper = "ILL"
+ def TRAP: SignalWrapper = "TRAP"
+ def ABRT: SignalWrapper = "ABRT"
+ def EMT: SignalWrapper = "EMT"
+ def FPE: SignalWrapper = "FPE"
+ def KILL: SignalWrapper = "KILL"
+ def BUS: SignalWrapper = "BUS"
+ def SEGV: SignalWrapper = "SEGV"
+ def SYS: SignalWrapper = "SYS"
+ def PIPE: SignalWrapper = "PIPE"
+ def ALRM: SignalWrapper = "ALRM"
+ def TERM: SignalWrapper = "TERM"
+ def URG: SignalWrapper = "URG"
+ def STOP: SignalWrapper = "STOP"
+ def TSTP: SignalWrapper = "TSTP"
+ def CONT: SignalWrapper = "CONT"
+ def CHLD: SignalWrapper = "CHLD"
+ def TTIN: SignalWrapper = "TTIN"
+ def TTOU: SignalWrapper = "TTOU"
+ def IO: SignalWrapper = "IO"
+ def XCPU: SignalWrapper = "XCPU"
+ def XFSZ: SignalWrapper = "XFSZ"
+ def VTALRM: SignalWrapper = "VTALRM"
+ def PROF: SignalWrapper = "PROF"
+ def WINCH: SignalWrapper = "WINCH"
+ def INFO: SignalWrapper = "INFO"
+ def USR1: SignalWrapper = "USR1"
+ def USR2: SignalWrapper = "USR2"
+
+ /** Given a number of seconds, a signal, and a function: sets up a handler which upon
+ * receiving the signal once, calls the function with argument true, and if the
+ * signal is received again within the allowed time, calls it with argument false.
+ * (Otherwise it calls it with true and starts the timer over again.)
+ */
+ def requireInterval(seconds: Int, wrapper: SignalWrapper)(fn: Boolean => Unit) = {
+ var received = false
+ wrapper add {
+ if (received) fn(false)
+ else {
+ received = true
+ fn(true)
+ timer(seconds)(received = false)
+ }
+ }
+ }
+}
+
+
+