/* NSC -- new Scala compiler * Copyright 2005-2013 LAMP/EPFL * @author Paul Phillips */ package scala package tools package nsc package interpreter import scala.language.implicitConversions import scala.collection.mutable.ListBuffer class ProcessResult(val line: String) { import scala.sys.process._ private val buffer = new ListBuffer[String] val builder = Process(line) val logger = ProcessLogger(buffer += _) val exitCode = builder ! logger def lines = buffer.toList override def toString = "`%s` (%d lines, exit %d)".format(line, buffer.size, exitCode) } trait LoopCommands { self: { def echo(msg: String): Unit } => protected def out: JPrintWriter // So outputs can be suppressed. def echoCommandMessage(msg: String): Unit = out.println(msg) // available commands def commands: List[LoopCommand] // a single interpreter command abstract class LoopCommand(val name: String, val help: String) extends (String => Result) { def usage: String = "" def usageMsg: String = s":$name${ if (usage == "") "" else " " + usage }" def apply(line: String): Result // called if no args are given def showUsage(): Result = { "usage is " + usageMsg Result(keepRunning = true, None) } // subclasses may provide completions def completion: Completion = NoCompletion } object LoopCommand { def nullary(name: String, help: String, f: () => Result): LoopCommand = new NullaryCmd(name, help, _ => f()) def cmd(name: String, usage: String, help: String, f: String => Result, completion: Completion = NoCompletion): LoopCommand = if (usage == "") new NullaryCmd(name, help, f) else new LineCmd(name, usage, help, f, completion) } /** print a friendly help message */ def helpCommand(line: String): Result = line match { case "" => helpSummary() case CommandMatch(cmd) => echo(f"%n${cmd.help}") case _ => ambiguousError(line) } def helpSummary() = { val usageWidth = commands map (_.usageMsg.length) max val formatStr = s"%-${usageWidth}s %s" echo("All commands can be abbreviated, e.g., :he instead of :help.") for (cmd <- commands) echo(formatStr.format(cmd.usageMsg, cmd.help)) } def ambiguousError(cmd: String): Result = { matchingCommands(cmd) match { case Nil => echo(cmd + ": no such command. Type :help for help.") case xs => echo(cmd + " is ambiguous: did you mean " + xs.map(":" + _.name).mkString(" or ") + "?") } Result(keepRunning = true, None) } // all commands with given prefix private def matchingCommands(cmd: String) = commands.filter(_.name.startsWith(cmd.stripPrefix(":"))) // extract command from partial name, or prefer exact match if multiple matches private object CommandMatch { def unapply(name: String): Option[LoopCommand] = matchingCommands(name) match { case Nil => None case x :: Nil => Some(x) case xs => xs find (_.name == name) } } // extract command name and rest of line private val commandish = """(\S+)(?:\s+)?(.*)""".r def colonCommand(line: String): Result = line.trim match { case "" => helpSummary() case commandish(CommandMatch(cmd), rest) => cmd(rest) case commandish(name, _) => ambiguousError(name) case _ => echo("?") } import Completion.Candidates def colonCompletion(line: String, cursor: Int): Completion = line.trim match { case commandish(name @ CommandMatch(cmd), rest) => if (name.length > cmd.name.length) cmd.completion else new Completion { def resetVerbosity(): Unit = () def complete(buffer: String, cursor: Int) = Candidates(cursor - name.length + 1, List(cmd.name)) } case commandish(name, _) if matchingCommands(name).nonEmpty => new Completion { def resetVerbosity(): Unit = () def complete(buffer: String, cursor: Int) = Candidates(cursor - name.length + 1, matchingCommands(name).map(_.name)) } case _ => NoCompletion } class NullaryCmd(name: String, help: String, f: String => Result) extends LoopCommand(name, help) { def apply(line: String): Result = f(line) } class LineCmd(name: String, argWord: String, help: String, f: String => Result, override val completion: Completion) extends LoopCommand(name, help) { override def usage = argWord def apply(line: String): Result = f(line) } class VarArgsCmd(name: String, argWord: String, help: String, f: List[String] => Result) extends LoopCommand(name, help) { override def usage = argWord def apply(line: String): Result = apply(words(line)) def apply(args: List[String]) = f(args) } // the result of a single command case class Result(keepRunning: Boolean, lineToRecord: Option[String]) object Result { // the default result means "keep running, and don't record that line" val default = Result(keepRunning = true, None) // "keep running, and record this line" def recording(line: String) = Result(keepRunning = true, Option(line)) // most commands do not want to micromanage the Result, but they might want // to print something to the console, so we accommodate Unit and String returns. implicit def resultFromUnit(x: Unit): Result = default implicit def resultFromString(msg: String): Result = { echoCommandMessage(msg) default } } }