All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scala.tools.nsc.interpreter.shell.LoopCommands.scala Maven / Gradle / Ivy

The newest version!
/*
 * Scala (https://www.scala-lang.org)
 *
 * Copyright EPFL and Lightbend, Inc.
 *
 * Licensed under Apache License 2.0
 * (http://www.apache.org/licenses/LICENSE-2.0).
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

package scala.tools.nsc.interpreter
package shell

import java.io.{PrintWriter => JPrintWriter}
import scala.language.implicitConversions
import scala.collection.mutable.ListBuffer
import scala.tools.nsc.interpreter.ReplStrings.words

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 {
  protected 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,
      val detailedHelp: Option[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
    override def toString(): String = name
  }
  object LoopCommand {
    def nullary(name: String, help: String, f: () => Result): LoopCommand =
      nullary(name, help, None, f)

    def nullary(name: String, help: String, detailedHelp: Option[String], f: () => Result): LoopCommand =
      new NullaryCmd(name, help, detailedHelp, _ => f())

    def cmd(name: String, usage: String, help: String, f: String => Result, completion: Completion = NoCompletion): LoopCommand =
      cmdWithHelp(name, usage, help, None, f, completion)

    def cmdWithHelp(name: String, usage: String, help: String, detailedHelp: Option[String],
      f: String => Result, completion: Completion = NoCompletion): LoopCommand =
      if (usage == "") new NullaryCmd(name, help, detailedHelp, f)
      else new LineCmd(name, usage, help, detailedHelp, f, completion)
  }

  /** print a friendly help message */
  def helpCommand(line: String): Result = line match {
    case ""                => helpSummary()
    case CommandMatch(cmd) => echo(f"%n${cmd.detailedHelp getOrElse 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))
    echo("")
    echo("Useful default key bindings:")
    echo("  TAB           code completion")
    echo("  CTRL-ALT-T    show type at cursor, hit again to show code with types/implicits inferred.")
  }
  def ambiguousError(cmd: String): Result = {
    matchingCommands(cmd) match {
      case Nil  => echo(s"No such command '$cmd'.  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 unique 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

  // expect line includes leading colon
  def colonCommand(line: String): Result = line.trim match {
    case ""                                  => helpSummary()
    case commandish(CommandMatch(cmd), rest) => cmd(rest)
    case commandish(name, _)                 => ambiguousError(name)
    case _                                   => echo("?")
  }

  def colonCompletion(line: String, cursor: Int): Completion =
    line match {
      case commandish(name0, rest) =>
        val name = name0 take cursor
        val cmds = matchingCommands(name)
        val cursorAtName = cursor <= name.length
        cmds match {
          case Nil                            => NoCompletion
          case cmd :: Nil if !cursorAtName    => cmd.completion
          case cmd :: Nil if cmd.name == name => NoCompletion
          case cmd :: Nil =>
            val completion = ":" + cmd.name
            new Completion {
              def complete(buffer: String, cursor: Int, filter: Boolean) =
                CompletionResult(buffer, cursor = 1, List(CompletionCandidate(completion)), "", "")
            }
          case cmd :: _ =>
            new Completion {
              def complete(buffer: String, cursor: Int, filter: Boolean) =
                CompletionResult(buffer, cursor = 1, cmds.map(cmd => CompletionCandidate(":" + cmd.name)), "", "")
            }
        }
      case _ => NoCompletion
    }

  class NullaryCmd(name: String, help: String, detailedHelp: Option[String],
    f: String => Result) extends LoopCommand(name, help, detailedHelp) {
    def apply(line: String): Result = f(line)
  }

  class LineCmd(name: String, argWord: String, help: String, detailedHelp: Option[String],
    f: String => Result, override val completion: Completion) extends LoopCommand(name, help, detailedHelp) {
    override def usage = argWord
    def apply(line: String): Result = f(line)
  }

  class VarArgsCmd(name: String, argWord: String, help: String, detailedHelp: Option[String],
      f: List[String] => Result) extends LoopCommand(name, help, detailedHelp) {
    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
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy