From 5222234ee14269a434bc8e88474b929cab32c52f Mon Sep 17 00:00:00 2001 From: Mark Harrah Date: Mon, 3 May 2010 08:17:19 -0400 Subject: [PATCH] history commands: !!, !?string, !-n, !n, !string --- sbt/src/main/scala/sbt/LineReader.scala | 46 +++++++++++-------- sbt/src/main/scala/sbt/Main.scala | 43 ++++++++++++++++- sbt/src/main/scala/sbt/complete/History.scala | 42 +++++++++++++++++ 3 files changed, 110 insertions(+), 21 deletions(-) create mode 100644 sbt/src/main/scala/sbt/complete/History.scala diff --git a/sbt/src/main/scala/sbt/LineReader.scala b/sbt/src/main/scala/sbt/LineReader.scala index 23c5bde81..f6c19373e 100644 --- a/sbt/src/main/scala/sbt/LineReader.scala +++ b/sbt/src/main/scala/sbt/LineReader.scala @@ -36,6 +36,10 @@ abstract class JLine extends LineReader case null => None case x => Some(x.trim) } + def getHistory: Array[String] = + JLine.synchronized { + reader.getHistory.getHistoryList.toArray(new Array[String](0)) + } } private object JLine { @@ -49,23 +53,33 @@ private object JLine } def createReader() = withTerminal { t => - t.synchronized - { - val cr = new ConsoleReader - t.enableEcho() - cr.setBellEnabled(false) - cr - } + val cr = new ConsoleReader + t.enableEcho() + cr.setBellEnabled(false) + cr } def withJLine[T](action: => T): T = withTerminal { t => - t.synchronized + t.disableEcho() + try { action } + finally { t.enableEcho() } + } + private[sbt] def initializeHistory(cr: ConsoleReader, historyPath: Option[Path], log: Logger): Unit = + for(historyLocation <- historyPath) + { + val historyFile = historyLocation.asFile + Control.trapAndLog(log) { - t.disableEcho() - try { action } - finally { t.enableEcho() } + historyFile.getParentFile.mkdirs() + cr.getHistory.setHistoryFile(historyFile) } } + def simple(historyPath: Option[Path], log: Logger): SimpleReader = new SimpleReader(historyPath, log) +} +class SimpleReader private[sbt] (historyPath: Option[Path], log: Logger) extends JLine +{ + protected[this] val reader = JLine.createReader() + JLine.initializeHistory(reader, historyPath, log) } object SimpleReader extends JLine { @@ -77,15 +91,7 @@ private[sbt] final class LazyJLineReader(historyPath: Option[Path], completor: = { val cr = new ConsoleReader cr.setBellEnabled(false) - for(historyLocation <- historyPath) - { - val historyFile = historyLocation.asFile - Control.trapAndLog(log) - { - historyFile.getParentFile.mkdirs() - cr.getHistory.setHistoryFile(historyFile) - } - } + JLine.initializeHistory(cr, historyPath, log) cr.addCompletor(new LazyCompletor(completor)) cr } diff --git a/sbt/src/main/scala/sbt/Main.scala b/sbt/src/main/scala/sbt/Main.scala index 7d26cf869..b33808dea 100755 --- a/sbt/src/main/scala/sbt/Main.scala +++ b/sbt/src/main/scala/sbt/Main.scala @@ -197,6 +197,15 @@ class xMain extends xsbti.AppMain case Some(newProject) => continue(newProject, tail, failAction) case None => failed(BuildErrorExitCode) } + + case action :: tail if action.startsWith(HistoryPrefix) => + historyCommand(action.substring(HistoryPrefix.length).trim, baseProject.historyPath, project.log) match + { + case Some(command) => + println(command) //better to print it than to log it + continue(project, command :: tail, failAction) + case None => failed(UsageErrorExitCode) + } case action :: tail if action.startsWith(FileCommandsPrefix) => getSource(action.substring(FileCommandsPrefix.length).trim, baseProject.info.projectDirectory) match @@ -271,6 +280,37 @@ class xMain extends xsbti.AppMain if(message eq null) None else Some(message) } } + private def historyCommand(s: String, historyPath: Option[Path], log: Logger): Option[String] = + { + val lines = historyPath.toList.flatMap(h => xsbt.FileUtilities.readLines(h.asFile) ).toArray + if(lines.isEmpty) + { + log.warn("No history") + None + } + else + { + def replaceLastCommand(s: String) { + lines(lines.length - 1) = s + historyPath foreach { h => xsbt.FileUtilities.writeLines(h.asFile, lines) } + } + val command = historyCommand(complete.History(lines, log), s, log) + command.foreach(replaceLastCommand) + command + } + } + private def historyCommand(history: complete.History, s: String, log: Logger): Option[String] = + { + val ContainsPrefix = "?" + val Last = "!" + val PreviousPrefix = "-" + if(s == Last) + history !! + else if(s.startsWith(ContainsPrefix)) + history !? s.substring(ContainsPrefix.length) + else + history ! s + } object SetProject { def unapply(s: String) = @@ -355,6 +395,7 @@ class xMain extends xsbti.AppMain reader.readLine("> ").getOrElse(ExitCommand) } + val HistoryPrefix = "!" /** The name of the command that loads a console with access to the current project through the variable 'project'.*/ val ProjectConsoleAction = "console-project" /** The name of the command that shows the current project and logging level of that project.*/ @@ -398,7 +439,7 @@ class xMain extends xsbti.AppMain /** The prefix used to identify a file or local port to read commands from. */ val FileCommandsPrefix = "<" /** The prefix used to identify the action to run after an error*/ - val FailureHandlerPrefix = "!" + val FailureHandlerPrefix = "-" /** The prefix used to identify commands for managing processors.*/ val ProcessorPrefix = "*" diff --git a/sbt/src/main/scala/sbt/complete/History.scala b/sbt/src/main/scala/sbt/complete/History.scala new file mode 100644 index 000000000..6d2e81041 --- /dev/null +++ b/sbt/src/main/scala/sbt/complete/History.scala @@ -0,0 +1,42 @@ +package sbt.complete + +import History.number + +final class History private(lines: Array[String], log: Logger) extends NotNull +{ + private def reversed = lines.reverse + + def all: Seq[String] = lines.toArray + def size = lines.length + def !! : Option[String] = !- (1) + def apply(i: Int): Option[String] = if(0 <= i && i < size) Some( lines(i) ) else { log.error("Invalid history index: " + i); None } + def !(i: Int): Option[String] = apply(i) + + def !(s: String): Option[String] = + number(s) match + { + case Some(n) => if(n < 0) !- (-n) else apply(n) + case None => nonEmpty(s) { reversed.find(_.startsWith(s)) } + } + def !- (n: Int): Option[String] = apply(size - n - 1) + + def !?(s: String): Option[String] = nonEmpty(s) { reversed.drop(1).find(_.contains(s)) } + + private def nonEmpty[T](s: String)(act: => Option[T]): Option[T] = + if(s.isEmpty) + { + log.error("No action specified to history command") + None + } + else + act +} + +object History +{ + def apply(lines: Seq[String], log: Logger): History = new History(lines.toArray, log) + + private def number(s: String): Option[Int] = + try { Some(s.toInt) } + catch { case e: NumberFormatException => None } +} \ No newline at end of file