Skip to content

Commit 5392543

Browse files
committed
Merge pull request scala#2706 from som-snytt/issue/repl-edit
SI-7637 Repl edit command
2 parents 3aba63b + 33b45ee commit 5392543

File tree

1 file changed

+86
-0
lines changed

1 file changed

+86
-0
lines changed

src/repl/scala/tools/nsc/interpreter/ILoop.scala

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,14 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
208208
/** Standard commands **/
209209
lazy val standardCommands = List(
210210
cmd("cp", "<path>", "add a jar or directory to the classpath", addClasspath),
211+
cmd("edit", "<id>|<line>", "edit history", editCommand),
211212
cmd("help", "[command]", "print this summary or command-specific help", helpCommand),
212213
historyCommand,
213214
cmd("h?", "<string>", "search the history", searchHistory),
214215
cmd("imports", "[name name ...]", "show import history, identifying sources of names", importsCommand),
215216
cmd("implicits", "[-v]", "show the implicits in scope", intp.implicitsCommand),
216217
cmd("javap", "<path|class>", "disassemble a file or class name", javapCommand),
218+
cmd("line", "<id>|<line>", "place line(s) at the end of history", lineCommand),
217219
cmd("load", "<path>", "load and interpret a Scala file", loadCommand),
218220
nullary("paste", "enter paste mode: all input up to ctrl-D compiled together", pasteCommand),
219221
nullary("power", "enable power user mode", powerCmd),
@@ -442,6 +444,90 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter)
442444
unleashAndSetPhase()
443445
}
444446

447+
def lineCommand(what: String): Result = editCommand(what, None)
448+
449+
// :edit id or :edit line
450+
def editCommand(what: String): Result = editCommand(what, Properties.envOrNone("EDITOR"))
451+
452+
def editCommand(what: String, editor: Option[String]): Result = {
453+
def diagnose(code: String) = {
454+
echo("The edited code is incomplete!\n")
455+
val errless = intp compileSources new BatchSourceFile("<pastie>", s"object pastel {\n$code\n}")
456+
if (errless) echo("The compiler reports no errors.")
457+
}
458+
def historicize(text: String) = history match {
459+
case jlh: JLineHistory => text.lines foreach jlh.add ; jlh.moveToEnd() ; true
460+
case _ => false
461+
}
462+
def edit(text: String): Result = editor match {
463+
case Some(ed) =>
464+
val tmp = File.makeTemp()
465+
tmp.writeAll(text)
466+
try {
467+
val pr = new ProcessResult(s"$ed ${tmp.path}")
468+
pr.exitCode match {
469+
case 0 =>
470+
tmp.safeSlurp() match {
471+
case Some(edited) if edited.trim.isEmpty => echo("Edited text is empty.")
472+
case Some(edited) =>
473+
echo(edited.lines map ("+" + _) mkString "\n")
474+
val res = intp interpret edited
475+
if (res == IR.Incomplete) diagnose(edited)
476+
else {
477+
historicize(edited)
478+
Result(lineToRecord = Some(edited), keepRunning = true)
479+
}
480+
case None => echo("Can't read edited text. Did you delete it?")
481+
}
482+
case x => echo(s"Error exit from $ed ($x), ignoring")
483+
}
484+
} finally {
485+
tmp.delete()
486+
}
487+
case None =>
488+
if (historicize(text)) echo("Placing text in recent history.")
489+
else echo(f"No EDITOR defined and you can't change history, echoing your text:%n$text")
490+
}
491+
492+
// if what is a number, use it as a line number or range in history
493+
def isNum = what forall (c => c.isDigit || c == '-' || c == '+')
494+
// except that "-" means last value
495+
def isLast = (what == "-")
496+
if (isLast || !isNum) {
497+
val name = if (isLast) intp.mostRecentVar else what
498+
val sym = intp.symbolOfIdent(name)
499+
intp.prevRequestList collectFirst { case r if r.defines contains sym => r } match {
500+
case Some(req) => edit(req.line)
501+
case None => echo(s"No symbol in scope: $what")
502+
}
503+
} else try {
504+
val s = what
505+
// line 123, 120+3, -3, 120-123, 120-, note -3 is not 0-3 but (cur-3,cur)
506+
val (start, len) =
507+
if ((s indexOf '+') > 0) {
508+
val (a,b) = s splitAt (s indexOf '+')
509+
(a.toInt, b.drop(1).toInt)
510+
} else {
511+
(s indexOf '-') match {
512+
case -1 => (s.toInt, 1)
513+
case 0 => val n = s.drop(1).toInt ; (history.index - n, n)
514+
case _ if s.last == '-' => val n = s.init.toInt ; (n, history.index - n)
515+
case i => val n = s.take(i).toInt ; (n, s.drop(i+1).toInt - n)
516+
}
517+
}
518+
import scala.collection.JavaConverters._
519+
val index = (start - 1) max 0
520+
val text = history match {
521+
case jlh: JLineHistory => jlh.entries(index).asScala.take(len) map (_.value) mkString "\n"
522+
case _ => history.asStrings.slice(index, index + len) mkString "\n"
523+
}
524+
edit(text)
525+
} catch {
526+
case _: NumberFormatException => echo(s"Bad range '$what'")
527+
echo("Use line 123, 120+3, -3, 120-123, 120-, note -3 is not 0-3 but (cur-3,cur)")
528+
}
529+
}
530+
445531
/** fork a shell and run a command */
446532
lazy val shCommand = new LoopCommand("sh", "run a shell command (result is implicitly => List[String])") {
447533
override def usage = "<command line>"

0 commit comments

Comments
 (0)