VirtusLab / scala-cli

Scala CLI is a command-line tool to interact with the Scala language. It lets you compile, run, test, and package your Scala code (and more!)
https://scala-cli.virtuslab.org
Apache License 2.0
541 stars 128 forks source link

--interactive should not close the terminal after piping Scala code #2185

Open bphenriques opened 1 year ago

bphenriques commented 1 year ago

Version(s) 1.0.0

Describe what needs to be done and why I want to automate a repl that has several libs and imports by default. My current ammonite setup:

object load {
  def fs2Version(version: String) = {
    repl.load.apply(s"""
      import $$ivy.`co.fs2::fs2-core:$version`
      import $$ivy.`co.fs2::fs2-reactive-streams:$version`
      import $$ivy.`co.fs2::fs2-io:$version`

      import cats.implicits._
      import cats.syntax.all._
      import cats.effect.{IO, Resource}
      import fs2.io.file.{Files, Path}
      import fs2.{Stream, text}

      // For unsafeRunSync
      implicit val runtime = cats.effect.unsafe.IORuntime.global
    """)
  }

  def fs2 = fs2Version("3.3.0")
}

Which makes it very easy to have a quick typelevel repl by calling load.fs2.

I am trying to replace ammonite for scala-cli, so I am exploring existing command line options for the same purpose, however the terminal exits right away.

~ ❯ echo 'import cats.implicits._; import cats.syntax.all._; import cats.effect.{IO, Resource}; import fs2.io.file.{Files, Path}; import fs2.{Stream, text}; implicit val runtime = cats.effect.unsafe.IORuntime.global' | scala-cli repl --scala 2 --toolkit typelevel:latest --interactive
Welcome to Scala 2.13.10 (OpenJDK 64-Bit Server VM, Java 19.0.1).
Type in expressions for evaluation. Or try :help.

scala> import cats.implicits._; import cats.syntax.all._; import cats.effect.{IO, Resource}; import fs2.io.file.{Files, Path}; import fs2.{Stream, text}import cats.implicits._
import cats.syntax.all._
import cats.effect.{IO, Resource}
import fs2.io.file.{Files, Path}
import fs2.{Stream, text}
val runtime: cats.effect.unsafe.IORuntime = IORuntime(cats.effect.unsafe.WorkStealingThreadPool@3dd750ba, cats.effect.unsafe.WorkStealingThreadPool@3dd750ba, IORuntimeConfig(512,1024,true,16,Duration.Inf,true,1 second,10 seconds,0.1))

scala> :quit

I am unsure if this is a bug, but it seems to be. I may be using scala-cli incorrectly, hence my request for feedback.

Is your feature request related to a past ticket or discussion? AFAIK no.

Describe alternatives you've considered It won't change much, but I considering moving the Scala code to a file and piping the file to scala-cli.

lwronski commented 1 year ago

Hi @bphenriques I sorry for the delayed response. I think I've found a potential solution to your issue. Try running the following command:

{ echo "import cats.implicits._; import cats.syntax.all._; import cats.effect.{IO, Resource}; import fs2.io.file.{Files, Path}; import fs2.{Stream, text}; implicit val runtime = cats.effect.unsafe.IORuntime.global"; cat; } | scala-cli repl --scala 2 --toolkit typelevel:latest

The trick here is using the cat command after the echo. This keeps the input stream open, allowing for interaction with the REPL later. However, I'm not sure if this will work with all shells.

lwronski commented 1 year ago

Unfortunately, the above trick with cat works only with Scala 2. For Scala 3, it throws the following error:

$ { echo "import cats.implicits._; import cats.syntax.all._; import cats.effect.{IO, Resource}; import fs2.io.file.{Files, Path}; import fs2.{Stream, text}; implicit val runtime = cats.effect.unsafe.IORuntime.global"; cat; } | scala-cli repl  --toolkit typelevel:latest 

Exception in thread "main" java.lang.IllegalStateException: Unable to create a system terminal
    at org.jline.terminal.TerminalBuilder.doBuild(TerminalBuilder.java:323)
    at org.jline.terminal.TerminalBuilder.build(TerminalBuilder.java:265)
    at dotty.tools.repl.JLineTerminal.<init>(JLineTerminal.scala:25)
    at dotty.tools.repl.ReplDriver.runUntilQuit(ReplDriver.scala:143)
    at dotty.tools.repl.ReplDriver.tryRunning(ReplDriver.scala:134)
    at dotty.tools.repl.Main$.main(Main.scala:7)
    at dotty.tools.repl.Main.main(Main.scala)
bphenriques commented 1 year ago

@lwronski thank you! I will try out once I got back from my holidays and I will let you know

Intuitively, shouldn't --interactive allow me to keep "interacting" with the repl? Similar to how we run docker run -it <image> /bin/bash.

bphenriques commented 1 year ago

Attempted to run the Scala 2 version and it partially worked:

Edit: Regarding Scala 3, I have the same output.

SethTisue commented 1 year ago

@bphenriques inserting cat into the pipeline is presumably severing the connection with the terminal and thus disabling JLine

bjornregnell commented 1 year ago

sbt can do this with something similar to this in your build.sbt:

Compile / console / initialCommands  := """
  import aCoolLibDepObjectOrPackage.*
  println("Up and running!")
"""

Now when you type console in sbt the REPL executes this stuff before you can continue with your REPL experiments, so it is possible also for Scala 3 to make this work already today without updating the compiler.

I think this is a very useful use case for scala-cli

bphenriques commented 1 year ago

The way I see, this ticket is somewhat conflating two different concerns unintentionally (my bad):

  1. Bug perhaps: --interactive should not close the terminal after piping Scala code
  2. Feature request: support running arbitrary code before running REPL (what https://github.com/VirtusLab/scala-cli/issues/604 talks about).

I would be content if 1. was addressed as it is good enough for me (I can create an wrapper) for it. I may have ideas for 2. but it might be preferable to discuss over the other issue given that it is an API concern - up to the maintainers.

bjornregnell commented 1 year ago

@tgodzik Perhaps consider separating the to things by renaming this issue and open #604 again?

(I guess fixing --interactive not closing when piping from cat is tricky because of jline not started with forced "dumb" terminal?)

lwronski commented 1 year ago

(I guess fixing --interactive not closing when piping from cat is tricky because of jline not started with forced "dumb" terminal?)

I also think that it's tricky. So, I believe it's reasonable to limit this issue to only adding support for initialCommands in REPL. This addresses real use cases of users. However, the trick with cat doesn't seem like a good solution to use.

Gedochao commented 1 month ago

Renamed this to ensure it's clear the current requirements are separate from #2732

bjornregnell commented 1 month ago

Perhaps there can be a scala-cli option that forces a "dumb" jline or similar so that it at least does not crash when piping?

bjornregnell commented 1 month ago

For reference: here is an example by @mpollmeier of how the repl can be used for piping: https://github.com/mpollmeier/scala-repl-pp

Gedochao commented 1 month ago

@bjornregnell @mbovel Likely this is the same as https://github.com/scala/scala3/issues/11978