Sfoglia il codice sorgente

Terminal (#179)

* Rename ConsolePure to ConsoleTerminal

* Fix the way to close console with Ctrl+C

* Fix windows pipeline error. Fix EndOfFileException

* Add ConsoleExperimentalApi

* Collect imports

* Review

- Change old CLI main deprecation level to ERROR
- Update documents
- Update tasks from pure to terminal

* Fix error in closing console.

* Fix terminal closing and Ctrl+C closing.

* Add console shut-downing status

* Don't invokeOnCompletion when console shut-downing

* Fix Input interrupt

* Ensure active unless console is shut downing

* Change MiraiConsole.isShutDowning to `!job.isActive`

* Code Review

- Update Message on MiraiConsolePureLoader.kt
- Change MiraiConsole.isShutDowning to MiraiConsole.isActive
- Change MiraiConsole.shutdown to ConsoleInternalApi

* run catching

* Fix console input

* Update shutdown

* Fix module

* Revert 5199395

* Typo
Karlatemp 5 anni fa
parent
commit
8fe2506e75
19 ha cambiato i file con 256 aggiunte e 114 eliminazioni
  1. 2 2
      .github/workflows/bintray.yml
  2. 2 2
      .github/workflows/shadow.yml
  3. 3 4
      README.md
  4. 5 0
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt
  5. 3 1
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/BuiltInJvmPluginLoaderImpl.kt
  6. 1 0
      backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt
  7. 1 2
      buildSrc/src/main/kotlin/Versions.kt
  8. 8 8
      docs/Run.md
  9. 3 3
      frontend/mirai-console-terminal/build.gradle.kts
  10. 34 0
      frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt
  11. 2 1
      frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/BufferedOutputStream.kt
  12. 74 0
      frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleInputImpl.kt
  13. 5 4
      frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleTerminalSettings.kt
  14. 25 12
      frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt
  15. 46 40
      frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleImplementationTerminal.kt
  16. 28 21
      frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt
  17. 11 11
      frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/noconsole/NoConsole.kt
  18. 1 1
      settings.gradle.kts
  19. 2 2
      tools/intellij-plugin/run/projects/test-project/src/test/kotlin/RunConsole.kt

+ 2 - 2
.github/workflows/bintray.yml

@@ -32,6 +32,6 @@ jobs:
         run: ./gradlew :mirai-console:fillBuildConstants -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
       - name: Gradle :mirai-console:bintrayUpload
         run: ./gradlew :mirai-console:bintrayUpload -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
-      - name: Gradle :mirai-console-pure:bintrayUpload
-        run: ./gradlew :mirai-console-pure:bintrayUpload -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
+      - name: Gradle :mirai-console-terminal:bintrayUpload
+        run: ./gradlew :mirai-console-terminal:bintrayUpload -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} -Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
 

+ 2 - 2
.github/workflows/shadow.yml

@@ -28,8 +28,8 @@ jobs:
         run: ./gradlew build # if test's failed, don't publish
       - name: Gradle :mirai-console:githubUpload
         run: ./gradlew :mirai-console:githubUpload -Dgithub_token=${{ secrets.MAMOE_TOKEN }} -Pgithub_token=${{ secrets.MAMOE_TOKEN }}
-      - name: Gradle :mirai-console-pure:githubUpload
-        run: ./gradlew :mirai-console-pure:githubUpload -Dgithub_token=${{ secrets.MAMOE_TOKEN }} -Pgithub_token=${{ secrets.MAMOE_TOKEN }}
+      - name: Gradle :mirai-console-terminal:githubUpload
+        run: ./gradlew :mirai-console-terminal:githubUpload -Dgithub_token=${{ secrets.MAMOE_TOKEN }} -Pgithub_token=${{ secrets.MAMOE_TOKEN }}
 
 
 #      - name: Upload artifact

+ 3 - 4
README.md

@@ -28,12 +28,11 @@ console 由后端和前端一起工作. 使用时必须选择一个前端.
 
 前端:
 
-- `mirai-console-pure`: console 的轻量命令行前端.
+- `mirai-console-terminal`: console 的 Unix 终端界面前端.
 - `mirai-console-graphical`: console 的 JavaFX 图形化界面前端. (开发中)
