Merge pull request #7236 from eed3si9n/wip/new2

Make new more interactive
This commit is contained in:
eugene yokota 2023-05-06 23:09:27 -04:00 committed by GitHub
commit 0c11ae2ace
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 122 additions and 31 deletions

View File

@ -176,9 +176,40 @@ trait Terminal extends AutoCloseable {
else 0
}
private[sbt] def flush(): Unit = printStream.flush()
private[sbt] def readArrow: Int = withRawInput {
val in = System.in
val ESC = '\u001B'
val EOT = '\u0004'
var result: Int = -1
def readBracket: Int =
in.read() match {
case '[' => readAnsiControl
case _ => 0
}
def readAnsiControl: Int =
in.read() match {
case 'A' => Terminal.VK_UP
case 'B' => Terminal.VK_DOWN
case 'C' => Terminal.VK_RIGHT
case 'D' => Terminal.VK_LEFT
case _ => 0
}
in.read() match {
case ESC => readBracket
// Ctrl+D to quit
case EOT => -1
case c => c
}
}
}
object Terminal {
private[sbt] final val VK_UP = 256
private[sbt] final val VK_DOWN = 257
private[sbt] final val VK_RIGHT = 258
private[sbt] final val VK_LEFT = 259
val NO_BOOT_CLIENTS_CONNECTED: Int = -2
// Disable noisy jline log spam
if (System.getProperty("sbt.jline.verbose", "false") != "true")

View File

@ -110,6 +110,7 @@ private[sbt] object xMain {
Seq(defaults, early),
runEarly(DefaultsCommand) :: runEarly(InitCommand) :: BootCommand :: Nil
)
.put(BasicKeys.detachStdio, detachStdio)
StandardMain.runManaged(state)
}
case _ if clientModByEnv || userCommands.exists(isClient) =>

View File

