diff options
author | Som Snytt <som.snytt@gmail.com> | 2015-07-20 17:07:51 -0700 |
---|---|---|
committer | Jason Zaugg <jzaugg@gmail.com> | 2015-09-02 12:45:29 +1000 |
commit | 1566196669bb74891a2116236b3e76ea340f6c29 (patch) | |
tree | fc857418f1ea580022dae0d833afb89a1bfa9002 /src | |
parent | 5a23aac8c421fa017edb094563c552236be13c6f (diff) | |
download | scala-1566196669bb74891a2116236b3e76ea340f6c29.tar.gz scala-1566196669bb74891a2116236b3e76ea340f6c29.tar.bz2 scala-1566196669bb74891a2116236b3e76ea340f6c29.zip |
SI-5408 Prompt after incomplete script paste
Transcript paste mode invites the user to keep typing like
regular paste mode, but really you must enter more transcript.
This matters if the script ends in the middle of incomplete
code that the user wants to complete by hand.
Previously,
```
scala> scala> def f() = {
// Detected repl transcript paste: ctrl-D to finish.
// Replaying 1 commands from transcript.
scala> def f() = {
scala> scala> def f() = {
// Detected repl transcript paste: ctrl-D to finish.
| }
// Replaying 1 commands from transcript.
scala> def f() = {
}
f: ()Unit
```
Now,
```
scala> scala> def f() = {
// Detected repl transcript. Paste more, or ctrl-D to finish.
// Replaying 1 commands from transcript.
scala> def f() = {
| 42
| }
f: ()Int
scala> f()
res0: Int = 42
```
Diffstat (limited to 'src')
-rw-r--r-- | src/repl/scala/tools/nsc/interpreter/ILoop.scala | 75 | ||||
-rw-r--r-- | src/repl/scala/tools/nsc/interpreter/Pasted.scala | 56 |
2 files changed, 65 insertions, 66 deletions
diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala index 92655ba643..a0cc116df8 100644 --- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala @@ -24,9 +24,9 @@ import io.AbstractFile import scala.collection.generic.Clearable import scala.concurrent.{ ExecutionContext, Await, Future, future } import ExecutionContext.Implicits._ -import java.io.{ BufferedReader, FileReader } +import java.io.{ BufferedReader, FileReader, StringReader } -import scala.util.{Try, Success, Failure} +import scala.util.{ Try, Success, Failure } /** The Scala interactive shell. It provides a read-eval-print loop * around the Interpreter class. @@ -748,28 +748,9 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) result } - private object paste extends Pasted { - import scala.util.matching.Regex.quote - val ContinuePrompt = replProps.continuePrompt - val ContinueString = replProps.continueText // " | " - val PromptString = prompt.lines.toList.last - val anyPrompt = s"""\\s*(?:${quote(PromptString.trim)}|${quote(AltPromptString.trim)})\\s*""".r - - def isPrompted(line: String) = matchesPrompt(line) - def isPromptOnly(line: String) = line match { case anyPrompt() => true ; case _ => false } - - def interpret(line: String): Unit = { - echo(line.trim) - intp interpret line - echo("") - } - - def transcript(start: String) = { - echo("\n// Detected repl transcript paste: ctrl-D to finish.\n") - apply(Iterator(start) ++ readWhile(!isPromptOnly(_))) - } - - def unapply(line: String): Boolean = isPrompted(line) + private object paste extends Pasted(prompt) { + def interpret(line: String) = intp interpret line + def echo(message: String) = ILoop.this echo message } private object invocation { @@ -784,30 +765,10 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) * read, go ahead and interpret it. Return the full string * to be recorded for replay, if any. */ - def interpretStartingWith(code: String): Option[String] = { + @tailrec final def interpretStartingWith(code: String): Option[String] = { // signal completion non-completion input has been received in.completion.resetVerbosity() - def reallyInterpret = intp.interpret(code) match { - case IR.Error => None - case IR.Success => Some(code) - case IR.Incomplete if in.interactive && code.endsWith("\n\n") => - echo("You typed two blank lines. Starting a new command.") - None - case IR.Incomplete => - in.readLine(paste.ContinuePrompt) match { - case null => - // we know compilation is going to fail since we're at EOF and the - // parser thinks the input is still incomplete, but since this is - // a file being read non-interactively we want to fail. So we send - // it straight to the compiler for the nice error message. - intp.compileString(code) - None - - case line => interpretStartingWith(code + "\n" + line) - } - } - /* Here we place ourselves between the user and the interpreter and examine * the input they are ostensibly submitting. We intervene in several cases: * @@ -820,9 +781,29 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) code match { case "" => None case lineComment() => None // line comment, do nothing - case paste() if !paste.running => paste.transcript(code) ; None + case paste() if !paste.running => paste.transcript(Iterator(code) ++ readWhile(!paste.isPromptOnly(_))) match { + case Some(s) => interpretStartingWith(s) + case _ => None + } case invocation() if intp.mostRecentVar != "" => interpretStartingWith(intp.mostRecentVar + code) - case _ => reallyInterpret + case _ => intp.interpret(code) match { + case IR.Error => None + case IR.Success => Some(code) + case IR.Incomplete if in.interactive && code.endsWith("\n\n") => + echo("You typed two blank lines. Starting a new command.") + None + case IR.Incomplete => in.readLine(paste.ContinuePrompt) match { + case null => + // we know compilation is going to fail since we're at EOF and the + // parser thinks the input is still incomplete, but since this is + // a file being read non-interactively we want to fail. So we send + // it straight to the compiler for the nice error message. + intp.compileString(code) + None + + case line => interpretStartingWith(s"$code\n$line") + } + } } } diff --git a/src/repl/scala/tools/nsc/interpreter/Pasted.scala b/src/repl/scala/tools/nsc/interpreter/Pasted.scala index f8d8c2ddb1..3a7eda1b77 100644 --- a/src/repl/scala/tools/nsc/interpreter/Pasted.scala +++ b/src/repl/scala/tools/nsc/interpreter/Pasted.scala @@ -15,19 +15,23 @@ package interpreter * a transcript should itself be pasteable and should achieve * the same result. */ -abstract class Pasted { - def interpret(line: String): Unit - def ContinueString: String - def PromptString: String - def AltPromptString: String = "scala> " +abstract class Pasted(prompt: String) { + def interpret(line: String): IR.Result + def echo(message: String): Unit - /* `testBoth` cannot be a val, as `Pasted` is inherited by `object paste` in ILoop, - which would cause `val testBoth` to be initialized before `val PromptString` was. + val PromptString = prompt.lines.toList.last + val AltPromptString = "scala> " + val ContinuePrompt = replProps.continuePrompt + val ContinueString = replProps.continueText // " | " + val anyPrompt = { + import scala.util.matching.Regex.quote + s"""\\s*(?:${quote(PromptString.trim)}|${quote(AltPromptString.trim)})\\s*""".r + } + + def isPrompted(line: String) = matchesPrompt(line) + def isPromptOnly(line: String) = line match { case anyPrompt() => true ; case _ => false } - object paste extends Pasted { - val PromptString = prompt.lines.toList.last - */ - private def testBoth = PromptString != AltPromptString + private val testBoth = PromptString != AltPromptString private val spacey = " \t".toSet def matchesPrompt(line: String) = matchesString(line, PromptString) || testBoth && matchesString(line, AltPromptString) @@ -91,13 +95,26 @@ abstract class Pasted { case _ => code } - def run(): Unit = { - println("// Replaying %d commands from transcript.\n" format cmds.size) - cmds foreach { cmd => - print(ActualPromptString) - interpret(cmd) - } + def interpreted(line: String) = { + echo(line.trim) + val res = interpret(line) + if (res != IR.Incomplete) echo("") + res + } + def incompletely(cmd: String) = { + print(ActualPromptString) + interpreted(cmd) == IR.Incomplete } + def run(): Option[String] = { + echo(s"// Replaying ${cmds.size} commands from transcript.\n") + cmds find incompletely + } + } + + // Run transcript and return incomplete line if any. + def transcript(lines: TraversableOnce[String]): Option[String] = { + echo("\n// Detected repl transcript. Paste more, or ctrl-D to finish.\n") + apply(lines) } /** Commands start on lines beginning with "scala>" and each successive @@ -105,9 +122,10 @@ abstract class Pasted { * Everything else is discarded. When the end of the transcript is spotted, * all the commands are replayed. */ - def apply(lines: TraversableOnce[String]) = { + def apply(lines: TraversableOnce[String]): Option[String] = { isRunning = true - try new PasteAnalyzer(lines.toList) run() + try new PasteAnalyzer(lines.toList).run() finally isRunning = false } + def unapply(line: String): Boolean = isPrompted(line) } |