diff options
Diffstat (limited to 'src/compiler')
10 files changed, 222 insertions, 166 deletions
diff --git a/src/compiler/scala/tools/nsc/interpreter/Completion.scala b/src/compiler/scala/tools/nsc/interpreter/Completion.scala index 3436c6631e..79f0317533 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Completion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Completion.scala @@ -7,30 +7,37 @@ package scala.tools.nsc package interpreter import java.util.{ List => JList } +import Completion._ /** An implementation-agnostic completion interface which makes no * reference to the jline classes. */ trait Completion { type ExecResult - trait Instance { - def complete(buffer: String, cursor: Int, candidates: JList[CharSequence]): Int - } def resetVerbosity(): Unit def execute(line: String): Option[ExecResult] - def completer(): Instance + def completer(): ScalaCompleter +} +object NoCompletion extends Completion { + type ExecResult = Nothing + def resetVerbosity() = () + def execute(line: String) = None + def completer() = NullCompleter } object Completion { - object Empty extends Completion { - type ExecResult = Nothing - object NullCompleter extends Instance { - def complete(buffer: String, cursor: Int, candidates: JList[CharSequence]) = -1 - } - def resetVerbosity() = () - def execute(line: String) = None - def completer() = NullCompleter + def empty: Completion = NoCompletion + + case class Candidates(cursor: Int, candidates: List[String]) { } + val NoCandidates = Candidates(-1, Nil) + + object NullCompleter extends ScalaCompleter { + def complete(buffer: String, cursor: Int): Candidates = NoCandidates + } + trait ScalaCompleter { + def complete(buffer: String, cursor: Int): Candidates } + def looksLikeInvocation(code: String) = ( (code != null) && (code startsWith ".") diff --git a/src/compiler/scala/tools/nsc/interpreter/History.scala b/src/compiler/scala/tools/nsc/interpreter/History.scala index e0364b5c6e..246cea12dd 100644 --- a/src/compiler/scala/tools/nsc/interpreter/History.scala +++ b/src/compiler/scala/tools/nsc/interpreter/History.scala @@ -16,13 +16,14 @@ trait History { def grep(s: String): List[String] def flush(): Unit } +object NoHistory extends History { + def asStrings = Nil + def grep(s: String) = Nil + def index = 0 + def size = 0 + def flush() = () +} object History { - object Empty extends History { - def asStrings = Nil - def grep(s: String) = Nil - def index = 0 - def size = 0 - def flush() = () - } + def empty: History = NoHistory } diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala index 413f08dfb1..5c57e70927 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala @@ -154,7 +154,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) /** Show the history */ def printHistory(xs: List[String]): Result = { - if (in.history eq History.Empty) + if (in.history eq NoHistory) return "No history available." val defaultLines = 20 @@ -273,7 +273,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) val oldReplay = replayCommandStack try file applyReader { reader => - in = new SimpleReader(reader, out, false) + in = SimpleReader(reader, out, false) plushln("Loading " + file + "...") loop() } @@ -530,29 +530,39 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) case _ => } + /** Tries to create a JLineReader, falling back to SimpleReader: + * unless settings or properties are such that it should start + * with SimpleReader. + */ + def chooseReader(settings: Settings): InteractiveReader = { + if (settings.Xnojline.value || Properties.isEmacsShell) + SimpleReader() + else try JLineReader( + if (settings.noCompletion.value) NoCompletion + else new JLineCompletion(intp) + ) + catch { + case _: Exception | _: NoClassDefFoundError => SimpleReader() + } + } + def process(settings: Settings): Boolean = { this.settings = settings createInterpreter() // sets in to some kind of reader depending on environmental cues in = in0 match { - case Some(in0) => new SimpleReader(in0, out, true) - case None => - // the interpreter is passed as an argument to expose tab completion info - if (settings.Xnojline.value || Properties.isEmacsShell) new SimpleReader - else JLineReader( - if (settings.noCompletion.value) Completion.Empty - else new JLineCompletion(intp) - ) + case Some(reader) => SimpleReader(reader, out, true) + case None => chooseReader(settings) } loadFiles(settings) - try { - // it is broken on startup; go ahead and exit - if (intp.reporter.hasErrors) return false - - printWelcome() + // it is broken on startup; go ahead and exit + if (intp.reporter.hasErrors) + return false + printWelcome() + try { // this is about the illusion of snappiness. We call initialize() // which spins off a separate thread, then print the prompt and try // our best to look ready. Ideally the user will spend a diff --git a/src/compiler/scala/tools/nsc/interpreter/IMain.scala b/src/compiler/scala/tools/nsc/interpreter/IMain.scala index 84189cddad..794e6c158a 100644 --- a/src/compiler/scala/tools/nsc/interpreter/IMain.scala +++ b/src/compiler/scala/tools/nsc/interpreter/IMain.scala @@ -91,7 +91,6 @@ class IMain(val settings: Settings, out: PrintWriter) { out.flush() } - /** directory to save .class files to */ val virtualDirectory = new VirtualDirectory("(memory)", None) diff --git a/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala b/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala index e7ef50ddf7..1a67cc647d 100644 --- a/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala @@ -10,38 +10,35 @@ import java.io.IOException import java.nio.channels.ClosedByInterruptException import scala.util.control.Exception._ import InteractiveReader._ +import Properties.isMac /** Reads lines from an input stream */ trait InteractiveReader { val interactive: Boolean - protected def readOneLine(prompt: String): String def history: History def completion: Completion + def init(): Unit def reset(): Unit - def redrawLine(): Unit = () - def currentLine = "" // the current buffer contents, if available - - def readLine(prompt: String): String = { - def handler: Catcher[String] = { - case e: ClosedByInterruptException => sys.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) => reset() ; readLine(prompt) - } - catching(handler) { readOneLine(prompt) } - } + protected def readOneLine(prompt: String): String + def redrawLine(): Unit + def currentLine: String - // hack necessary for OSX jvm suspension because read calls are not restarted after SIGTSTP - private def restartSystemCall(e: Exception): Boolean = - Properties.isMac && (e.getMessage == msgEINTR) + def readLine(prompt: String): String = + // hack necessary for OSX jvm suspension because read calls are not restarted after SIGTSTP + if (isMac) restartSysCalls(readOneLine(prompt), reset()) + else readOneLine(prompt) } object InteractiveReader { val msgEINTR = "Interrupted system call" - def apply(): InteractiveReader = new SimpleReader + def restartSysCalls[R](body: => R, reset: => Unit): R = + try body catch { + case e: IOException if e.getMessage == msgEINTR => reset ; body + } - // @deprecated("Use `apply` instead") def createDefault(intp: IMain): InteractiveReader = apply(intp) - // @deprecated("Use `apply` instead") def createDefault(comp: Completion): InteractiveReader = apply(comp) + def apply(): InteractiveReader = SimpleReader() } + diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala index 3f2003a2f7..148b80df12 100644 --- a/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala @@ -264,7 +264,7 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput def completions(buf: String): List[String] = topLevelFor(Parsed.dotted(buf + ".", buf.length + 1)) - def completer() = new JLineCompleterClass + def completer(): ScalaCompleter = new JLineTabCompletion /** This gets a little bit hairy. It's no small feat delegating everything * and also keeping track of exactly where the cursor is and where it's supposed @@ -272,7 +272,7 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput * string in the list of completions, that means we are expanding a unique * completion, so don't update the "last" buffer because it'll be wrong. */ - class JLineCompleterClass extends Instance with Completer { + class JLineTabCompletion extends ScalaCompleter { // For recording the buffer on the last tab hit private var lastBuf: String = "" private var lastCursor: Int = -1 @@ -281,35 +281,43 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput def isConsecutiveTabs(buf: String, cursor: Int) = cursor == lastCursor && buf == lastBuf + def sameHead(xs: List[String]) = { + if (xs.isEmpty || xs.head.length == 0) false + else { + val y :: ys = xs + val ch = y.head + ys forall (s => s.length > 0 && s.head == ch) + } + } // Longest common prefix - def commonPrefix(xs: List[String]) = - if (xs.isEmpty) "" - else xs.reduceLeft(_ zip _ takeWhile (x => x._1 == x._2) map (_._1) mkString) + def commonPrefix(xs: List[String]): String = { + if (sameHead(xs)) xs.head + commonPrefix(xs map (_.tail)) + else "" + } // This is jline's entry point for completion. - override def complete(_buf: String, cursor: Int, candidates: JList[CharSequence]): Int = { - val buf = if (_buf == null) "" else _buf + override def complete(buf: String, cursor: Int): Candidates = { verbosity = if (isConsecutiveTabs(buf, cursor)) verbosity + 1 else 0 DBG("\ncomplete(%s, %d) last = (%s, %d), verbosity: %s".format(buf, cursor, lastBuf, lastCursor, verbosity)) // we don't try lower priority completions unless higher ones return no results. - def tryCompletion(p: Parsed, completionFunction: Parsed => List[String]): Option[Int] = { + def tryCompletion(p: Parsed, completionFunction: Parsed => List[String]): Option[Candidates] = { completionFunction(p) match { case Nil => None case xs => - // modify in place and return the position - xs foreach (candidates add _) - - // update the last buffer unless this is an alternatives list - if (xs contains "") Some(p.cursor) - else { - val advance = commonPrefix(xs) - lastCursor = p.position + advance.length - lastBuf = (buf take p.position) + advance - - DBG("tryCompletion(%s, _) lastBuf = %s, lastCursor = %s, p.position = %s".format(p, lastBuf, lastCursor, p.position)) - Some(p.position) - } + Some(Candidates( + if (xs contains "") p.cursor + else { + // update the last buffer unless this is an alternatives list + val advance = commonPrefix(xs) + lastCursor = p.position + advance.length + lastBuf = (buf take p.position) + advance + DBG("tryCompletion(%s, _) lastBuf = %s, lastCursor = %s, p.position = %s".format( + p, lastBuf, lastCursor, p.position)) + p.position + }, + xs) + ) } } @@ -332,14 +340,12 @@ class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput * compiler still throws some Errors it may not be. */ try { - (lastResultCompletion orElse regularCompletion orElse fileCompletion) getOrElse cursor + (lastResultCompletion orElse regularCompletion orElse fileCompletion) getOrElse Candidates(cursor, Nil) } catch { case ex: Exception => - DBG("Error: complete(%s, %s, _) provoked %s".format(_buf, cursor, ex)) - candidates add " " - candidates add "<completion error>" - cursor + DBG("Error: complete(%s, %s) provoked %s".format(buf, cursor, ex)) + Candidates(cursor, List(" ", "<completion error>")) } } } diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineHistory.scala b/src/compiler/scala/tools/nsc/interpreter/JLineHistory.scala new file mode 100644 index 0000000000..e9be7141f1 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/JLineHistory.scala @@ -0,0 +1,56 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +import java.io.File +import java.util.{ List => JList } +import scala.tools.jline.console.ConsoleReader +import scala.tools.jline.console.completer._ +import scala.tools.jline.console.history._ +import scala.tools.jline.console.history.{ FileHistory, PersistentHistory, History => JHistory } +import scala.tools.jline.console.history.History.{ Entry => JEntry } +import scala.tools.jline.console.ConsoleReader +import scala.collection.JavaConverters._ +import Properties.userHome + +/** A wrapper for JLine's History. + */ +class JLineHistory(val jhistory: JHistory) extends History { + def asJavaList = jhistory.entries() + def asStrings = asList map (_.value.toString) + def asList: List[JEntry] = asJavaList.asScala.toList + def index = jhistory.index() + def size = jhistory.size() + + def grep(s: String) = asStrings filter (_ contains s) + def flush() = jhistory match { + case x: PersistentHistory => x.flush() + case _ => () + } +} + +object JLineHistory { + val ScalaHistoryFile = ".scala_history" + + def apply() = new JLineHistory( + try newFile() + catch { case x : Exception => + Console.println("Error creating file history: memory history only. " + x) + newMemory() + } + ) + + def newMemory() = new MemoryHistory() + def newFile() = new FileHistory(new File(userHome, ScalaHistoryFile)) { + // flush after every add to avoid installing a shutdown hook. + // (The shutdown hook approach also loses history when they aren't run.) + override def add(item: CharSequence): Unit = { + super.add(item) + flush() + } + } +} diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala index d09567eadf..7652e6646b 100644 --- a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala @@ -16,65 +16,27 @@ import scala.tools.jline.console.history.History.{ Entry => JEntry } import scala.tools.jline.console.ConsoleReader import scala.collection.JavaConverters._ import Properties.userHome - -/** A wrapper for JLine's History. - */ -class JLineHistory(val jhistory: JHistory) extends History { - def asJavaList = jhistory.entries() - def asStrings = asList map (_.value.toString) - def asList: List[JEntry] = asJavaList.asScala.toList - def index = jhistory.index() - def size = jhistory.size() - - def grep(s: String) = asStrings filter (_ contains s) - def flush() = jhistory match { - case x: PersistentHistory => x.flush() - case _ => () - } -} - -object JLineHistory { - val ScalaHistoryFile = ".scala_history" - - def apply() = new JLineHistory( - try newFile() - catch { case x : Exception => - Console.println("Error creating file history: memory history only. " + x) - newMemory() - } - ) - - def newMemory() = new MemoryHistory() - def newFile() = new FileHistory(new File(userHome, ScalaHistoryFile)) { - // flush after every add to avoid installing a shutdown hook. - // (The shutdown hook approach also loses history when they aren't run.) - override def add(item: CharSequence): Unit = { - super.add(item) - flush() - } - } -} +import Completion._ /** Reads from the console using JLine */ class JLineReader(val completion: Completion) extends InteractiveReader { + val interactive = true lazy val history = JLineHistory() def reset() = consoleReader.getTerminal().reset() def init() = consoleReader.getTerminal().init() - override def redrawLine() = { - consoleReader.flush() - consoleReader.drawLine() - consoleReader.flush() + def scalaToJline(tc: ScalaCompleter): Completer = new Completer { + def complete(_buf: String, cursor: Int, candidates: JList[CharSequence]): Int = { + val buf = if (_buf == null) "" else _buf + val Candidates(newCursor, newCandidates) = tc.complete(buf, cursor) + newCandidates foreach (candidates add _) + newCursor + } } def argCompletor: ArgumentCompleter = { - val wrapped = new Completer { - val cc = completion.completer() - def complete(buffer: String, cursor: Int, candidates: JList[CharSequence]): Int = - cc.complete(buffer, cursor, candidates) - } - val c = new ArgumentCompleter(new JLineDelimiter, wrapped) + val c = new ArgumentCompleter(new JLineDelimiter, scalaToJline(completion.completer())) c setStrict false c } @@ -82,10 +44,10 @@ class JLineReader(val completion: Completion) extends InteractiveReader { val consoleReader = { val r = new ConsoleReader() r setBellEnabled false - if (history ne History.Empty) + if (history ne NoHistory) r setHistory history.jhistory - if (completion ne Completion.Empty) { + if (completion ne NoCompletion) { r addCompleter argCompletor r setAutoprintThreshold 400 // max completion candidates without warning } @@ -93,15 +55,16 @@ class JLineReader(val completion: Completion) extends InteractiveReader { r } - override def currentLine: String = consoleReader.getCursorBuffer.buffer.toString + def currentLine: String = consoleReader.getCursorBuffer.buffer.toString + def redrawLine() = { + consoleReader.flush() + consoleReader.drawLine() + consoleReader.flush() + } def readOneLine(prompt: String) = consoleReader readLine prompt - val interactive = true } object JLineReader { - def apply(intp: IMain): InteractiveReader = apply(new JLineCompletion(intp)) - def apply(comp: Completion): InteractiveReader = { - try new JLineReader(comp) - catch { case e @ (_: Exception | _: NoClassDefFoundError) => new SimpleReader } - } + def apply(intp: IMain): JLineReader = apply(new JLineCompletion(intp)) + def apply(comp: Completion): JLineReader = new JLineReader(comp) } diff --git a/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala b/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala index e4c0bf8307..079fa4b9fb 100644 --- a/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala @@ -13,15 +13,15 @@ class SimpleReader( in: BufferedReader, out: PrintWriter, val interactive: Boolean) -extends InteractiveReader { - def this() = this(Console.in, new PrintWriter(Console.out), true) - def this(in: io.File, out: PrintWriter, interactive: Boolean) = this(in.bufferedReader(), out, interactive) - - lazy val history = History.Empty - lazy val completion = Completion.Empty +extends InteractiveReader +{ + val history = NoHistory + val completion = NoCompletion def init() = () def reset() = () + def redrawLine() = () + def currentLine = "" def readOneLine(prompt: String): String = { if (interactive) { out.print(prompt) @@ -30,3 +30,11 @@ extends InteractiveReader { in.readLine() } } + +object SimpleReader { + def defaultIn = Console.in + def defaultOut = new PrintWriter(Console.out) + + def apply(in: BufferedReader = defaultIn, out: PrintWriter = defaultOut, interactive: Boolean = true): SimpleReader = + new SimpleReader(in, out, interactive) +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala b/src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala index 91dee97277..f72409c8e7 100644 --- a/src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala +++ b/src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala @@ -19,46 +19,55 @@ import scala.reflect.NameTransformer */ trait TypeStrings { private val ObjectClass = classOf[java.lang.Object] - private val primitives = Set[String]("byte", "char", "short", "int", "long", "float", "double", "boolean") - private def unbox(s: String): String = s.stripPrefix("java.lang.") match { - case "Integer" => "scala.Int" - case "Character" => "scala.Char" - case "Void" => "scala.Unit" - case x @ ("Byte" | "Short" | "Long" | "Float" | "Double" | "Boolean") => "scala." + x - case _ => NameTransformer.decode(s) - } + private val primitives = Set[String]("byte", "char", "short", "int", "long", "float", "double", "boolean", "void") + private val primitiveMap = primitives.toList map { x => + val key = x match { + case "void" => "Void" + case "int" => "Integer" + case "char" => "Character" + case s => s.capitalize + } + val value = x match { + case "void" => "Unit" + case s => s.capitalize + } + + ("java.lang." + key) -> ("scala." + value) + } toMap def scalaName(s: String): String = { - if (s endsWith "$") (s dropRight 1) + ".type" - else if (primitives(s)) "scala." + s.capitalize + if (s endsWith "$") s.init + ".type" else if (s == "void") "scala.Unit" - else unbox(s) + else if (primitives(s)) "scala." + s.capitalize + else primitiveMap.getOrElse(s, NameTransformer decode s) } def scalaName(clazz: JClass): String = scalaName(clazz.getName) def scalaName(m: ClassManifest[_]): String = scalaName(m.erasure) def anyClass(x: Any): JClass = if (x == null) null else x.asInstanceOf[AnyRef].getClass + private def brackets(tps: String*): String = + if (tps.isEmpty) "" + else tps.mkString("[", ", ", "]") + private def tvarString(tvar: TypeVariable[_]): String = tvarString(tvar.getBounds.toList) private def tvarString(bounds: List[AnyRef]): String = { val xs = bounds filterNot (_ == ObjectClass) collect { case x: Class[_] => x } if (xs.isEmpty) "_" else scalaName(xs.head) } - private def tparamString(clazz: JClass): String = { - val tps = clazz.getTypeParameters.toList - if (tps.isEmpty) - return "" + private def tparamString(clazz: JClass): String = + brackets(clazz.getTypeParameters map tvarString: _*) - (tps map tvarString).mkString("[", ", ", "]") - } - private def tparamString[T: Manifest] : String = { - val tps = manifest[T].typeArguments - if (tps.isEmpty) - return "" + private def tparamString[T: Manifest] : String = + brackets(manifest[T].typeArguments map (m => tvarString(List(m.erasure))): _*) - tps.map(m => tvarString(List(m.erasure))).mkString("[", ", ", "]") - } - /** Going for an overabundance of caution right now. + /** Going for an overabundance of caution right now. Later these types + * can be a lot more precise, but right now the manifests have a habit of + * introducing material which is not syntactically valid as scala source. + * When this happens it breaks the repl. It would be nice if we mandated + * that manifest toString methods (or some other method, since it's bad + * practice to rely on toString for correctness) generated the VALID string + * representation of the type. */ def fromTypedValue[T: Manifest](x: T): String = fromManifest[T] def fromValue(value: Any): String = if (value == null) "Null" else fromClazz(anyClass(value)) |