-- `mirai-console-terminal`: console 的 Unix 终端界面前端. (开发中)
 
 
-**注意:`mirai-console` 后端和 pure 前端正在进行完全的重构, 所有 API 都不具有稳定性**
+**注意:`mirai-console` 后端和 terminal 前端正在进行完全的重构, 所有 API 都不具有稳定性**
 
 ### 使用
 
@@ -56,7 +55,7 @@ dependencies {
   implementation("net.mamoe:mirai-core:$CORE_VERSION") // mirai-core 的 API
   implementation("net.mamoe:mirai-console:$CONSOLE_VERSION") // 后端
   
-  testImplementation("net.mamoe:mirai-console-pure:$CONSOLE_VERSION") // 前端, 用于启动测试
+  testImplementation("net.mamoe:mirai-console-terminal:$CONSOLE_VERSION") // 前端, 用于启动测试
 }
 ```
 

+ 5 - 0
backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/MiraiConsole.kt

@@ -18,6 +18,7 @@ import kotlinx.coroutines.Job
 import net.mamoe.mirai.Bot
 import net.mamoe.mirai.console.MiraiConsole.INSTANCE
 import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
+import net.mamoe.mirai.console.command.BuiltInCommands
 import net.mamoe.mirai.console.extensions.BotConfigurationAlterer
 import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge
 import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage
@@ -146,6 +147,10 @@ public interface MiraiConsole : CoroutineScope {
                 else -> null!!
             }
         }
+
+        @ConsoleExperimentalApi("This is a low-level API and might be removed in the future.")
+        public val isActive: Boolean
+            get() = job.isActive
     }
 }
 

+ 3 - 1
backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/BuiltInJvmPluginLoaderImpl.kt

@@ -106,7 +106,9 @@ internal object BuiltInJvmPluginLoaderImpl :
 
     override fun disable(plugin: JvmPlugin) {
         if (!plugin.isEnabled) return
-        ensureActive()
+
+        if (MiraiConsole.isActive)
+            ensureActive()
 
         if (plugin is JvmPluginInternal) {
             plugin.internalOnDisable()

+ 1 - 0
backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/internal/plugin/JvmPluginInternal.kt

@@ -151,6 +151,7 @@ internal abstract class JvmPluginInternal(
                 )
             )
             .also {
+                if (!MiraiConsole.isActive) return@also
                 BuiltInJvmPluginLoaderImpl.coroutineContext[Job]!!.invokeOnCompletion {
                     this.cancel()
                 }

+ 1 - 2
buildSrc/src/main/kotlin/Versions.kt

@@ -11,8 +11,7 @@ object Versions {
     const val core = "1.3.0"
     const val console = "1.0-RC-dev-2"
     const val consoleGraphical = "0.0.7"
-    const val consoleTerminal = "0.1.0"
-    const val consolePure = console
+    const val consoleTerminal = console
 
     const val kotlinCompiler = "1.4.10"
     const val kotlinStdlib = kotlinCompiler

+ 8 - 8
docs/Run.md

@@ -19,16 +19,16 @@ https://github.com/LXY1226/MiraiOK
 - mirai-console 任一前端
 - 相关依赖
 
-只有 mirai-console 前端才有入口点 `main` 方法。目前只有一个 pure 前端可用。
+只有 mirai-console 前端才有入口点 `main` 方法。目前只有一个 terminal 前端可用。
 
-### 启动 mirai-console-pure 前端
+### 启动 mirai-console-terminal 前端
 
 mirai 在版本发布时会同时发布打包依赖的 Shadow JAR,存放在 [mirai-repo]。
 
 1. 在 [mirai-repo] 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`):
    - mirai-core-qqandroid
    - mirai-console
-   - mirai-console-pure
+   - mirai-console-terminal
 
 2. 创建一个新的文件, 名为 `start-mirai-console.bat`/`start-mirai-console.ps1`/`start-mirai-console.sh`
 
@@ -36,26 +36,26 @@ Windows CMD:
 ```shell script
 @echo off
 title Mirai Console
-java -cp "./libs/*" net.mamoe.mirai.console.pure.MiraiConsolePureLoader %*
+java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader %*
 pause
 ```
 
 Windows PowerShell:
 ```shell script
 $Host.UI.RawUI.WindowTitle = "Mirai Console"
-java -cp "./libs/*" net.mamoe.mirai.console.pure.MiraiConsolePureLoader $args
+java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $args
 pause
 ```
 
 Linux:
 ```shell script
 #!/usr/bin/env bash
-java -cp "./libs/*" net.mamoe.mirai.console.pure.MiraiConsolePureLoader $*
+java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $*
 ```
 
 然后就可以开始使用 mirai-console 了
 