@ -15,6 +15,7 @@ import sbt.BasicCommandStrings.TerminateAction
import sbt.SlashSyntax0._
import sbt.io._, syntax._
import sbt.util._
import sbt.internal.util.{ ConsoleAppender, Terminal => ITerminal }
import sbt.internal.util.complete.{ DefaultParsers, Parser }, DefaultParsers._
import xsbti.AppConfiguration
import sbt.librarymanagement._
@ -161,39 +162,96 @@ private[sbt] object TemplateCommandUtil {
private final val ScalaToolkitSlug = "scala/toolkit.local"
private final val TypelevelToolkitSlug = "typelevel/toolkit.local"
private final val SbtCrossPlatformSlug = "sbt/cross-platform.local"
private def fortifyArgs(): List[String] = {
val templates = List(
ScalaToolkitSlug -> "Scala Toolkit (beta) by Scala Center and VirtusLab",
TypelevelToolkitSlug -> "Toolkit to start building Typelevel apps",
SbtCrossPlatformSlug -> "A cross-JVM/JS/Native project",
"scala/scala-seed.g8" -> "Scala 2 seed template",
"playframework/play-scala-seed.g8" -> "A Play project in Scala",
"playframework/play-java-seed.g8" -> "A Play project in Java",
"scala-js/vite.g8" -> "A Scala.JS + Vite project",
"holdenk/sparkProjectTemplate.g8" -> "A Scala Spark project",
"spotify/scio.g8" -> "A Scio project",
"disneystreaming/smithy4s.g8" -> "A Smithy4s project",
)
val mappingList = templates.zipWithIndex.map {
case (v, idx) => (idx + 1) -> v
private lazy val term: ITerminal = ITerminal.get
private lazy val isAnsiSupported = term.isAnsiSupported
private lazy val nonMoveLetters = ('a' to 'z').toList diff List('h', 'j', 'k', 'l', 'q')
private lazy val templates = List(
ScalaToolkitSlug -> "Scala Toolkit (beta) by Scala Center and VirtusLab",
TypelevelToolkitSlug -> "Toolkit to start building Typelevel apps",
SbtCrossPlatformSlug -> "A cross-JVM/JS/Native project",
"scala/scala-seed.g8" -> "Scala 2 seed template",
"playframework/play-scala-seed.g8" -> "A Play project in Scala",
"playframework/play-java-seed.g8" -> "A Play project in Java",
"scala-js/vite.g8" -> "A Scala.JS + Vite project",
"holdenk/sparkProjectTemplate.g8" -> "A Scala Spark project",
"spotify/scio.g8" -> "A Scio project",
"disneystreaming/smithy4s.g8" -> "A Smithy4s project",
)
private def fortifyArgs(): List[String] =
if (System.console eq null) Nil
else
ITerminal.withStreams(true, false) {
val mappingList = templates.zipWithIndex.map {
case (v, idx) => nonMoveLetters(idx).toString -> v
}
val out = term.printStream
out.println("")
out.println("Welcome to sbt new!")
out.println("Here are some templates to get started:")
val ans = askTemplate(mappingList, 0)
val mappings = Map(mappingList: _*)
mappings.get(ans).map(_._1).toList
}
private def askTemplate(mappingList: List[(String, (String, String))], focus: Int): String = {
val msg = "Select a template"
displayMappings(mappingList, focus)
val focusValue = ('a' + focus).toChar.toString
if (!isAnsiSupported) ask(msg, focusValue)
else {
val out = term.printStream
out.print(s"$msg: ")
val ans0 = term.readArrow
def printThenReturn(ans: String): String = {
out.println(ans) // this is necessary to move the cursor
out.flush()
ans
}
ans0 match {
case '\r' | '\n' => printThenReturn(focusValue)
case 'q' | 'Q' | -1 => printThenReturn("")
case 'j' | 'J' | ITerminal.VK_DOWN =>
clearMenu(mappingList)
askTemplate(mappingList, math.min(focus + 1, mappingList.size - 1))
case 'k' | 'K' | ITerminal.VK_UP =>
clearMenu(mappingList)
askTemplate(mappingList, math.max(focus - 1, 0))
case c if nonMoveLetters.contains(c.toChar) =>
printThenReturn(c.toChar.toString)
case _ =>
clearMenu(mappingList)
askTemplate(mappingList, focus)
}
}
System.out.println("")
System.out.println("Welcome to sbt new!")
System.out.println("Here are some templates to get started:")
mappingList.foreach {
case (k, (slug, desc)) =>
val key = if (k < 10) s" $k" else k.toString
System.out.println(s" $key) ${slug.padTo(33, ' ')} - $desc")
}
private def clearMenu(mappingList: List[(String, (String, String))]): Unit = {
val out = term.printStream
out.print(ConsoleAppender.CursorLeft1000)
out.print(ConsoleAppender.cursorUp(mappingList.size + 1))
}
private def displayMappings(mappingList: List[(String, (String, String))], focus: Int): Unit = {
import scala.Console.{ RESET, REVERSED }
val out = term.printStream
mappingList.zipWithIndex.foreach {
case ((k, (slug, desc)), idx) =>
if (idx == focus && isAnsiSupported) {
out.print(REVERSED)
}
out.print(s" $k) ${slug.padTo(33, ' ')} - $desc")
if (idx == focus && isAnsiSupported) {
out.print(RESET)
}
out.println()
}
System.out.println(" q) quit")
val ans = ask("Select a template", "1")
val mappings = Map((mappingList.map { case (k, v) => k.toString -> v }): _*)
mappings.get(ans).map(_._1).toList
out.println(" q) quit")
out.flush()
}
private def ask(question: String, default: String): String = {
System.out.print(s"$question (default: $default): ")
val ans0 = System.console().readLine()
val ans0 = System.console.readLine()
if (ans0 == "") default
else ans0
}
@ -212,16 +270,17 @@ private[sbt] object TemplateCommandUtil {
private final val defaultScalaV = "3.2.2"
private def scalaToolkitTemplate(): Unit = {
val defaultScalaToolkitV = "0.1.6"
val defaultScalaToolkitV = "0.1.7"
val scalaV = ask("Scala version", defaultScalaV)
val toolkitV = ask("Scala Toolkit version", defaultScalaToolkitV)
val content = s"""
val toolkit = "org.scala-lang" %% "toolkit" % "$toolkitV"
// val toolkitTest = "org.scala-lang" %% "toolkit-test" % "$toolkitV"
val toolkitV = "$toolkitV"
val toolkit = "org.scala-lang" %% "toolkit" % toolkitV
val toolkitTest = "org.scala-lang" %% "toolkit-test" % toolkitV
ThisBuild / scalaVersion := "$scalaV"
libraryDependencies += toolkit
// libraryDependencies += (toolkitTest % Test)
libraryDependencies += (toolkitTest % Test)
"""
IO.write(new File("build.sbt"), content)
copyResource("ScalaMain.scala.txt", new File("src/main/scala/example/Main.scala"))