IDE 现象一则

背景

Win11 21H2(OS Build 22000.2538)

IntelliJ IDEA 2023.3 (Community Edition) (Scala Plugin 2023.3.19) + JDK 21

PlayScala Project (sbt 1.9.7, Scala 3.3.1, Play 3.0.1, sbt-plugin 3.0.1)

简述

在 Windows 上使用 Intellij IDEA 打开 PlayScala 项目,使用自带 Run Configurations 启用 Use sbt shell 运行 sbt run 命令,Play Server 会自动停止。

但未启用 Use sbt shell 或直接在 sbt shell 输入 run 命令,可以正常工作。

run.log
 [info] welcome to sbt 1.9.7 (Microsoft Java 21)
[info] loading global plugins from C:\Users\xxx\.sbt\1.0\plugins
[info] loading settings for project hello-play-scala-build from plugins.sbt,idea7.sbt ...
[info] loading project definition from C:\Users\xxx\Workspace\hello-play-scala\project
[info] loading settings for project root from build.sbt ...
[info]   __              __
[info]   \ \     ____   / /____ _ __  __
[info]    \ \   / __ \ / // __ `// / / /
[info]    / /  / /_/ // // /_/ // /_/ /
[info]   /_/  / .___//_/ \__,_/ \__, /
[info]       /_/               /____/
[info]
[info] Version 3.0.1 running Java 21
[info]
[info] Play is run entirely by the community. Please consider contributing and/or donating:
[info] https://www.playframework.com/sponsors
[info]
[info] Defining Global / ideaPort
[info] The new value will be used by Compile / compile, Test / compile
[info] Reapplying settings...
[info]   __              __
[info]   \ \     ____   / /____ _ __  __
[info]    \ \   / __ \ / // __ `// / / /
[info]    / /  / /_/ // // /_/ // /_/ /
[info]   /_/  / .___//_/ \__,_/ \__, /
[info]       /_/               /____/
[info]
[info] Version 3.0.1 running Java 21
[info]
[info] Play is run entirely by the community. Please consider contributing and/or donating:
[info] https://www.playframework.com/sponsors
[info]
[IJ][hello-play-scala] $ ~run
[info] sbt server started at local:sbt-server-4cb92cee932ea78bdec1
[info] started sbt server

--- (Running the application, auto-reloading is enabled) ---

INFO  p.c.s.PekkoHttpServer - Listening for HTTP on /[0:0:0:0:0:0:0:0]:9000

(Server started, use Enter to stop and go back to the console...)

INFO  p.a.i.l.c.CoordinatedShutdownSupport - Starting synchronous coordinated shutdown with ServerStoppedReason reason and 40000 milliseconds timeout
INFO  p.c.s.PekkoHttpServer - Stopping Pekko HTTP server...
INFO  p.c.s.PekkoHttpServer - Unbinding /[0:0:0:0:0:0:0:0]:9000
INFO  p.c.s.PekkoHttpServer - Terminating server binding for /[0:0:0:0:0:0:0:0]:9000
INFO  p.c.s.PekkoHttpServer - Running provided shutdown stop hooks

[success] Total time: 6 s, completed 27 Dec 2023, 13:51:04
[info] 1. Monitoring source files for root/run...
[info]    Press <enter> to interrupt or '?' for more options.
[info] Received input event: CancelWatch.

细节

0x00 TL;DR

feature 对抗之战。

0x01 feature "use Enter to stop"

此 feature 归口于 PlayFramework sbt-plugin

表现为,收到 '\r' 停止,收到 '\n' 输出换行并继续运行。

sbt-plugin!Play.sbt.PlayInteractionMode.scala
  private def waitForKey(): Unit = {
    withConsoleReader { consoleReader =>
      @tailrec def waitEOF(): Unit = {
        consoleReader.readCharacter() match {
          case 4 | 13 => // STOP on Ctrl-D or Enter
          case 11     => consoleReader.clearScreen(); waitEOF()
          case 10     => println(); waitEOF()
          case _      => waitEOF()
        }
      }
      doWithoutEcho(waitEOF())
    }
  }

0x02 feature "myPatchNewline"

此 feature 归口于 JetBrains/pty4j.

表现为,The "myPatchNewline" code in WinPTYOutputStream converts an LF character to CRLF.  

    Converting it to CR makes much more sense.  winpty interprets LF as Ctrl-RETURN rather than RETURN, and it interprets CRLF as two keypresses (RETURN followed by Ctrl-RETURN).  

事实上,启用 consoleMode 会打开 myPatchNewline 特性,以及打开错误流

com/pty4j/windows/WinPTYOutputStream.java
if (myPatchNewline) {
    byte[] newBuf = new byte[len];
    int newPos = 0;
    for (int i = off; i < off + len; ++i) {
        if (b[i] == '\n') {
            newBuf[newPos++] = '\r';
        } else {
            newBuf[newPos++] = b[i];
        }
    }
    b = newBuf;
    off = 0;
    len = newPos;
}

从代码可看出,启用此特性,'\n' 会被转为 '\r', '\r\n" 会被转为 "\r\r".

通过设置环境变量 WINPTY_DEBUG=trace,input ,打开 winpty-debugserver.exe (rprichard/winpty),可以看到 trace 信息。 

input chars: run^M^M (72 75 6E 0D 0D)
keypress: R ch='r'
keypress: U ch='u'
keypress: N ch='n'
keypress: RETURN ch=0xd
keypress: RETURN ch=0xd

0x03 Sbt / projectWindowActions

此 feature 归口于 JetBrains/intellij-scala.

intellij-scala/sbt-impl/org.jetbrains.sbt.shell/communication.scala
private def processCommand(commandAndListener: (String, CommandListener[_])): Unit = {
    val (cmd, listener) = commandAndListener

    listener.started()

    val handler = process.acquireShellProcessHandler()
    handler.addProcessListener(listener)

    process.usingWriter { shell =>
      shell.println(cmd)
      shell.flush()
    }
    listener.future.onComplete { _ =>
      handler.removeProcessListener(listener)
    }
  }

此处 PrintWriter.println 会输出 System.lineSeparator, 即 CRLF.

所以,在 sbt task 节点右键运行 (SbtNodeAction) 执行命令也是相同表现。

0x04 SbtShellExecuteActionHandler

intellij-community/com.intellij.execution.console/ProcessBackedConsoleExecuteActionHandler
protected void execute(String text, LanguageConsoleView console) {
    processLine(text);
}

public void processLine(String line) {
    sendText(line + "\n");
}

在 IDEA 控制台输入命令执行,只有 LF.

更多

Play Framework

限 IntelliJ IDEA Ultimate, 支持 Play 2.4+.

参考

  1. https://stackoverflow.com/questions/70670665/play-application-stops-immediately-when-running-with-intellij-configuration
  2. https://www.jetbrains.com/help/idea/getting-started-with-play-framework.html
posted @ 2023-12-26 00:46  UPeRVv  阅读(9)  评论(0编辑  收藏  举报