-### mirai-console-pure 前端参数
-使用 `./start-mirai-console --help` 查看 mirai-console-pure 支持的启动参数
+### mirai-console-terminal 前端参数
+使用 `./start-mirai-console --help` 查看 mirai-console-terminal 支持的启动参数
 
 [mirai-repo]: https://github.com/project-mirai/mirai-repo/tree/master/shadow

+ 3 - 3
frontend/mirai-console-pure/build.gradle.kts → frontend/mirai-console-terminal/build.gradle.kts

@@ -66,10 +66,10 @@ ext.apply {
     this.set("shadowJar", x)
 }
 
-version = Versions.consolePure
+version = Versions.consoleTerminal
 
-description = "Console Pure CLI frontend for mirai"
+description = "Console Terminal CLI frontend for mirai"
 
-setupPublishing("mirai-console-pure", bintrayPkgName = "mirai-console-pure")
+setupPublishing("mirai-console-terminal", bintrayPkgName = "mirai-console-terminal")
 
 // endregion

+ 34 - 0
frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt

@@ -0,0 +1,34 @@
+/*
+ * Copyright 2019-2020 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/master/LICENSE
+ *
+ */
+
+package net.mamoe.mirai.console.pure
+
+import net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader
+
+@Deprecated(
+    message = "Please use MiraiConsoleTerminalLoader",
+    level = DeprecationLevel.ERROR,
+    replaceWith = ReplaceWith(
+        "MiraiConsoleTerminalLoader",
+        "net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader"
+    )
+)
+object MiraiConsolePureLoader {
+    @Deprecated(
+        message = "for binary compatibility",
+        level = DeprecationLevel.ERROR
+    )
+    @JvmStatic
+    fun main(args: Array<String>) {
+        System.err.println("WARNING: Mirai Console Pure已经更名为 Mirai Console Terminal")
+        System.err.println("请使用新的入口点 net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader")
+        MiraiConsoleTerminalLoader.main(args)
+    }
+}

+ 2 - 1
frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/BufferedOutputStream.kt → frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/BufferedOutputStream.kt

@@ -5,9 +5,10 @@
  * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
  *
  * https://github.com/mamoe/mirai/blob/master/LICENSE
+ *
  */
 
-package net.mamoe.mirai.console.pure
+package net.mamoe.mirai.console.terminal
 
 import java.io.ByteArrayOutputStream
 import java.io.OutputStream

+ 74 - 0
frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleInputImpl.kt

@@ -0,0 +1,74 @@
+/*
+ * Copyright 2019-2020 Mamoe Technologies and contributors.
+ *
+ * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
+ * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
+ *
+ * https://github.com/mamoe/mirai/blob/master/LICENSE
+ *
+ */
+
+package net.mamoe.mirai.console.terminal
+
+import kotlinx.coroutines.CancellableContinuation
+import kotlinx.coroutines.suspendCancellableCoroutine
+import net.mamoe.mirai.console.util.ConsoleInput
+import org.fusesource.jansi.Ansi
+import org.jline.reader.EndOfFileException
+import java.time.Instant
+import java.time.LocalDateTime
+import java.time.ZoneId
+import java.time.format.DateTimeFormatter
+import java.util.concurrent.Executors
+import kotlin.coroutines.resumeWithException
+
+
+internal object ConsoleInputImpl : ConsoleInput {
+    private val format = DateTimeFormatter.ofPattern("HH:mm:ss")
+    internal val thread = Executors.newSingleThreadExecutor { task ->
+        Thread(task, "Mirai Console Input Thread").also {
+            it.isDaemon = false
+        }
+    }
+    internal var executingCoroutine: CancellableContinuation<String>? = null
+
+
+    override suspend fun requestInput(hint: String): String {
+        return suspendCancellableCoroutine { coroutine ->
+            if (thread.isShutdown || thread.isTerminated) {
+                coroutine.resumeWithException(EndOfFileException())
+                return@suspendCancellableCoroutine
+            }
+            executingCoroutine = coroutine
+            kotlin.runCatching {
+                thread.submit {
+                    kotlin.runCatching {
+                        lineReader.readLine(
+                            if (hint.isNotEmpty()) {
+                                lineReader.printAbove(
+                                    Ansi.ansi()
+                                        .fgCyan()
+                                        .a(
+                                            LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault())
+                                                .format(format)
+                                        )
+                                        .a(" ")
+                                        .fgMagenta().a(hint)
+                                        .reset()
+                                        .toString()
+                                )
+                                "$hint > "
+                            } else "> "
+                        )
+                    }.let { result ->
+                        executingCoroutine = null
+                        coroutine.resumeWith(result)
+                    }
+                }
+            }.onFailure { error ->
+                executingCoroutine = null
+                kotlin.runCatching { coroutine.resumeWithException(EndOfFileException(error)) }
+            }
+        }
+    }
+}

