Working without Terminal

This commit is contained in:
Karlatemp 2020-09-11 19:22:55 +08:00
parent ca4107b476
commit f1015915ba
No known key found for this signature in database
GPG Key ID: 21FBDDF664FF06F8
5 changed files with 368 additions and 3 deletions

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2018-2020 Karlatemp. All rights reserved.
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
*
* LuckPerms-Mirai/mirai-console.mirai-console-pure.main/ConsolePureSettings.kt
*
* 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/Karlatemp/LuckPerms-Mirai/blob/master/LICENSE
*/
/*
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
*/
package net.mamoe.mirai.console.pure
@Retention(AnnotationRetention.BINARY)
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY,
AnnotationTarget.FIELD,
AnnotationTarget.CONSTRUCTOR
)
@MustBeDocumented
annotation class ConsolePureExperimentalAPI
@ConsolePureExperimentalAPI
public object ConsolePureSettings {
@JvmField
var setupAnsi: Boolean = System.getProperty("os.name")
.toLowerCase()
.contains("windows")// Just for Windows
@JvmField
var noConsole: Boolean = false
@JvmField
var dropAnsi = false
@JvmField
var noConsoleSafeReading=false
}

View File

@ -27,8 +27,10 @@ import org.jline.reader.UserInterruptException
val consoleLogger by lazy { DefaultLogger("console") }
@OptIn(ConsoleInternalAPI::class)
@OptIn(ConsoleInternalAPI::class, ConsolePureExperimentalAPI::class)
internal fun startupConsoleThread() {
if (ConsolePureSettings.noConsole) return
MiraiConsole.launch(CoroutineName("Input")) {
while (true) {
try {

View File

@ -17,7 +17,7 @@
"INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER_WARNING",
"EXPOSED_SUPER_CLASS"
)
@file:OptIn(ConsoleInternalAPI::class, ConsoleFrontEndImplementation::class)
@file:OptIn(ConsoleInternalAPI::class, ConsoleFrontEndImplementation::class, ConsolePureExperimentalAPI::class)
package net.mamoe.mirai.console.pure
@ -34,6 +34,8 @@ import net.mamoe.mirai.console.plugin.DeferredPluginLoader
import net.mamoe.mirai.console.plugin.PluginLoader
import net.mamoe.mirai.console.plugin.jvm.JarPluginLoader
import net.mamoe.mirai.console.pure.ConsoleInputImpl.requestInput
import net.mamoe.mirai.console.pure.noconsole.AllEmptyLineReader
import net.mamoe.mirai.console.pure.noconsole.NoConsole
import net.mamoe.mirai.console.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleInput
import net.mamoe.mirai.console.util.ConsoleInternalAPI
@ -119,6 +121,8 @@ private object ConsoleInputImpl : ConsoleInput {
}
val lineReader: LineReader by lazy {
if (ConsolePureSettings.noConsole) return@lazy AllEmptyLineReader
LineReaderBuilder.builder()
.terminal(terminal)
.completer(NullCompleter())
@ -126,6 +130,8 @@ val lineReader: LineReader by lazy {
}
val terminal: Terminal = run {
if (ConsolePureSettings.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

View File

@ -15,7 +15,7 @@
"INVISIBLE_GETTER",
"INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER",
)
@file:OptIn(ConsoleInternalAPI::class)
@file:OptIn(ConsoleInternalAPI::class, ConsolePureExperimentalAPI::class)
package net.mamoe.mirai.console.pure
@ -26,6 +26,7 @@ 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.util.ConsoleExperimentalAPI
import net.mamoe.mirai.console.util.ConsoleInternalAPI
import net.mamoe.mirai.console.util.CoroutineScopeUtils.childScope
@ -33,6 +34,7 @@ import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.minutesToMillis
import java.io.PrintStream
import kotlin.system.exitProcess
/**
* mirai-console-pure CLI 入口点
@ -40,6 +42,7 @@ import java.io.PrintStream
object MiraiConsolePureLoader {
@JvmStatic
fun main(args: Array<String>) {
parse(args, exitProcess = true)
startAsDaemon()
try {
runBlocking {
@ -50,6 +53,75 @@ object MiraiConsolePureLoader {
}
}
@ConsolePureExperimentalAPI
fun printlnHelpMessage() {
val help = mapOf(
"--help" to "显示此帮助",
"--no-console" to "使用无终端操作环境",
"--dont-setup-terminal-ansi" to
"[NoConsole] [Windows Only]\n" +
"不进行ansi console初始化工作",
"--drop-ansi" to "[NoConsole] 禁用 ansi",
"--safe-reading" to
"[NoConsole] 如果启动此选项, console在获取用户输入的时候会获得一个安全的空字符串\n" +
" 如果不启动, 将会直接 error",
)
val prefixPlaceholder = String(CharArray(
help.keys.maxOfOrNull { it.length }!! + 3
) { ' ' })
fun printOption(optionName: String, value: String) {
print(optionName)
print(prefixPlaceholder.substring(optionName.length))
val lines = value.split('\n').iterator()
if (lines.hasNext()) println(lines.next())
lines.forEach { line ->
print(prefixPlaceholder)
println(line)
}
}
help.entries.forEach { (optionName, value) ->
printOption(optionName, value)
}
}
@ConsolePureExperimentalAPI
fun parse(args: Array<String>, exitProcess: Boolean = false) {
val iterator = args.iterator()
while (iterator.hasNext()) {
when (val option = iterator.next()) {
"--help" -> {
printlnHelpMessage()
if (exitProcess) exitProcess(0)
return
}
"--no-console" -> {
ConsolePureSettings.noConsole = true
}
"--dont-setup-terminal-ansi" -> {
ConsolePureSettings.setupAnsi = false
}
"--drop-ansi" -> {
ConsolePureSettings.dropAnsi = true
ConsolePureSettings.setupAnsi = false
}
"--safe-reading" -> {
ConsolePureSettings.noConsoleSafeReading = true
}
else -> {
println("Unknown option `$option`")
printlnHelpMessage()
if (exitProcess)
@Suppress("UNREACHABLE_CODE")
exitProcess(1)
return
}
}
}
if (ConsolePureSettings.noConsole)
SystemOutputPrintStream // Setup Output Channel
}
@Suppress("MemberVisibilityCanBePrivate")
@ConsoleExperimentalAPI
fun startAsDaemon(instance: MiraiConsoleImplementationPure = MiraiConsoleImplementationPure()) {

View File

@ -0,0 +1,240 @@
/*
* Copyright (c) 2018-2020 Karlatemp. All rights reserved.
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
*
* LuckPerms-Mirai/mirai-console.mirai-console-pure.main/NoConsole.kt
*
* 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/Karlatemp/LuckPerms-Mirai/blob/master/LICENSE
*/
/*
* @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp>
*/
package net.mamoe.mirai.console.pure.noconsole
import net.mamoe.mirai.console.pure.ConsolePureExperimentalAPI
import net.mamoe.mirai.console.pure.ConsolePureSettings
import org.jline.keymap.KeyMap
import org.jline.reader.*
import org.jline.terminal.Attributes
import org.jline.terminal.MouseEvent
import org.jline.terminal.Size
import org.jline.terminal.Terminal
import org.jline.terminal.impl.AbstractTerminal
import org.jline.utils.AttributedString
import org.jline.utils.NonBlockingReader
import java.io.File
import java.io.InputStream
import java.io.OutputStream
import java.io.PrintWriter
private const val SPACE_INT = ' '.toInt()
private const val SPACE_BYTE = ' '.toByte()
internal object NoNonBlockingReader : NonBlockingReader() {
override fun read(timeout: Long, isPeek: Boolean): Int {
return SPACE_INT
}
override fun close() {
}
override fun readBuffered(b: CharArray?): Int {
return 0
}
}
internal object AllSpaceInputStream : InputStream() {
override fun read(): Int {
return SPACE_INT
}
override fun available(): Int {
return 1
}
override fun read(b: ByteArray, off: Int, len: Int): Int {
for (i in off until (off + len)) {
b[i] = SPACE_BYTE
}
return len
}
override fun close() {
}
}
internal object AllIgnoredOutputStream : OutputStream() {
override fun close() {
}
override fun write(b: ByteArray, off: Int, len: Int) {
}
override fun write(b: ByteArray) {
}
override fun write(b: Int) {
}
override fun flush() {
}
}
@OptIn(ConsolePureExperimentalAPI::class)
internal val SystemOutputPrintStream by lazy {
if (ConsolePureSettings.setupAnsi) {
org.fusesource.jansi.AnsiConsole.systemInstall()
}
System.out
}
internal object AllEmptyLineReader : LineReader {
private fun <T> ignored(): T = error("Ignored")
override fun defaultKeyMaps(): MutableMap<String, KeyMap<Binding>> = ignored()
override fun printAbove(str: String?) {
SystemOutputPrintStream.println(str)
}
@OptIn(ConsolePureExperimentalAPI::class)
override fun readLine(): String =
if (ConsolePureSettings.noConsoleSafeReading) ""
else error("Unsupported Reading line when console front-end closed.")
override fun readLine(mask: Char?): String = readLine()
override fun readLine(prompt: String?): String = readLine()
override fun readLine(prompt: String?, mask: Char?): String = readLine()
override fun readLine(prompt: String?, mask: Char?, buffer: String?): String = readLine()
override fun readLine(prompt: String?, rightPrompt: String?, mask: Char?, buffer: String?): String = ""
override fun readLine(
prompt: String?,
rightPrompt: String?,
maskingCallback: MaskingCallback?,
buffer: String?
): String = ""
override fun printAbove(str: AttributedString?) {
str?.let { printAbove(it.toAnsi()) }
}
override fun isReading(): Boolean = false
override fun variable(name: String?, value: Any?) = this
override fun option(option: LineReader.Option?, value: Boolean) = this
override fun callWidget(name: String?) {}
override fun getVariables(): MutableMap<String, Any> = ignored()
override fun getVariable(name: String?): Any = ignored()
override fun setVariable(name: String?, value: Any?) {}
override fun isSet(option: LineReader.Option?): Boolean = ignored()
override fun setOpt(option: LineReader.Option?) {}
override fun unsetOpt(option: LineReader.Option?) {}
override fun getTerminal(): Terminal = NoConsole
override fun getWidgets(): MutableMap<String, Widget> = ignored()
override fun getBuiltinWidgets(): MutableMap<String, Widget> = ignored()
override fun getBuffer(): Buffer = ignored()
override fun getAppName(): String = "Mirai Console"
override fun runMacro(macro: String?) {}
override fun readMouseEvent(): MouseEvent = ignored()
override fun getHistory(): History = ignored()
override fun getParser(): Parser = ignored()
override fun getHighlighter(): Highlighter = ignored()
override fun getExpander(): Expander = ignored()
override fun getKeyMaps(): MutableMap<String, KeyMap<Binding>> = ignored()
override fun getKeyMap(): String = ignored()
override fun setKeyMap(name: String?): Boolean = ignored()
override fun getKeys(): KeyMap<Binding> = ignored()
override fun getParsedLine(): ParsedLine = ignored()
override fun getSearchTerm(): String = ignored()
override fun getRegionActive(): LineReader.RegionType = ignored()
override fun getRegionMark(): Int = ignored()
override fun addCommandsInBuffer(commands: MutableCollection<String>?) {}
override fun editAndAddInBuffer(file: File?) {}
override fun getLastBinding(): String = ignored()
override fun getTailTip(): String = ignored()
override fun setTailTip(tailTip: String?) {}
override fun setAutosuggestion(type: LineReader.SuggestionType?) {}
override fun getAutosuggestion(): LineReader.SuggestionType = ignored()
}
internal object NoConsole : AbstractTerminal(
"No Console", "No Console"
) {
override fun reader(): NonBlockingReader = NoNonBlockingReader
private val AllIgnoredPrintWriter = object : PrintWriter(AllIgnoredOutputStream) {
override fun close() {
}
override fun flush() {
}
}
// We don't need it. Mirai-Console using LineReader to print messages.
override fun writer(): PrintWriter = AllIgnoredPrintWriter
override fun input(): InputStream = AllSpaceInputStream
override fun output(): OutputStream = AllIgnoredOutputStream
private val attributes0 = Attributes()
override fun getAttributes(): Attributes {
return Attributes(attributes0)
}
override fun setAttributes(attr: Attributes?) {
attr?.let { attributes0.copy(it) }
}
private val size0 = Size(189, 53)
override fun getSize(): Size {
return Size().also { it.copy(size0) }
}
override fun setSize(size: Size?) {
size?.let { size0.copy(it) }
}
}