diff --git a/modules/build/src/main/scala/scala/build/Build.scala b/modules/build/src/main/scala/scala/build/Build.scala index 51e7587a64..4bcf57cd2a 100644 --- a/modules/build/src/main/scala/scala/build/Build.scala +++ b/modules/build/src/main/scala/scala/build/Build.scala @@ -17,7 +17,7 @@ import scala.build.errors.* import scala.build.input.VirtualScript.VirtualScriptNameRegex import scala.build.input.* import scala.build.internal.resource.ResourceMapper -import scala.build.internal.{Constants, MainClass, Util} +import scala.build.internal.{Constants, MainClass, Name, Util} import scala.build.options.ScalaVersionUtil.asVersion import scala.build.options.* import scala.build.options.validation.ValidationException @@ -76,12 +76,19 @@ object Build { case _ => inferredMainClass(mainClasses, logger) .left.flatMap { mainClasses => + // decode the names to present them to the user, + // but keep the link to each original name to account for package prefixes: + // "pack.Main$minus1" decodes to "pack.Main-1", which encodes back to "pack$u002EMain$minus1" + // ^^^^^^^^^^^^^^^^----------------NOT THE SAME-----------------------^^^^^^^^^^^^^^^^^^^^^ + val decodedToEncoded = mainClasses.map(mc => Name.decoded(mc) -> mc).toMap + options.interactive.flatMap { interactive => interactive .chooseOne( "Found several main classes. Which would you like to run?", - mainClasses.toList + decodedToEncoded.keys.toList ) + .map(decodedToEncoded(_)) // encode back the name of the chosen class .toRight { SeveralMainClassesFoundError( ::(mainClasses.head, mainClasses.tail.toList), diff --git a/modules/core/src/main/scala/scala/build/internals/Name.scala b/modules/core/src/main/scala/scala/build/internals/Name.scala index 9a04f57803..e08044b974 100644 --- a/modules/core/src/main/scala/scala/build/internals/Name.scala +++ b/modules/core/src/main/scala/scala/build/internals/Name.scala @@ -17,6 +17,8 @@ case class Name(raw: String) { object Name { + def decoded(name: String) = NameTransformer.decode(name) + val alphaKeywords = Set( "abstract", "case", diff --git a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala index 616b199813..176021408b 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/RunTestDefinitions.scala @@ -1555,6 +1555,39 @@ abstract class RunTestDefinitions(val scalaVersionOpt: Option[String]) } } + test("decoded classNames in interactive ask") { + val fileName = "watch.scala" + + val inputs = TestInputs( + os.rel / fileName -> + """object `Run-1` extends App {println("Run-1 launched")} + |object `Run-2` extends App {println("Run-2 launched")} + |""".stripMargin + ) + inputs.fromRoot { root => + val confDir = root / "config" + val confFile = confDir / "test-config.json" + + os.write(confFile, "{\"interactive-was-suggested\":true}", createFolders = true) + + if (!Properties.isWin) + os.perms.set(confDir, "rwx------") + + val configEnv = Map("SCALA_CLI_CONFIG" -> confFile.toString) + + val proc = os.proc(TestUtil.cli, "run", "--interactive", fileName) + .call( + cwd = root, + mergeErrIntoOut = true, + env = Map("SCALA_CLI_INTERACTIVE_INPUTS" -> "Run-1") ++ configEnv + ) + + expect(proc.out.trim.contains("[0] Run-1")) + expect(proc.out.trim.contains("[1] Run-2")) + expect(proc.out.trim.contains("Run-1 launched")) + } + } + test("BuildInfo fields should be reachable") { val inputs = TestInputs( os.rel / "Main.scala" ->