* 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
This commit is contained in:
Karlatemp 2020-09-18 12:43:57 +08:00 committed by GitHub
parent f990b7ce8c
commit 8fe2506e75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 256 additions and 114 deletions

View File

@ -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 }} 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 - 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 }} 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 - name: Gradle :mirai-console-terminal: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 }} 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 }}

View File

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

View File

@ -28,12 +28,11 @@ console 由后端和前端一起工作. 使用时必须选择一个前端.
前端: 前端:
- `mirai-console-pure`: console 的轻量命令行前端. - `mirai-console-terminal`: console 的 Unix 终端界面前端.
- `mirai-console-graphical`: console 的 JavaFX 图形化界面前端. (开发中) - `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-core:$CORE_VERSION") // mirai-core 的 API
implementation("net.mamoe:mirai-console:$CONSOLE_VERSION") // 后端 implementation("net.mamoe:mirai-console:$CONSOLE_VERSION") // 后端
testImplementation("net.mamoe:mirai-console-pure:$CONSOLE_VERSION") // 前端, 用于启动测试 testImplementation("net.mamoe:mirai-console-terminal:$CONSOLE_VERSION") // 前端, 用于启动测试
} }
``` ```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -66,10 +66,10 @@ ext.apply {
this.set("shadowJar", x) 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 // endregion

View File

@ -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)
}
}

View File

@ -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. * 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 * 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.ByteArrayOutputStream
import java.io.OutputStream import java.io.OutputStream

View File

@ -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)) }
}
}
}
}

View File

@ -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. * 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 * https://github.com/mamoe/mirai/blob/master/LICENSE
*
*/ */
/* /*
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp> * @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
*/ */
package net.mamoe.mirai.console.pure package net.mamoe.mirai.console.terminal
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
@RequiresOptIn(level = RequiresOptIn.Level.WARNING) @RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@ -23,10 +24,10 @@ package net.mamoe.mirai.console.pure
AnnotationTarget.CONSTRUCTOR AnnotationTarget.CONSTRUCTOR
) )
@MustBeDocumented @MustBeDocumented
annotation class ConsolePureExperimentalApi annotation class ConsoleTerminalExperimentalApi
@ConsolePureExperimentalApi @ConsoleTerminalExperimentalApi
public object ConsolePureSettings { public object ConsoleTerminalSettings {
@JvmField @JvmField
var setupAnsi: Boolean = System.getProperty("os.name") var setupAnsi: Boolean = System.getProperty("os.name")
.toLowerCase() .toLowerCase()

View File

@ -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. * 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 * 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.CancellationException
import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.cancel import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.command.BuiltInCommands 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
import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand
import net.mamoe.mirai.console.command.ConsoleCommandSender 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.ConsoleInternalApi
import net.mamoe.mirai.console.util.requestInput import net.mamoe.mirai.console.util.requestInput
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.DefaultLogger
import org.jline.reader.EndOfFileException
import org.jline.reader.UserInterruptException import org.jline.reader.UserInterruptException
val consoleLogger by lazy { DefaultLogger("console") } val consoleLogger by lazy { DefaultLogger("console") }
@OptIn(ConsoleInternalApi::class, ConsolePureExperimentalApi::class) @OptIn(ConsoleInternalApi::class, ConsoleTerminalExperimentalApi::class)
internal fun startupConsoleThread() { 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) { while (true) {
try { try {
val next = MiraiConsole.requestInput("").let { val next = MiraiConsole.requestInput("").let {
@ -65,17 +81,14 @@ internal fun startupConsoleThread() {
} catch (e: CancellationException) { } catch (e: CancellationException) {
return@launch return@launch
} catch (e: UserInterruptException) { } catch (e: UserInterruptException) {
MiraiConsole.cancel() BuiltInCommands.StopCommand.run { ConsoleCommandSender.handle() }
return@launch
} catch (eof: EndOfFileException) {
consoleLogger.warning("Closing input service...")
return@launch return@launch
} catch (e: Throwable) { } catch (e: Throwable) {
consoleLogger.error("Unhandled exception", e) consoleLogger.error("Unhandled exception", e)
} }
} }
} }
MiraiConsole.job.invokeOnCompletion {
runCatching {
terminal.close()
}.exceptionOrNull()?.printStackTrace()
}
} }

View File

@ -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. * 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 * https://github.com/mamoe/mirai/blob/master/LICENSE
*
*/ */
@file:Suppress( @file:Suppress(
@ -17,13 +18,16 @@
"INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER_WARNING", "INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER_WARNING",
"EXPOSED_SUPER_CLASS" "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 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.ConsoleFrontEndImplementation
import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsole
import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription 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.data.PluginDataStorage
import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader
import net.mamoe.mirai.console.plugin.loader.PluginLoader 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.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.ConsoleInput
import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.ConsoleInternalApi
import net.mamoe.mirai.console.util.NamedSupervisorJob 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.reader.impl.completer.NullCompleter
import org.jline.terminal.Terminal import org.jline.terminal.Terminal
import org.jline.terminal.TerminalBuilder import org.jline.terminal.TerminalBuilder
import org.jline.terminal.impl.AbstractWindowsTerminal
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths 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 @ConsoleExperimentalApi
class MiraiConsoleImplementationPure class MiraiConsoleImplementationTerminal
@JvmOverloads constructor( @JvmOverloads constructor(
override val rootPath: Path = Paths.get(".").toAbsolutePath(), override val rootPath: Path = Paths.get(".").toAbsolutePath(),
override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> = listOf(lazy { JvmPluginLoader }), override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> = listOf(lazy { JvmPluginLoader }),
override val frontEndDescription: MiraiConsoleFrontEndDescription = ConsoleFrontEndDescImpl, 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 dataStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")),
override val dataStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")), override val dataStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")),
override val configStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")), override val configStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")),
override val configStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")), override val configStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")),
) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope( ) : MiraiConsoleImplementation, CoroutineScope by CoroutineScope(
NamedSupervisorJob("MiraiConsoleImplementationPure") + NamedSupervisorJob("MiraiConsoleImplementationTerminal") +
CoroutineExceptionHandler { coroutineContext, throwable -> CoroutineExceptionHandler { coroutineContext, throwable ->
if (throwable is CancellationException) { if (throwable is CancellationException) {
return@CoroutineExceptionHandler 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 { val lineReader: LineReader by lazy {
if (ConsolePureSettings.noConsole) return@lazy AllEmptyLineReader val terminal = terminal
if (terminal is NoConsole) return@lazy AllEmptyLineReader
LineReaderBuilder.builder() LineReaderBuilder.builder()
.terminal(terminal) .terminal(terminal)
@ -126,7 +106,7 @@ val lineReader: LineReader by lazy {
} }
val terminal: Terminal = run { val terminal: Terminal = run {
if (ConsolePureSettings.noConsole) return@run NoConsole if (ConsoleTerminalSettings.noConsole) return@run NoConsole
val dumb = System.getProperty("java.class.path") val dumb = System.getProperty("java.class.path")
.contains("idea_rt.jar") || System.getProperty("mirai.idea") !== null || System.getenv("mirai.idea") !== null .contains("idea_rt.jar") || System.getProperty("mirai.idea") !== null || System.getenv("mirai.idea") !== null
@ -134,7 +114,33 @@ val terminal: Terminal = run {
runCatching { runCatching {
TerminalBuilder.builder() TerminalBuilder.builder()
.dumb(dumb) .dumb(dumb)
.paused(true)
.build() .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 { }.recoverCatching {
TerminalBuilder.builder() TerminalBuilder.builder()
.jansi(true) .jansi(true)
@ -147,7 +153,7 @@ val terminal: Terminal = run {
} }
private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription { 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 vendor: String get() = "Mamoe Technologies"
override val version: Semver = net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version override val version: Semver = net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version
} }

View File

@ -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. * 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 * https://github.com/mamoe/mirai/blob/master/LICENSE
*
*/ */
@file:Suppress( @file:Suppress(
@ -15,9 +16,9 @@
"INVISIBLE_GETTER", "INVISIBLE_GETTER",
"INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER", "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.CancellationException
import kotlinx.coroutines.CoroutineScope 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
import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start
import net.mamoe.mirai.console.data.AutoSavePluginDataHolder 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.ConsoleExperimentalApi
import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.ConsoleInternalApi
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.minutesToMillis import net.mamoe.mirai.utils.minutesToMillis
import java.io.FileDescriptor
import java.io.FileOutputStream
import java.io.PrintStream import java.io.PrintStream
import kotlin.system.exitProcess import kotlin.system.exitProcess
/** /**
* mirai-console-pure CLI 入口点 * mirai-console-terminal CLI 入口点
*/ */
object MiraiConsolePureLoader { object MiraiConsoleTerminalLoader {
@JvmStatic @JvmStatic
fun main(args: Array<String>) { fun main(args: Array<String>) {
parse(args, exitProcess = true) parse(args, exitProcess = true)
@ -53,10 +56,10 @@ object MiraiConsolePureLoader {
} }
} }
@ConsolePureExperimentalApi @ConsoleTerminalExperimentalApi
fun printHelpMessage() { fun printHelpMessage() {
val help = listOf( 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 net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version
}.getOrElse { "<unknown>" }, }.getOrElse { "<unknown>" },
"" to "", "" to "",
@ -96,7 +99,7 @@ object MiraiConsolePureLoader {
} }
} }
@ConsolePureExperimentalApi @ConsoleTerminalExperimentalApi
fun parse(args: Array<String>, exitProcess: Boolean = false) { fun parse(args: Array<String>, exitProcess: Boolean = false) {
val iterator = args.iterator() val iterator = args.iterator()
while (iterator.hasNext()) { while (iterator.hasNext()) {
@ -107,19 +110,19 @@ object MiraiConsolePureLoader {
return return
} }
"--no-console" -> { "--no-console" -> {
ConsolePureSettings.noConsole = true ConsoleTerminalSettings.noConsole = true
} }
"--dont-setup-terminal-ansi" -> { "--dont-setup-terminal-ansi" -> {
ConsolePureSettings.setupAnsi = false ConsoleTerminalSettings.setupAnsi = false
} }
"--no-ansi" -> { "--no-ansi" -> {
ConsolePureSettings.noAnsi = true ConsoleTerminalSettings.noAnsi = true
ConsolePureSettings.setupAnsi = false ConsoleTerminalSettings.setupAnsi = false
} }
"--reading-replacement" -> { "--reading-replacement" -> {
ConsolePureSettings.noConsoleSafeReading = true ConsoleTerminalSettings.noConsoleSafeReading = true
if (iterator.hasNext()) { if (iterator.hasNext()) {
ConsolePureSettings.noConsoleReadingReplacement = iterator.next() ConsoleTerminalSettings.noConsoleReadingReplacement = iterator.next()
} else { } else {
println("Bad option `--reading-replacement`") println("Bad option `--reading-replacement`")
println("Usage: --reading-replacement <string>") println("Usage: --reading-replacement <string>")
@ -129,7 +132,7 @@ object MiraiConsolePureLoader {
} }
} }
"--safe-reading" -> { "--safe-reading" -> {
ConsolePureSettings.noConsoleSafeReading = true ConsoleTerminalSettings.noConsoleSafeReading = true
} }
else -> { else -> {
println("Unknown option `$option`") println("Unknown option `$option`")
@ -140,13 +143,13 @@ object MiraiConsolePureLoader {
} }
} }
} }
if (ConsolePureSettings.noConsole) if (ConsoleTerminalSettings.noConsole)
SystemOutputPrintStream // Setup Output Channel SystemOutputPrintStream // Setup Output Channel
} }
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
@ConsoleExperimentalApi @ConsoleExperimentalApi
fun startAsDaemon(instance: MiraiConsoleImplementationPure = MiraiConsoleImplementationPure()) { fun startAsDaemon(instance: MiraiConsoleImplementationTerminal = MiraiConsoleImplementationTerminal()) {
instance.start() instance.start()
overrideSTD() overrideSTD()
startupConsoleThread() startupConsoleThread()
@ -160,7 +163,7 @@ internal object ConsoleDataHolder : AutoSavePluginDataHolder,
@ConsoleExperimentalApi @ConsoleExperimentalApi
override val dataHolderName: String override val dataHolderName: String
get() = "Pure" get() = "Terminal"
} }
internal fun overrideSTD() { 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) { override suspend fun sendMessage(message: String) {
kotlin.runCatching { kotlin.runCatching {
lineReader.printAbove(message) lineReader.printAbove(message)
}.onFailure { }.onFailure { exception ->
consoleLogger.error("Exception while ConsoleCommandSenderImplPure.sendMessage", it) // If failed. It means JLine Terminal not working...
PrintStream(FileOutputStream(FileDescriptor.err)).use {
it.println("Exception while ConsoleCommandSenderImplTerminal.sendMessage")
exception.printStackTrace(it)
}
} }
} }

View File

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

View File

@ -19,7 +19,7 @@ fun includeProject(projectPath: String, path: String? = null) {
includeProject(":mirai-console", "backend/mirai-console") includeProject(":mirai-console", "backend/mirai-console")
includeProject(":mirai-console.codegen", "backend/codegen") 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-compiler-common", "tools/compiler-common")
includeProject(":mirai-console-intellij", "tools/intellij-plugin") includeProject(":mirai-console-intellij", "tools/intellij-plugin")
includeProject(":mirai-console-gradle", "tools/gradle-plugin") includeProject(":mirai-console-gradle", "tools/gradle-plugin")

View File

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