+ 5 - 4
frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsolePureSettings.kt → frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleTerminalSettings.kt

@@ -5,12 +5,13 @@
  * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
  *
  * https://github.com/mamoe/mirai/blob/master/LICENSE
+ *
  */
 /*
  * @author Karlatemp <[email protected]> <https://github.com/Karlatemp>
  */
 
-package net.mamoe.mirai.console.pure
+package net.mamoe.mirai.console.terminal
 
 @Retention(AnnotationRetention.BINARY)
 @RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@@ -23,10 +24,10 @@ package net.mamoe.mirai.console.pure
     AnnotationTarget.CONSTRUCTOR
 )
 @MustBeDocumented
-annotation class ConsolePureExperimentalApi
+annotation class ConsoleTerminalExperimentalApi
 
-@ConsolePureExperimentalApi
-public object ConsolePureSettings {
+@ConsoleTerminalExperimentalApi
+public object ConsoleTerminalSettings {
     @JvmField
     var setupAnsi: Boolean = System.getProperty("os.name")
         .toLowerCase()

+ 25 - 12
frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/ConsoleThread.kt → frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/ConsoleThread.kt

@@ -5,13 +5,14 @@
  * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
  *
  * https://github.com/mamoe/mirai/blob/master/LICENSE
+ *
  */
 
-package net.mamoe.mirai.console.pure
+package net.mamoe.mirai.console.terminal
 
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineName
-import kotlinx.coroutines.cancel
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
 import net.mamoe.mirai.console.MiraiConsole
 import net.mamoe.mirai.console.command.BuiltInCommands
@@ -20,18 +21,33 @@ import net.mamoe.mirai.console.command.CommandExecuteStatus
 import net.mamoe.mirai.console.command.CommandManager
 import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
 import net.mamoe.mirai.console.command.ConsoleCommandSender
+import net.mamoe.mirai.console.terminal.noconsole.NoConsole
 import net.mamoe.mirai.console.util.ConsoleInternalApi
 import net.mamoe.mirai.console.util.requestInput
 import net.mamoe.mirai.utils.DefaultLogger
+import org.jline.reader.EndOfFileException
 import org.jline.reader.UserInterruptException
 
 val consoleLogger by lazy { DefaultLogger("console") }
 
-@OptIn(ConsoleInternalApi::class, ConsolePureExperimentalApi::class)
+@OptIn(ConsoleInternalApi::class, ConsoleTerminalExperimentalApi::class)
 internal fun startupConsoleThread() {
-    if (ConsolePureSettings.noConsole) return
+    if (terminal is NoConsole) return
 
-    MiraiConsole.launch(CoroutineName("Input")) {
+    MiraiConsole.launch(CoroutineName("Input Cancelling Daemon")) {
+        while (true) {
+            delay(2000)
+        }
+    }.invokeOnCompletion {
+        runCatching<Unit> {
+            terminal.close()
+            ConsoleInputImpl.thread.shutdownNow()
+            runCatching {
+                ConsoleInputImpl.executingCoroutine?.cancel(EndOfFileException())
+            }
+        }.exceptionOrNull()?.printStackTrace()
+    }
+    MiraiConsole.launch(CoroutineName("Console Command")) {
         while (true) {
             try {
                 val next = MiraiConsole.requestInput("").let {
@@ -65,17 +81,14 @@ internal fun startupConsoleThread() {
             } catch (e: CancellationException) {
                 return@launch
             } catch (e: UserInterruptException) {
-                MiraiConsole.cancel()
+                BuiltInCommands.StopCommand.run { ConsoleCommandSender.handle() }
+                return@launch
+            } catch (eof: EndOfFileException) {
+                consoleLogger.warning("Closing input service...")
                 return@launch
             } catch (e: Throwable) {
                 consoleLogger.error("Unhandled exception", e)
             }
         }
     }
-
-    MiraiConsole.job.invokeOnCompletion {
-        runCatching {
-            terminal.close()
-        }.exceptionOrNull()?.printStackTrace()
-    }
 }

+ 46 - 40
frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsoleImplementationPure.kt → frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleImplementationTerminal.kt

@@ -5,6 +5,7 @@
  * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
  *
  * https://github.com/mamoe/mirai/blob/master/LICENSE
+ *
  */
 
 @file:Suppress(
@@ -17,13 +18,16 @@
     "INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER_WARNING",
     "EXPOSED_SUPER_CLASS"
 )
-@file:OptIn(ConsoleInternalApi::class, ConsoleFrontEndImplementation::class, ConsolePureExperimentalApi::class)
+@file:OptIn(ConsoleInternalApi::class, ConsoleFrontEndImplementation::class, ConsoleTerminalExperimentalApi::class)
 
-package net.mamoe.mirai.console.pure
+package net.mamoe.mirai.console.terminal
 
 
 import com.vdurmont.semver4j.Semver
-import kotlinx.coroutines.*
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineExceptionHandler
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
 import net.mamoe.mirai.console.ConsoleFrontEndImplementation
 import net.mamoe.mirai.console.MiraiConsole
 import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription
@@ -32,10 +36,10 @@ import net.mamoe.mirai.console.data.MultiFilePluginDataStorage
 import net.mamoe.mirai.console.data.PluginDataStorage
 import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
 import net.mamoe.mirai.console.plugin.loader.PluginLoader
-import net.mamoe.mirai.console.pure.ConsoleInputImpl.requestInput
+import net.mamoe.mirai.console.terminal.ConsoleInputImpl.requestInput
+import net.mamoe.mirai.console.terminal.noconsole.AllEmptyLineReader
+import net.mamoe.mirai.console.terminal.noconsole.NoConsole
 import net.mamoe.mirai.console.util.ConsoleExperimentalApi
-import net.mamoe.mirai.console.pure.noconsole.AllEmptyLineReader
-import net.mamoe.mirai.console.pure.noconsole.NoConsole
 import net.mamoe.mirai.console.util.ConsoleInput
 import net.mamoe.mirai.console.util.ConsoleInternalApi
 import net.mamoe.mirai.console.util.NamedSupervisorJob
@@ -46,31 +50,28 @@ import org.jline.reader.LineReaderBuilder
 import org.jline.reader.impl.completer.NullCompleter
 import org.jline.terminal.Terminal
 import org.jline.terminal.TerminalBuilder
+import org.jline.terminal.impl.AbstractWindowsTerminal
 import java.nio.file.Path
 import java.nio.file.Paths
-import java.time.Instant
-import java.time.LocalDateTime
-import java.time.ZoneId
-import java.time.format.DateTimeFormatter
 
 /**
- * mirai-console-pure 后端实现
+ * mirai-console-terminal 后端实现
  *
- * @see MiraiConsolePureLoader CLI 入口点
+ * @see MiraiConsoleTerminalLoader CLI 入口点
  */
 @ConsoleExperimentalApi
-class MiraiConsoleImplementationPure
+class MiraiConsoleImplementationTerminal
 @JvmOverloads constructor(
     override val rootPath: Path = Paths.get(".").toAbsolutePath(),
     override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> = listOf(lazy { JvmPluginLoader }),
     override val frontEndDescription: MiraiConsoleFrontEndDescription = ConsoleFrontEndDescImpl,
-    override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl = ConsoleCommandSenderImplPure,
+    override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl = ConsoleCommandSenderImplTerminal,
     override val dataStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")),
     override val dataStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")),
     override val configStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")),
     override val configStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")),
 ) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope(
-    NamedSupervisorJob("MiraiConsoleImplementationPure") +
+    NamedSupervisorJob("MiraiConsoleImplementationTerminal") +
             CoroutineExceptionHandler { coroutineContext, throwable ->
                 if (throwable is CancellationException) {
                     return@CoroutineExceptionHandler
@@ -94,30 +95,9 @@ class MiraiConsoleImplementationPure
     }
 }
 
-private object ConsoleInputImpl : ConsoleInput {
-    private val format = DateTimeFormatter.ofPattern("HH:mm:ss")
-
-    override suspend fun requestInput(hint: String): String {
-        return withContext(Dispatchers.IO) {
-            lineReader.readLine(
-                if (hint.isNotEmpty()) {
-                    lineReader.printAbove(
-                        Ansi.ansi()
-                            .fgCyan().a(LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()).format(format))
-                            .a(" ")
-                            .fgMagenta().a(hint)
-                            .reset()
-                            .toString()
-                    )
-                    "$hint > "
-                } else "> "
-            )
-        }
-    }
-}
-
 val lineReader: LineReader by lazy {
-    if (ConsolePureSettings.noConsole) return@lazy AllEmptyLineReader
+    val terminal = terminal
+    if (terminal is NoConsole) return@lazy AllEmptyLineReader
 
     LineReaderBuilder.builder()
         .terminal(terminal)
@@ -126,7 +106,7 @@ val lineReader: LineReader by lazy {
 }
 
 val terminal: Terminal = run {
-    if (ConsolePureSettings.noConsole) return@run NoConsole
+    if (ConsoleTerminalSettings.noConsole) return@run NoConsole
 
     val dumb = System.getProperty("java.class.path")
         .contains("idea_rt.jar") || System.getProperty("mirai.idea") !== null || System.getenv("mirai.idea") !== null
@@ -134,7 +114,33 @@ val terminal: Terminal = run {
     runCatching {
         TerminalBuilder.builder()
             .dumb(dumb)
+            .paused(true)
             .build()
+            .let { terminal ->
+                if (terminal is AbstractWindowsTerminal) {
+                    val pumpField = runCatching {
+                        AbstractWindowsTerminal::class.java.getDeclaredField("pump").also {
+                            it.isAccessible = true
+                        }
+                    }.onFailure { err ->
+                        err.printStackTrace()
+                        return@let terminal.also { it.resume() }
+                    }.getOrThrow()
+                    var response = terminal
+                    terminal.setOnClose {
+                        response = NoConsole
+                    }
+                    terminal.resume()
+                    val pumpThread = pumpField[terminal] as? Thread ?: return@let NoConsole
+                    @Suppress("ControlFlowWithEmptyBody")
+                    while (pumpThread.state == Thread.State.NEW);
+                    Thread.sleep(1000)
+                    terminal.setOnClose(null)
+                    return@let response
+                }
+                terminal.resume()
+                terminal
+            }
     }.recoverCatching {
         TerminalBuilder.builder()
             .jansi(true)
@@ -147,7 +153,7 @@ val terminal: Terminal = run {
 }
 
 private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription {
-    override val name: String get() = "Pure"
+    override val name: String get() = "Terminal"
     override val vendor: String get() = "Mamoe Technologies"
     override val version: Semver = net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version
 }

+ 28 - 21
frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt → frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/MiraiConsoleTerminalLoader.kt

@@ -5,6 +5,7 @@
  * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link.
  *
  * https://github.com/mamoe/mirai/blob/master/LICENSE
+ *
  */
 
 @file:Suppress(
@@ -15,9 +16,9 @@
     "INVISIBLE_GETTER",
     "INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER",
 )
-@file:OptIn(ConsoleInternalApi::class, ConsolePureExperimentalApi::class)
+@file:OptIn(ConsoleInternalApi::class, ConsoleTerminalExperimentalApi::class)
 
-package net.mamoe.mirai.console.pure
+package net.mamoe.mirai.console.terminal
 
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
@@ -26,20 +27,22 @@ import net.mamoe.mirai.console.MiraiConsole
 import net.mamoe.mirai.console.MiraiConsoleImplementation
 import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
 import net.mamoe.mirai.console.data.AutoSavePluginDataHolder
-import net.mamoe.mirai.console.pure.noconsole.SystemOutputPrintStream
+import net.mamoe.mirai.console.terminal.noconsole.SystemOutputPrintStream
 import net.mamoe.mirai.console.util.ConsoleExperimentalApi
 import net.mamoe.mirai.console.util.ConsoleInternalApi
 import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
 import net.mamoe.mirai.message.data.Message
 import net.mamoe.mirai.utils.DefaultLogger
 import net.mamoe.mirai.utils.minutesToMillis
+import java.io.FileDescriptor
+import java.io.FileOutputStream
 import java.io.PrintStream
 import kotlin.system.exitProcess
 
 /**
- * mirai-console-pure CLI 入口点
+ * mirai-console-terminal CLI 入口点
  */
-object MiraiConsolePureLoader {
+object MiraiConsoleTerminalLoader {
     @JvmStatic
     fun main(args: Array<String>) {
         parse(args, exitProcess = true)
@@ -53,10 +56,10 @@ object MiraiConsolePureLoader {
         }
     }
 
-    @ConsolePureExperimentalApi
+    @ConsoleTerminalExperimentalApi
     fun printHelpMessage() {
         val help = listOf(
-            "" to "Mirai-Console[Pure FrontEnd] v" + kotlin.runCatching {
+            "" to "Mirai-Console[Terminal FrontEnd] v" + kotlin.runCatching {
                 net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version
             }.getOrElse { "<unknown>" },
             "" to "",
@@ -96,7 +99,7 @@ object MiraiConsolePureLoader {
         }
     }
 
-    @ConsolePureExperimentalApi
+    @ConsoleTerminalExperimentalApi
     fun parse(args: Array<String>, exitProcess: Boolean = false) {
         val iterator = args.iterator()
         while (iterator.hasNext()) {
@@ -107,19 +110,19 @@ object MiraiConsolePureLoader {
                     return
                 }
                 "--no-console" -> {
-                    ConsolePureSettings.noConsole = true
+                    ConsoleTerminalSettings.noConsole = true
                 }
                 "--dont-setup-terminal-ansi" -> {
-                    ConsolePureSettings.setupAnsi = false
+                    ConsoleTerminalSettings.setupAnsi = false
                 }
                 "--no-ansi" -> {
-                    ConsolePureSettings.noAnsi = true
-                    ConsolePureSettings.setupAnsi = false
+                    ConsoleTerminalSettings.noAnsi = true
+                    ConsoleTerminalSettings.setupAnsi = false
                 }
                 "--reading-replacement" -> {
-                    ConsolePureSettings.noConsoleSafeReading = true
+                    ConsoleTerminalSettings.noConsoleSafeReading = true
                     if (iterator.hasNext()) {
-                        ConsolePureSettings.noConsoleReadingReplacement = iterator.next()
+                        ConsoleTerminalSettings.noConsoleReadingReplacement = iterator.next()
                     } else {
                         println("Bad option `--reading-replacement`")
                         println("Usage: --reading-replacement <string>")
@@ -129,7 +132,7 @@ object MiraiConsolePureLoader {
                     }
                 }
                 "--safe-reading" -> {
-                    ConsolePureSettings.noConsoleSafeReading = true
+                    ConsoleTerminalSettings.noConsoleSafeReading = true
                 }
                 else -> {
                     println("Unknown option `$option`")
@@ -140,13 +143,13 @@ object MiraiConsolePureLoader {
                 }
             }
         }
-        if (ConsolePureSettings.noConsole)
+        if (ConsoleTerminalSettings.noConsole)
             SystemOutputPrintStream // Setup Output Channel
     }
 
     @Suppress("MemberVisibilityCanBePrivate")
     @ConsoleExperimentalApi
-    fun startAsDaemon(instance: MiraiConsoleImplementationPure = MiraiConsoleImplementationPure()) {
+    fun startAsDaemon(instance: MiraiConsoleImplementationTerminal = MiraiConsoleImplementationTerminal()) {
         instance.start()
         overrideSTD()
         startupConsoleThread()
@@ -160,7 +163,7 @@ internal object ConsoleDataHolder : AutoSavePluginDataHolder,
 
     @ConsoleExperimentalApi
     override val dataHolderName: String
-        get() = "Pure"
+        get() = "Terminal"
 }
 
 internal fun overrideSTD() {
@@ -181,12 +184,16 @@ internal fun overrideSTD() {
 }
 
 
-internal object ConsoleCommandSenderImplPure : MiraiConsoleImplementation.ConsoleCommandSenderImpl {
+internal object ConsoleCommandSenderImplTerminal : MiraiConsoleImplementation.ConsoleCommandSenderImpl {
     override suspend fun sendMessage(message: String) {
         kotlin.runCatching {
             lineReader.printAbove(message)
-        }.onFailure {
-            consoleLogger.error("Exception while ConsoleCommandSenderImplPure.sendMessage", it)
+        }.onFailure { exception ->
+            // If failed. It means JLine Terminal not working...
+            PrintStream(FileOutputStream(FileDescriptor.err)).use {
+                it.println("Exception while ConsoleCommandSenderImplTerminal.sendMessage")
+                exception.printStackTrace(it)
+            }
         }
     }
 

+ 11 - 11
frontend/mirai-console-pure/src/main/kotlin/net/mamoe/mirai/console/pure/noconsole/NoConsole.kt → frontend/mirai-console-terminal/src/main/kotlin/net/mamoe/mirai/console/terminal/noconsole/NoConsole.kt

@@ -10,12 +10,12 @@
 /*
  * @author Karlatemp <[email protected]> <https://github.com/Karlatemp>
  */
-@file:OptIn(ConsolePureExperimentalApi::class)
+@file:OptIn(ConsoleTerminalExperimentalApi::class)
 
-package net.mamoe.mirai.console.pure.noconsole
+package net.mamoe.mirai.console.terminal.noconsole
 
-import net.mamoe.mirai.console.pure.ConsolePureExperimentalApi
-import net.mamoe.mirai.console.pure.ConsolePureSettings
+import net.mamoe.mirai.console.terminal.ConsoleTerminalExperimentalApi
+import net.mamoe.mirai.console.terminal.ConsoleTerminalSettings
 import org.jline.keymap.KeyMap
 import org.jline.reader.*
 import org.jline.terminal.Attributes
@@ -69,8 +69,8 @@ internal object AllIgnoredOutputStream : OutputStream() {
 }
 
 internal val SystemOutputPrintStream by lazy {
-    @OptIn(ConsolePureExperimentalApi::class)
-    if (ConsolePureSettings.setupAnsi) {
+    @OptIn(ConsoleTerminalExperimentalApi::class)
+    if (ConsoleTerminalSettings.setupAnsi) {
         org.fusesource.jansi.AnsiConsole.systemInstall()
     }
     System.out
@@ -81,16 +81,16 @@ internal object AllEmptyLineReader : LineReader {
 
     override fun printAbove(str: String?) {
         if (str == null) return
-        @OptIn(ConsolePureExperimentalApi::class)
-        if (ConsolePureSettings.noAnsi) {
+        @OptIn(ConsoleTerminalExperimentalApi::class)
+        if (ConsoleTerminalSettings.noAnsi) {
             SystemOutputPrintStream.println(ANSI_REGEX.replace(str, ""))
         } else SystemOutputPrintStream.println(str)
     }
 
-    @OptIn(ConsolePureExperimentalApi::class)
+    @OptIn(ConsoleTerminalExperimentalApi::class)
     override fun readLine(): String =
-        if (ConsolePureSettings.noConsoleSafeReading) ConsolePureSettings.noConsoleReadingReplacement
-        else error("Unsupported Reading line when console front-end closed.")
+        if (ConsoleTerminalSettings.noConsoleSafeReading) ConsoleTerminalSettings.noConsoleReadingReplacement
+        else throw EndOfFileException("Unsupported Reading line when console front-end closed.")
 
     // region
     private fun <T> ignored(): T = error("Ignored")

+ 1 - 1
settings.gradle.kts

@@ -19,7 +19,7 @@ fun includeProject(projectPath: String, path: String? = null) {
 
 includeProject(":mirai-console", "backend/mirai-console")
 includeProject(":mirai-console.codegen", "backend/codegen")
-includeProject(":mirai-console-pure", "frontend/mirai-console-pure")
+includeProject(":mirai-console-terminal", "frontend/mirai-console-terminal")
 includeProject(":mirai-console-compiler-common", "tools/compiler-common")
 includeProject(":mirai-console-intellij", "tools/intellij-plugin")
 includeProject(":mirai-console-gradle", "tools/gradle-plugin")

+ 2 - 2
tools/intellij-plugin/run/projects/test-project/src/test/kotlin/RunConsole.kt

@@ -2,11 +2,11 @@ import net.mamoe.mirai.alsoLogin
 import net.mamoe.mirai.console.MiraiConsole
 import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable
 import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.load
-import net.mamoe.mirai.console.pure.MiraiConsolePureLoader
+import net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader
 import org.example.myplugin.MyPluginMain
 
 suspend fun main() {
-    MiraiConsolePureLoader.startAsDaemon()
+    MiraiConsoleTerminalLoader.startAsDaemon()
 
     MyPluginMain.load() // 主动加载插件, Console 会调用 MyPluginMain.onLoad
     MyPluginMain.enable() // 主动启用插件, Console 会调用 MyPluginMain.onEnable