diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5cabee077..221268a2a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -38,6 +38,9 @@ slf4jtest = "com.github.valfirst:slf4j-test:2.9.0" # Specific versions are neede # text-serializer-gson gson = "com.google.code.gson:gson:2.8.0" +# text-serializer-ansi +ansi = "net.kyori:ansi:1.0.0" + # tests junit-api = { module = "org.junit.jupiter:junit-jupiter-api" } junit-bom = { module = "org.junit:junit-bom", version.ref = "junit" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 92a70970c..84d8d118b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -44,7 +44,8 @@ sequenceOf( "text-serializer-gson", "text-serializer-gson-legacy-impl", "text-serializer-legacy", - "text-serializer-plain" + "text-serializer-plain", + "text-serializer-ansi", ).forEach { include("adventure-$it") project(":adventure-$it").projectDir = file(it) diff --git a/text-serializer-ansi/build.gradle.kts b/text-serializer-ansi/build.gradle.kts new file mode 100644 index 000000000..dc78b8b6f --- /dev/null +++ b/text-serializer-ansi/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + id("adventure.common-conventions") +} + +dependencies { + api(projects.adventureApi) + api(libs.ansi) +} + +applyJarMetadata("net.kyori.adventure.text.serializer.ansi") diff --git a/text-serializer-ansi/src/main/java/net/kyori/adventure/text/serializer/ansi/ANSIComponentSerializer.java b/text-serializer-ansi/src/main/java/net/kyori/adventure/text/serializer/ansi/ANSIComponentSerializer.java new file mode 100644 index 000000000..1d5cd84eb --- /dev/null +++ b/text-serializer-ansi/src/main/java/net/kyori/adventure/text/serializer/ansi/ANSIComponentSerializer.java @@ -0,0 +1,132 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2023 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.text.serializer.ansi; + +import java.util.function.Consumer; +import net.kyori.adventure.builder.AbstractBuilder; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.flattener.ComponentFlattener; +import net.kyori.adventure.text.serializer.ComponentEncoder; +import net.kyori.adventure.util.PlatformAPI; +import net.kyori.ansi.ColorLevel; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * A serializer which emits ANSI escape sequences. + * + *

Note that this serializer does NOT support deserialization.

+ * + * @since 4.14.0 + */ +public interface ANSIComponentSerializer extends ComponentEncoder { + /** + * Gets a component serializer for serialization to a string using ANSI escape codes. + * + *

Note that this serializer does NOT support deserialization.

+ * + * @return a component serializer for serialization with ANSI escape sequences. + * @since 4.14.0 + */ + static @NotNull ANSIComponentSerializer ansi() { + return ANSIComponentSerializerImpl.Instances.INSTANCE; + } + + /** + * Create a new builder. + * + * @return a new ANSI serializer builder + * @since 4.14.0 + */ + static ANSIComponentSerializer.@NotNull Builder builder() { + return new ANSIComponentSerializerImpl.BuilderImpl(); + } + + /** + * A builder for the ANSI component serializer. + * + * @since 4.14.0 + */ + interface Builder extends AbstractBuilder { + /** + * Sets the default color level used when serializing. + * + *

By default, this serializer will use {@link ColorLevel#compute()} to try to detect the color level of the terminal being used.

+ * + * @param colorLevel the color level + * @return this builder + * @see ColorLevel + * @since 4.14.0 + */ + @NotNull Builder colorLevel(final @NotNull ColorLevel colorLevel); + + /** + * Sets the component flattener instance to use when traversing the component for serialization. + * + *

By default, this serializer will use {@link ComponentFlattener#basic()}.

+ * + * @param componentFlattener the flattener instance. + * @return this builder + * @since 4.14.0 + */ + @NotNull Builder flattener(final @NotNull ComponentFlattener componentFlattener); + + /** + * Builds the serializer. + * + * @return the built serializer + */ + @Override + @NotNull ANSIComponentSerializer build(); + } + + /** + * A {@link ANSIComponentSerializer} service provider. + * + * @since 4.14.0 + */ + @ApiStatus.Internal + @PlatformAPI + interface Provider { + /** + * Provides a {@link ANSIComponentSerializer}. + * + * @return a {@link ANSIComponentSerializer} + * @since 4.8.0 + */ + @ApiStatus.Internal + @PlatformAPI + @NotNull ANSIComponentSerializer ansi(); + + /** + * Completes the building process of {@link Builder}. + * + * @return a {@link Consumer} + * @since 4.8.0 + */ + @ApiStatus.Internal + @PlatformAPI + @NotNull Consumer builder(); + } +} diff --git a/text-serializer-ansi/src/main/java/net/kyori/adventure/text/serializer/ansi/ANSIComponentSerializerImpl.java b/text-serializer-ansi/src/main/java/net/kyori/adventure/text/serializer/ansi/ANSIComponentSerializerImpl.java new file mode 100644 index 000000000..cc3377783 --- /dev/null +++ b/text-serializer-ansi/src/main/java/net/kyori/adventure/text/serializer/ansi/ANSIComponentSerializerImpl.java @@ -0,0 +1,175 @@ +/* + * This file is part of adventure, licensed under the MIT License. + * + * Copyright (c) 2017-2023 KyoriPowered + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package net.kyori.adventure.text.serializer.ansi; + +import java.util.Optional; +import java.util.function.Consumer; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.flattener.ComponentFlattener; +import net.kyori.adventure.text.flattener.FlattenerListener; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.util.Services; +import net.kyori.ansi.ANSIComponentRenderer; +import net.kyori.ansi.ColorLevel; +import net.kyori.ansi.StyleOps; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Range; + +final class ANSIComponentSerializerImpl implements ANSIComponentSerializer { + private static final Optional SERVICE = Services.service(Provider.class); + + static final Consumer BUILDER = SERVICE + .map(Provider::builder) + .orElseGet(() -> builder -> { + // NOOP + }); + + final ColorLevel colorLevel; + final ComponentFlattener flattener; + + static final class Instances { + static final ANSIComponentSerializer INSTANCE = SERVICE + .map(Provider::ansi) + .orElseGet(() -> new ANSIComponentSerializerImpl(ColorLevel.compute(), ComponentFlattener.basic())); + } + + ANSIComponentSerializerImpl(final ColorLevel colorLevel, final ComponentFlattener flattener) { + this.colorLevel = colorLevel; + this.flattener = flattener; + } + + @Override + public @NotNull String serialize(final @NotNull Component component) { + final ANSIComponentRenderer.ToString