From b87a5390b9a71917f8d7c503863574dee37960d8 Mon Sep 17 00:00:00 2001 From: Him188 Date: Tue, 1 Dec 2020 09:07:11 +0800 Subject: [PATCH] Review AnsiMessageBuilder --- .../src/util/AnsiMessageBuilder.kt | 208 ++++++++++-------- 1 file changed, 115 insertions(+), 93 deletions(-) diff --git a/backend/mirai-console/src/util/AnsiMessageBuilder.kt b/backend/mirai-console/src/util/AnsiMessageBuilder.kt index 0215a5bd2..b36baeb77 100644 --- a/backend/mirai-console/src/util/AnsiMessageBuilder.kt +++ b/backend/mirai-console/src/util/AnsiMessageBuilder.kt @@ -13,11 +13,23 @@ package net.mamoe.mirai.console.util import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge +import net.mamoe.mirai.console.util.AnsiMessageBuilder.Companion.asAnsiMessageBuilder import net.mamoe.mirai.console.util.AnsiMessageBuilder.Companion.dropAnsi +import net.mamoe.mirai.contact.Contact +import net.mamoe.mirai.message.MessageReceipt +import java.io.Serializable +/** + * @see buildAnsiMessage + * @see sendAnsiMessage + * @see AnsiMessageBuilder + * @see AnsiMessageBuilder.create + * @see asAnsiMessageBuilder + * @since 1.1 + */ public open class AnsiMessageBuilder public constructor( - public val delegate: StringBuilder -) : Appendable { + public val delegate: StringBuilder, +) : Appendable, Serializable, Comparable { override fun toString(): String = delegate.toString() /** @@ -29,7 +41,7 @@ public open class AnsiMessageBuilder public constructor( * * @param code Ansi 操作码 * - * @see from + * @see asAnsiMessageBuilder * @see create */ public open fun ansi(code: String): AnsiMessageBuilder = append(code) @@ -50,69 +62,7 @@ public open class AnsiMessageBuilder public constructor( public open fun lightPurple(): AnsiMessageBuilder = append(Color.LIGHT_PURPLE) public open fun lightCyan(): AnsiMessageBuilder = append(Color.LIGHT_CYAN) - internal object Color { - const val RESET = "\u001b[0m" - const val WHITE = "\u001b[30m" - const val RED = "\u001b[31m" - const val EMERALD_GREEN = "\u001b[32m" - const val GOLD = "\u001b[33m" - const val BLUE = "\u001b[34m" - const val PURPLE = "\u001b[35m" - const val GREEN = "\u001b[36m" - const val GRAY = "\u001b[90m" - const val LIGHT_RED = "\u001b[91m" - const val LIGHT_GREEN = "\u001b[92m" - const val LIGHT_YELLOW = "\u001b[93m" - const val LIGHT_BLUE = "\u001b[94m" - const val LIGHT_PURPLE = "\u001b[95m" - const val LIGHT_CYAN = "\u001b[96m" - } - - internal class NoAnsiMessageBuilder(builder: StringBuilder) : AnsiMessageBuilder(builder) { - override fun reset(): AnsiMessageBuilder = this - override fun white(): AnsiMessageBuilder = this - override fun red(): AnsiMessageBuilder = this - override fun emeraldGreen(): AnsiMessageBuilder = this - override fun gold(): AnsiMessageBuilder = this - override fun blue(): AnsiMessageBuilder = this - override fun purple(): AnsiMessageBuilder = this - override fun green(): AnsiMessageBuilder = this - override fun gray(): AnsiMessageBuilder = this - override fun lightRed(): AnsiMessageBuilder = this - override fun lightGreen(): AnsiMessageBuilder = this - override fun lightYellow(): AnsiMessageBuilder = this - override fun lightBlue(): AnsiMessageBuilder = this - override fun lightPurple(): AnsiMessageBuilder = this - override fun lightCyan(): AnsiMessageBuilder = this - override fun ansi(code: String): AnsiMessageBuilder = this - } - public companion object { - // CSI序列由ESC [、若干个(包括0个)“参数字节”、若干个“中间字节”,以及一个“最终字节”组成。各部分的字符范围如下: - // - // CSI序列在ESC [之后各个组成部分的字符范围[12]:5.4 - // 组成部分 字符范围 ASCII - // 参数字节 0x30–0x3F 0–9:;<=>? - // 中间字节 0x20–0x2F 空格、!"#$%&'()*+,-./ - // 最终字节 0x40–0x7E @A–Z[\]^_`a–z{|}~ - // - // @see https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#CSI%E5%BA%8F%E5%88%97 - @Suppress("RegExpRedundantEscape") - private val DROP_CSI_PATTERN = """\u001b\[([\u0030-\u003F])*?([\u0020-\u002F])*?[\u0040-\u007E]""".toRegex() - - // 序列具有不同的长度。所有序列都以ASCII字符ESC(27 / 十六进制 0x1B)开头, - // 第二个字节则是0x40–0x5F(ASCII @A–Z[\]^_)范围内的字符。[12]:5.3.a - // - // 标准规定,在8位环境中,这两个字节的序列可以合并为0x80-0x9F范围内的单个字节(详情请参阅C1控制字符集)。 - // 但是,在现代设备上,这些代码通常用于其他目的,例如UTF-8的一部分或CP-1252字符,因此并不使用这种合并的方式。 - // - // 除ESC之外的其他C0代码(通常是BEL,BS,CR,LF,FF,TAB,VT,SO和SI)在输出时也可能会产生与某些控制序列相似或相同的效果。 - // - // @see https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97 - // - // 注: 缺少详细资料, 只能认定 ansi 长度固定为二字节 (CSI除外) - private val DROP_ANSI_PATTERN = """\u001b[\u0040–\u005F]""".toRegex() - /** * 从 [String] 中剔除 ansi 控制符 */ @@ -122,30 +72,28 @@ public open class AnsiMessageBuilder public constructor( .replace(DROP_ANSI_PATTERN, "") /** - * 使用 [builder] 封装一个 [AnsiMessageBuilder] + * 使用 [this] 封装一个 [AnsiMessageBuilder] * * @param noAnsi 为 `true` 时忽略全部与 ansi 有关的方法的调用 */ @JvmStatic @JvmOverloads - public fun from( - builder: StringBuilder, - noAnsi: Boolean = false - ): AnsiMessageBuilder = if (noAnsi) { - NoAnsiMessageBuilder(builder) - } else AnsiMessageBuilder(builder) + @JvmName("from") + public fun StringBuilder.asAnsiMessageBuilder(noAnsi: Boolean = false): AnsiMessageBuilder = + if (noAnsi) NoAnsiMessageBuilder(this) else AnsiMessageBuilder(this) /** * @param capacity [StringBuilder] 的初始化大小 * * @param noAnsi 为 `true` 时忽略全部与 ansi 有关的方法的调用 + * @see AnsiMessageBuilder */ @JvmStatic @JvmOverloads public fun create( capacity: Int = 16, - noAnsi: Boolean = false - ): AnsiMessageBuilder = from(StringBuilder(capacity), noAnsi) + noAnsi: Boolean = false, + ): AnsiMessageBuilder = StringBuilder(capacity).asAnsiMessageBuilder(noAnsi) /** * 判断 [sender] 是否支持带 ansi 控制符的正确显示 @@ -161,11 +109,12 @@ public open class AnsiMessageBuilder public constructor( * 往 [StringBuilder] 追加 ansi 控制符 */ public inline fun StringBuilder.appendAnsi( - action: AnsiMessageBuilder.() -> Unit - ): AnsiMessageBuilder = from(this).apply(action) - + action: AnsiMessageBuilder.() -> Unit, + ): AnsiMessageBuilder = this.asAnsiMessageBuilder().apply(action) } + override fun compareTo(other: AnsiMessageBuilder): Int = this.delegate.compareTo(other.delegate) + ///////////////////////////////////////////////////////////////////////////////// override fun append(c: Char): AnsiMessageBuilder = apply { delegate.append(c) } override fun append(csq: CharSequence?): AnsiMessageBuilder = apply { delegate.append(csq) } @@ -184,31 +133,38 @@ public open class AnsiMessageBuilder public constructor( /** * @param capacity [StringBuilder] 初始化大小 + * @see AnsiMessageBuilder.create + * @since 1.1 */ +@JvmSynthetic public fun AnsiMessageBuilder(capacity: Int = 16): AnsiMessageBuilder = AnsiMessageBuilder(StringBuilder(capacity)) /** - * 构建一条 ansi 信息 + * 构建一条 ANSI 信息 * - * @see [AnsiMessageBuilder] + * @see AnsiMessageBuilder + * @since 1.1 */ +@JvmSynthetic public inline fun buildAnsiMessage( capacity: Int = 16, - action: AnsiMessageBuilder.() -> Unit + action: AnsiMessageBuilder.() -> Unit, ): String = AnsiMessageBuilder.create(capacity, false).apply(action).toString() // 不在 top-level 使用者会得到 Internal error: Couldn't inline sendAnsiMessage /** - * 向 [CommandSender] 发送一条带有 ansi 控制符的信息 + * 向 [CommandSender] 发送一条带有 ANSI 控制符的信息 * - * @see [AnsiMessageBuilder] + * @see AnsiMessageBuilder + * @since 1.1 */ +@JvmSynthetic public suspend inline fun CommandSender.sendAnsiMessage( capacity: Int = 16, - builder: AnsiMessageBuilder.() -> Unit -) { - sendMessage( + builder: AnsiMessageBuilder.() -> Unit, +): MessageReceipt? { + return sendMessage( AnsiMessageBuilder.create(capacity, noAnsi = !AnsiMessageBuilder.isAnsiSupported(this)) .apply(builder) .toString() @@ -216,15 +172,81 @@ public suspend inline fun CommandSender.sendAnsiMessage( } /** - * 向 [CommandSender] 发送一条带有 ansi 控制符的信息 + * 向 [CommandSender] 发送一条带有 ANSI 控制符的信息 * - * @see [AnsiMessageBuilder.Companion.dropAnsi] + * @see AnsiMessageBuilder.Companion.dropAnsi + * @since 1.1 */ -public suspend inline fun CommandSender.sendAnsiMessage(message: String) { - sendMessage( - if (AnsiMessageBuilder.isAnsiSupported(this)) - message - else - message.dropAnsi() +@JvmSynthetic +public suspend inline fun CommandSender.sendAnsiMessage(message: String): MessageReceipt? { + return sendMessage( + if (AnsiMessageBuilder.isAnsiSupported(this)) message + else message.dropAnsi() ) } + + +// internals + + +// CSI序列由ESC [、若干个(包括0个)“参数字节”、若干个“中间字节”,以及一个“最终字节”组成。各部分的字符范围如下: +// +// CSI序列在ESC [之后各个组成部分的字符范围[12]:5.4 +// 组成部分 字符范围 ASCII +// 参数字节 0x30–0x3F 0–9:;<=>? +// 中间字节 0x20–0x2F 空格、!"#$%&'()*+,-./ +// 最终字节 0x40–0x7E @A–Z[\]^_`a–z{|}~ +// +// @see https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#CSI%E5%BA%8F%E5%88%97 +@Suppress("RegExpRedundantEscape") +private val DROP_CSI_PATTERN = """\u001b\[([\u0030-\u003F])*?([\u0020-\u002F])*?[\u0040-\u007E]""".toRegex() + +// 序列具有不同的长度。所有序列都以ASCII字符ESC(27 / 十六进制 0x1B)开头, +// 第二个字节则是0x40–0x5F(ASCII @A–Z[\]^_)范围内的字符。[12]:5.3.a +// +// 标准规定,在8位环境中,这两个字节的序列可以合并为0x80-0x9F范围内的单个字节(详情请参阅C1控制字符集)。 +// 但是,在现代设备上,这些代码通常用于其他目的,例如UTF-8的一部分或CP-1252字符,因此并不使用这种合并的方式。 +// +// 除ESC之外的其他C0代码(通常是BEL,BS,CR,LF,FF,TAB,VT,SO和SI)在输出时也可能会产生与某些控制序列相似或相同的效果。 +// +// @see https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97 +// +// 注: 缺少详细资料, 只能认定 ansi 长度固定为二字节 (CSI除外) +private val DROP_ANSI_PATTERN = """\u001b[\u0040–\u005F]""".toRegex() + +private object Color { + const val RESET = "\u001b[0m" + const val WHITE = "\u001b[30m" + const val RED = "\u001b[31m" + const val EMERALD_GREEN = "\u001b[32m" + const val GOLD = "\u001b[33m" + const val BLUE = "\u001b[34m" + const val PURPLE = "\u001b[35m" + const val GREEN = "\u001b[36m" + const val GRAY = "\u001b[90m" + const val LIGHT_RED = "\u001b[91m" + const val LIGHT_GREEN = "\u001b[92m" + const val LIGHT_YELLOW = "\u001b[93m" + const val LIGHT_BLUE = "\u001b[94m" + const val LIGHT_PURPLE = "\u001b[95m" + const val LIGHT_CYAN = "\u001b[96m" +} + +private class NoAnsiMessageBuilder(builder: StringBuilder) : AnsiMessageBuilder(builder) { + override fun reset(): AnsiMessageBuilder = this + override fun white(): AnsiMessageBuilder = this + override fun red(): AnsiMessageBuilder = this + override fun emeraldGreen(): AnsiMessageBuilder = this + override fun gold(): AnsiMessageBuilder = this + override fun blue(): AnsiMessageBuilder = this + override fun purple(): AnsiMessageBuilder = this + override fun green(): AnsiMessageBuilder = this + override fun gray(): AnsiMessageBuilder = this + override fun lightRed(): AnsiMessageBuilder = this + override fun lightGreen(): AnsiMessageBuilder = this + override fun lightYellow(): AnsiMessageBuilder = this + override fun lightBlue(): AnsiMessageBuilder = this + override fun lightPurple(): AnsiMessageBuilder = this + override fun lightCyan(): AnsiMessageBuilder = this + override fun ansi(code: String): AnsiMessageBuilder = this +}