From 40bd24ecb2668273ffdc216a490785e39d1815a0 Mon Sep 17 00:00:00 2001 From: vae <66442608+sylviameows@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:21:29 -0500 Subject: [PATCH 01/11] draft expression highlighter --- build.gradle | 4 + .../dev/dfonline/codeclient/CodeClient.java | 2 + .../highlighter/ExpressionHighlighter.java | 143 ++++++++++++++++++ .../highlighter/MiniMessageHighlighter.java | 100 ++++++++++++ .../hypercube/HypercubeCommands.java | 86 +++++++++++ .../hypercube/HypercubeMinimessage.java | 38 +++++ .../screen/chat/MChatInputSuggester.java | 52 +++++++ .../mixin/{ => screen/chat}/MChatScreen.java | 2 +- src/main/resources/CodeClient.mixins.json | 3 +- 9 files changed, 428 insertions(+), 2 deletions(-) create mode 100644 src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java create mode 100644 src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java create mode 100644 src/main/java/dev/dfonline/codeclient/hypercube/HypercubeCommands.java create mode 100644 src/main/java/dev/dfonline/codeclient/hypercube/HypercubeMinimessage.java create mode 100644 src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java rename src/main/java/dev/dfonline/codeclient/mixin/{ => screen/chat}/MChatScreen.java (93%) diff --git a/build.gradle b/build.gradle index 53cd8d3a..4c26d79f 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,10 @@ dependencies { modImplementation "dev.isxander.yacl:yet-another-config-lib-fabric:${project.yacl_version}" modImplementation "com.terraformersmc:modmenu:${project.modmenu_version}" + // adventure api for minimessage parsing (see ExpressionHighlighter) +// implementation "net.kyori:adventure-api:4.16.0" + modImplementation include("net.kyori:adventure-platform-fabric:5.12.0") + implementation include("org.java-websocket:Java-WebSocket:1.5.3") } diff --git a/src/main/java/dev/dfonline/codeclient/CodeClient.java b/src/main/java/dev/dfonline/codeclient/CodeClient.java index 348df883..3b5c5500 100644 --- a/src/main/java/dev/dfonline/codeclient/CodeClient.java +++ b/src/main/java/dev/dfonline/codeclient/CodeClient.java @@ -9,6 +9,7 @@ import dev.dfonline.codeclient.config.KeyBinds; import dev.dfonline.codeclient.dev.*; import dev.dfonline.codeclient.dev.debug.Debug; +import dev.dfonline.codeclient.dev.highlighter.ExpressionHighlighter; import dev.dfonline.codeclient.dev.menu.InsertOverlayFeature; import dev.dfonline.codeclient.dev.menu.RecentValues; import dev.dfonline.codeclient.dev.menu.SlotGhostManager; @@ -178,6 +179,7 @@ private static void loadFeatures() { feat(new ChatAutoEdit()); feat(new CPUDisplay()); feat(new MessageHiding()); + feat(new ExpressionHighlighter()); } /** diff --git a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java new file mode 100644 index 00000000..760d2ae3 --- /dev/null +++ b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java @@ -0,0 +1,143 @@ +package dev.dfonline.codeclient.dev.highlighter; + +import dev.dfonline.codeclient.CodeClient; +import dev.dfonline.codeclient.Feature; +import dev.dfonline.codeclient.hypercube.HypercubeCommands; +import dev.dfonline.codeclient.hypercube.HypercubeMinimessage; +import dev.dfonline.codeclient.location.Dev; +import dev.dfonline.codeclient.location.Location; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.minimessage.Context; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.ParsingException; +import net.kyori.adventure.text.minimessage.tag.Inserting; +import net.kyori.adventure.text.minimessage.tag.Tag; +import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.EditBox; +import net.minecraft.text.OrderedText; +import net.minecraft.text.TextColor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.regex.Pattern; + +import static net.kyori.adventure.platform.fabric.FabricAudiences.nonWrappingSerializer; + +public class ExpressionHighlighter extends Feature { + private final Set CODES = Set.of( + "default", + "selected", + "uuid", + "var", + "math", + "damager", + "killer", + "shooter", + "victim", + "projectile", + "random", + "round", + "index", + "entry" + ); + + private final List COLORS = List.of( + TextColor.fromRgb(0xffd600) + ); + + + @Override + public boolean enabled() { + return CodeClient.location instanceof Dev; // todo: config + } + + public record HighlightedExpression(OrderedText text, @Nullable OrderedText preview) { + + } + + private String cachedInput = ""; + private boolean cachedParseMinimessage = true; + private HighlightedExpression cachedHighlight = new HighlightedExpression(OrderedText.empty(), null); + + private final MiniMessage formatter = HypercubeMinimessage.FORMATTER; + private final MiniMessageHighlighter highlighter = new MiniMessageHighlighter(); + + // creates an expression based on text input + public HighlightedExpression format(String input) { + OrderedText text = convert(highlighter.highlight(input)); + OrderedText preview = convert(formatter.deserialize(input)); + + return new HighlightedExpression(text, preview); + } + + // draws the preview above the chat bar + public void draw(DrawContext context, int mouseX, int mouseY, OrderedText input /* todo take out of mixin */) { + TextRenderer renderer = CodeClient.MC.textRenderer; + + var screen = CodeClient.MC.currentScreen; + if (screen == null) return; + + int y = screen.height - 25; + context.drawTextWithShadow(renderer, input, 4, y,0xffffff); + } + + private OrderedText convert(Component component) { + return nonWrappingSerializer().serialize(component).asOrderedText(); + } + + + private class EmptyInsertion implements Inserting { + @Override + public @NotNull Component value() { return Component.empty(); } + } + + private enum CommandType { + NUMBER(HypercubeCommands.NUMBER, true, false), + STRING(HypercubeCommands.STRING, true, false), + TEXT(HypercubeCommands.TEXT, true), + VARIABLE(HypercubeCommands.VARIABLE, true, false), + ITEM_NAME(HypercubeCommands.ITEM_NAME), + ITEM_LORE_ADD(HypercubeCommands.ITEM_LORE_ADD), + ITEM_LORE_SET(HypercubeCommands.ITEM_LORE_SET, 1), + ITEM_LORE_INSERT(HypercubeCommands.ITEM_LORE_INSERT, 1), + PLOT_NAME(HypercubeCommands.PLOT_NAME), + RELORE(HypercubeCommands.RELORE); + + final Pattern regex; + final int argumentIndex; + final boolean hasCount; + final boolean parseMinimessage; + + CommandType(Pattern regex) { + this(regex, 0); + } + + CommandType(Pattern regex, int argumentIndex) { + this(regex, argumentIndex, false, true); + } + + CommandType(Pattern regex, boolean hasCount) { + this(regex, 0, hasCount, true); + } + + CommandType(Pattern regex, boolean hasCount, boolean parseMinimessage) { + this(regex, 0, hasCount, parseMinimessage); + } + + CommandType(Pattern regex, int argumentIndex, boolean hasCount, boolean parseMinimessage) { + this.regex = regex; + this.argumentIndex = argumentIndex; + this.hasCount = hasCount; + this.parseMinimessage = parseMinimessage; + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java b/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java new file mode 100644 index 00000000..07a9b7d3 --- /dev/null +++ b/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java @@ -0,0 +1,100 @@ +package dev.dfonline.codeclient.dev.highlighter; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.minimessage.Context; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.ParsingException; +import net.kyori.adventure.text.minimessage.internal.parser.node.TagNode; +import net.kyori.adventure.text.minimessage.internal.parser.node.TagPart; +import net.kyori.adventure.text.minimessage.internal.parser.node.ValueNode; +import net.kyori.adventure.text.minimessage.tag.Tag; +import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; +import net.kyori.adventure.text.minimessage.tree.Node; +import net.minecraft.client.gui.widget.TextFieldWidget; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.function.Consumer; + +/** + * Parses MiniMessage input, but leaves the tags in the message for formatting in the edit box. + */ +public class MiniMessageHighlighter { + public MiniMessage HIGHLIGHTER = MiniMessage.builder().tags(new ShownTagResolver()).build(); + + + public Component highlight(String input) { + Node.Root root = HIGHLIGHTER.deserializeToTree(input); + + System.out.println(root.input()); + + int index = 0; + + handle(root); + + + + return Component.text(input).color(NamedTextColor.AQUA); + } + + + + @SuppressWarnings("UnstableApiUsage") + private String handle(Node node) { + String style; + if (node instanceof TagNode tagNode) { + System.out.println(tagNode.tag() + " | " + tagNode.name()); + for (TagPart part : tagNode.parts()) { + System.out.println(part.token() + " : " + part.value()); + } + + style = "gray"; + } + if (node instanceof ValueNode valueNode) { + System.out.println(valueNode.value() + " | " + valueNode.token()); + style = "gray"; + } + + for (Node child : node.children()) { + System.out.println("- new child"); + handle(child); + System.out.println("- end of child"); + } + + return null; + } + + + private class ShownTagResolver implements TagResolver { + private final TagResolver standard = TagResolver.resolver( + StandardTags.color(), + StandardTags.decorations(TextDecoration.BOLD), + StandardTags.decorations(TextDecoration.ITALIC), + StandardTags.decorations(TextDecoration.UNDERLINED), + StandardTags.decorations(TextDecoration.STRIKETHROUGH), + StandardTags.reset(), + StandardTags.gradient(), + StandardTags.rainbow() + ); + + @Override + public @Nullable Tag resolve(@NotNull String name, @NotNull ArgumentQueue arguments, @NotNull Context ctx) throws ParsingException { + Tag tag = standard.resolve(name, arguments, ctx); + if (tag != null) { + return tag; + } + return null; + } + + @Override + public boolean has(@NotNull String name) { + return standard.has(name); + } + } + +} diff --git a/src/main/java/dev/dfonline/codeclient/hypercube/HypercubeCommands.java b/src/main/java/dev/dfonline/codeclient/hypercube/HypercubeCommands.java new file mode 100644 index 00000000..f10ee42a --- /dev/null +++ b/src/main/java/dev/dfonline/codeclient/hypercube/HypercubeCommands.java @@ -0,0 +1,86 @@ +package dev.dfonline.codeclient.hypercube; + +import java.util.regex.Pattern; + +/** + * Hypercube command pattern matchers which include their aliases as well as their trailing space. + */ +public class HypercubeCommands { + private static final Pattern ITEM = argument("item", "i"); + private static final Pattern LORE = argument("lore", "l"); + + public static final Pattern ITEM_NAME = multi( + command(ITEM, argument("name", "n")), + command(argument("rename")) + ); + + public static final Pattern ITEM_LORE_ADD = multi( + command(ITEM, LORE, argument("add", "a")), + command(LORE, argument("add", "a")), + command(argument("addlore", "ila")) + ); + public static final Pattern ITEM_LORE_SET = multi( + command(ITEM, LORE, argument("set", "s")), + command(LORE, argument("set", "s")), + command(argument("setlore", "ils")) + ); + public static final Pattern ITEM_LORE_INSERT = multi( + command(ITEM, LORE, argument("insert", "i")), + command(LORE, argument("insert", "i")) + ); + + public static final Pattern PLOT_NAME = command(argument("plot", "p"), argument("name")); + + public static final Pattern NUMBER = command("number", "num"); + public static final Pattern STRING = command("string", "str"); + public static final Pattern TEXT = command("styledtext", "text", "stxt", "txt"); + public static final Pattern VARIABLE = command("variable", "var"); + + public static final Pattern RELORE = command("relore"); + + + // utility functions: + private static Pattern argument(String... strings) { + var builder = new StringBuilder(); + + boolean separator = false; + for (String string : strings) { + if (separator) builder.append("|"); + else separator = true; + builder.append("(").append(string).append(")"); + } + + return Pattern.compile(builder.toString(), Pattern.CASE_INSENSITIVE); + } + + private static Pattern command(Pattern... arguments) { + var builder = new StringBuilder(); + + boolean separator = false; + for (Pattern argument : arguments) { + if (separator) builder.append(" "); + else separator = true; + builder.append("(").append(argument.pattern()).append(")"); + } + builder.append(" "); + + return Pattern.compile(builder.toString(), Pattern.CASE_INSENSITIVE); + } + + private static Pattern command(String... strings) { + return Pattern.compile(argument(strings).pattern()+" "); + } + + private static Pattern multi(Pattern... commands) { + var builder = new StringBuilder(); + + boolean separator = false; + for (Pattern command : commands) { + if (separator) builder.append("|"); + else separator = true; + builder.append("(").append(command.pattern()).append(")"); + } + + return Pattern.compile(builder.toString(), Pattern.CASE_INSENSITIVE); + } +} diff --git a/src/main/java/dev/dfonline/codeclient/hypercube/HypercubeMinimessage.java b/src/main/java/dev/dfonline/codeclient/hypercube/HypercubeMinimessage.java new file mode 100644 index 00000000..e139b1c9 --- /dev/null +++ b/src/main/java/dev/dfonline/codeclient/hypercube/HypercubeMinimessage.java @@ -0,0 +1,38 @@ +package dev.dfonline.codeclient.hypercube; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.Tag; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; +import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; + +public class HypercubeMinimessage { + public static final MiniMessage FORMATTER = MiniMessage.builder().tags(Tags.ALL).build(); + + private static class Tags { + private static final int MAX_REPETITION_COUNT = 32; + + // todo: remove when adventure is >5.15.0 (minecraft 1.21) + private static final TagResolver standard = StandardTags.defaults(); + + private static final TagResolver space = repetitionTagResolver("space", " "); + private static final TagResolver newline = repetitionTagResolver("newline", "\n"); + + static final TagResolver ALL = TagResolver.resolver(standard, space, newline); + + private static TagResolver repetitionTagResolver(String name, String literal) { + return TagResolver.resolver(name, (arguments, context) -> { + int count; + if (arguments.hasNext()) { + count = arguments.pop().asInt().orElseThrow(() -> { + context.newException("Count must be a number"); + return null; + }); + } else count = 1; + + String repeated = literal.repeat(Math.min(count, MAX_REPETITION_COUNT)); + return Tag.selfClosingInserting(Component.text(repeated)); + }); + } + } +} diff --git a/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java b/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java new file mode 100644 index 00000000..0e2bf0c4 --- /dev/null +++ b/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java @@ -0,0 +1,52 @@ +package dev.dfonline.codeclient.mixin.screen.chat; + +import dev.dfonline.codeclient.CodeClient; +import dev.dfonline.codeclient.dev.highlighter.ExpressionHighlighter; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.ChatInputSuggestor; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.text.OrderedText; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(ChatInputSuggestor.class) +public class MChatInputSuggester { + + @Shadow @Final + TextFieldWidget textField; + + @Unique + private OrderedText preview = null; + + @Inject(method = "provideRenderText", at = @At("RETURN"), cancellable = true) + private void provideRenderText(String original, int firstCharacterIndex, CallbackInfoReturnable cir) { + OrderedText originalValue = cir.getReturnValue(); + + CodeClient.getFeature(ExpressionHighlighter.class).ifPresent((action) -> { + if (!action.enabled()) return; + + ExpressionHighlighter.HighlightedExpression expression = action.format(textField.getText()); + + preview = expression.preview(); + cir.setReturnValue(expression.text()); // todo: highlighting in the edit box + }); + } + + @Inject(method = "render", at = @At("HEAD")) + private void renderPreview(DrawContext context, int mouseX, int mouseY, CallbackInfo ci) { + CodeClient.getFeature(ExpressionHighlighter.class).ifPresent((action) -> { + if (!action.enabled() || preview == null) return; + + action.draw(context, mouseX, mouseY, preview); + }); + } + + + +} diff --git a/src/main/java/dev/dfonline/codeclient/mixin/MChatScreen.java b/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatScreen.java similarity index 93% rename from src/main/java/dev/dfonline/codeclient/mixin/MChatScreen.java rename to src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatScreen.java index d290cf43..fe08d222 100644 --- a/src/main/java/dev/dfonline/codeclient/mixin/MChatScreen.java +++ b/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatScreen.java @@ -1,4 +1,4 @@ -package dev.dfonline.codeclient.mixin; +package dev.dfonline.codeclient.mixin.screen.chat; import dev.dfonline.codeclient.CodeClient; import dev.dfonline.codeclient.dev.ChatAutoEdit; diff --git a/src/main/resources/CodeClient.mixins.json b/src/main/resources/CodeClient.mixins.json index a9b1e6f5..6f29d233 100644 --- a/src/main/resources/CodeClient.mixins.json +++ b/src/main/resources/CodeClient.mixins.json @@ -4,7 +4,8 @@ "package": "dev.dfonline.codeclient.mixin", "compatibilityLevel": "JAVA_17", "client": [ - "MChatScreen", + "screen.chat.MChatScreen", + "screen.chat.MChatInputSuggester", "MKeyboard", "MMouse", "entity.MEntity", From 1df13baac45df14d67e71295f0be2e550909081b Mon Sep 17 00:00:00 2001 From: vae <66442608+sylviameows@users.noreply.github.com> Date: Tue, 8 Oct 2024 18:37:12 -0500 Subject: [PATCH 02/11] edit box and formatting --- .../highlighter/ExpressionHighlighter.java | 202 ++++++++++++++++-- .../highlighter/MiniMessageHighlighter.java | 6 +- .../screen/chat/MChatInputSuggester.java | 15 +- 3 files changed, 191 insertions(+), 32 deletions(-) diff --git a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java index 760d2ae3..423ae5d0 100644 --- a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java +++ b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java @@ -4,30 +4,28 @@ import dev.dfonline.codeclient.Feature; import dev.dfonline.codeclient.hypercube.HypercubeCommands; import dev.dfonline.codeclient.hypercube.HypercubeMinimessage; +import dev.dfonline.codeclient.hypercube.item.VarItem; +import dev.dfonline.codeclient.hypercube.item.VarItems; import dev.dfonline.codeclient.location.Dev; -import dev.dfonline.codeclient.location.Location; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.TextDecoration; -import net.kyori.adventure.text.minimessage.Context; +import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.text.minimessage.ParsingException; -import net.kyori.adventure.text.minimessage.tag.Inserting; -import net.kyori.adventure.text.minimessage.tag.Tag; -import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue; -import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; -import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; -import net.minecraft.client.MinecraftClient; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.EditBox; +import net.minecraft.predicate.NumberRange; +import net.minecraft.text.CharacterVisitor; import net.minecraft.text.OrderedText; +import net.minecraft.text.Style; import net.minecraft.text.TextColor; -import org.jetbrains.annotations.NotNull; +import org.apache.commons.lang3.IntegerRange; import org.jetbrains.annotations.Nullable; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; import java.util.regex.Pattern; import static net.kyori.adventure.platform.fabric.FabricAudiences.nonWrappingSerializer; @@ -65,18 +63,182 @@ public record HighlightedExpression(OrderedText text, @Nullable OrderedText prev } private String cachedInput = ""; - private boolean cachedParseMinimessage = true; + private int cachedPosition = 0; private HighlightedExpression cachedHighlight = new HighlightedExpression(OrderedText.empty(), null); private final MiniMessage formatter = HypercubeMinimessage.FORMATTER; private final MiniMessageHighlighter highlighter = new MiniMessageHighlighter(); // creates an expression based on text input - public HighlightedExpression format(String input) { - OrderedText text = convert(highlighter.highlight(input)); - OrderedText preview = convert(formatter.deserialize(input)); + public HighlightedExpression format(String input, String partial, IntegerRange range) { + if (Objects.equals(input, cachedInput) && cachedPosition == range.getMinimum()) { + return cachedHighlight; + } + + cachedPosition = range.getMinimum(); + cachedInput = input; + + // parse commands + if (input.startsWith("/")) { + for (CommandType command : CommandType.values()) { + var matcher = command.regex.matcher(input); + if (!matcher.find(1)) { + continue; + } + cachedHighlight = formatCommand(input, command, matcher.end(), range); + return cachedHighlight; + } + cachedHighlight = null; + return null; + } + + var player = CodeClient.MC.player; + if (player == null) return null; + var item = player.getMainHandStack(); + try { + VarItem varItem = VarItems.parse(item); + Component text; + + switch (varItem.getId()) { + case "num", "var", "txt" -> text = highlighter.highlight(input, false); + case "comp" -> text = highlighter.highlight(input, true); + default -> { + return null; + } + } + + Component preview = formatter.deserialize(input); + + cachedHighlight = new HighlightedExpression(subSequence(convert(text), range), convert(preview)); + return cachedHighlight; + } catch (Exception ignored) { + cachedHighlight = null; + return null; + } + } + + private HighlightedExpression formatCommand(String input, CommandType command, int index, IntegerRange range) { + int start = index; + int end = input.length(); + + if (start > end) return null; + + + if (command.argumentIndex > 0) { + var pattern = Pattern.compile("^(\\S*\\s+){"+command.argumentIndex+"}"); + var matcher = pattern.matcher(input); + if (matcher.find(start)) { + start = matcher.end(); + } + if (start > end) return null; + } + + if (command.hasCount) { + var pattern = Pattern.compile("\\s\\d+$"); + var matcher = pattern.matcher(input); + if (matcher.find(start)) { + end = matcher.start(); + } + } + + // edit box + Component highlighted = highlighter.highlight( + input.substring(start, end), + command.parseMinimessage + ); + + Component combined = Component.text(input.substring(0, start)).color(NamedTextColor.GRAY).append(highlighted); + + if (end < input.length()) { + combined = combined.append(Component.text(input.substring(end)).color(NamedTextColor.LIGHT_PURPLE)); + } + + // preview text + Component formatted = formatter.deserialize(input.substring(start, end)); + +// return new HighlightedExpression(splitComponent(combined, cursor), convert(formatted)); + return new HighlightedExpression(subSequence(convert(combined), range), convert(formatted)); + } + + private OrderedText splitComponent(Component component, int cursor) { + String raw = PlainTextComponentSerializer.plainText().serialize(component); + + Pattern half = Pattern.compile("^.{"+(cursor-1)+"}"); + Matcher matcher = half.matcher(raw); + + if (!matcher.matches()) return OrderedText.concat(convert(component)); + + Pattern first = Pattern.compile("^"+matcher.group()); + Pattern last = Pattern.compile(matcher.replaceAll("")+"$"); + + Component firstComponent = component.replaceText(builder -> { + builder.replacement(""); + builder.match(last); + }); + Component secondComponent = component.replaceText(builder -> { + builder.replacement(""); + builder.match(first); + }); - return new HighlightedExpression(text, preview); + return OrderedText.concat(convert(firstComponent), convert(secondComponent)); + } + + /** takes a given substring of a component. + * @param start inclusive + * @param end exclusive + * @return a component containing what's between the start index and end index. + */ + private Component substr(Component component, int start, int end) { + String raw = PlainTextComponentSerializer.plainText().serialize(component); + String split = raw.substring(start, end); + + // this variable reveals what needs to be replaced in the component. + var replace = raw.split(split); + + if (split.equals(raw)) return component; + + return component.replaceText(builder -> { + for (String section : replace) { + builder.matchLiteral(section); + } + builder.replacement(""); + }); + } + + /** takes a given substring of a component. + * @param start inclusive + * @return a component containing what's after the start index + */ + private Component substr(Component component, int start) { + String raw = PlainTextComponentSerializer.plainText().serialize(component); + return substr(component, start, raw.length()+1); + } + + private OrderedText subSequence(OrderedText original, int start, int end) { + return visitor -> acceptWithAbsoluteIndex(original, (index, style, codePoint) -> { + if (index >= start && index < end) { + return visitor.accept(index - start, style, codePoint); + } + return true; + }); + } + + private OrderedText subSequence(OrderedText original, IntegerRange range) { + int start = range.getMinimum(); + int end = range.getMaximum(); + + return subSequence(original, start, end); + } + + public boolean acceptWithAbsoluteIndex(OrderedText original, CharacterVisitor visitor) { + AtomicInteger index = new AtomicInteger(); + return original.accept((ignored, style, codePoint) -> { + final boolean shouldContinue = visitor.accept(index.getAndIncrement(), style, codePoint); + if (Character.isHighSurrogate(Character.toString(codePoint).charAt(0))) { + index.getAndIncrement(); + }; + return shouldContinue; + }); } // draws the preview above the chat bar @@ -94,12 +256,6 @@ private OrderedText convert(Component component) { return nonWrappingSerializer().serialize(component).asOrderedText(); } - - private class EmptyInsertion implements Inserting { - @Override - public @NotNull Component value() { return Component.empty(); } - } - private enum CommandType { NUMBER(HypercubeCommands.NUMBER, true, false), STRING(HypercubeCommands.STRING, true, false), diff --git a/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java b/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java index 07a9b7d3..33d2290b 100644 --- a/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java +++ b/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java @@ -1,5 +1,6 @@ package dev.dfonline.codeclient.dev.highlighter; +import dev.dfonline.codeclient.CodeClient; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextColor; @@ -27,8 +28,7 @@ public class MiniMessageHighlighter { public MiniMessage HIGHLIGHTER = MiniMessage.builder().tags(new ShownTagResolver()).build(); - - public Component highlight(String input) { + public Component highlight(String input, boolean useMiniMessage) { Node.Root root = HIGHLIGHTER.deserializeToTree(input); System.out.println(root.input()); @@ -39,7 +39,7 @@ public Component highlight(String input) { - return Component.text(input).color(NamedTextColor.AQUA); + return Component.text(input); } diff --git a/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java b/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java index 0e2bf0c4..0f8514c1 100644 --- a/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java +++ b/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java @@ -6,6 +6,7 @@ import net.minecraft.client.gui.screen.ChatInputSuggestor; import net.minecraft.client.gui.widget.TextFieldWidget; import net.minecraft.text.OrderedText; +import org.apache.commons.lang3.IntegerRange; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; @@ -25,13 +26,16 @@ public class MChatInputSuggester { private OrderedText preview = null; @Inject(method = "provideRenderText", at = @At("RETURN"), cancellable = true) - private void provideRenderText(String original, int firstCharacterIndex, CallbackInfoReturnable cir) { - OrderedText originalValue = cir.getReturnValue(); - + private void provideRenderText(String partial, int position, CallbackInfoReturnable cir) { CodeClient.getFeature(ExpressionHighlighter.class).ifPresent((action) -> { if (!action.enabled()) return; - ExpressionHighlighter.HighlightedExpression expression = action.format(textField.getText()); + System.out.println(partial + " | "+ textField.getText()); + + var range = IntegerRange.of(position, position + partial.length()); + ExpressionHighlighter.HighlightedExpression expression = action.format(textField.getText(), partial, range); + + if (expression == null) return; preview = expression.preview(); cir.setReturnValue(expression.text()); // todo: highlighting in the edit box @@ -44,9 +48,8 @@ private void renderPreview(DrawContext context, int mouseX, int mouseY, Callback if (!action.enabled() || preview == null) return; action.draw(context, mouseX, mouseY, preview); + preview = null; // prevents a preview from showing if the player deletes all text }); } - - } From fcddc0a5494356f2a2bd8c68c777395a2fbb8a99 Mon Sep 17 00:00:00 2001 From: vae <66442608+sylviameows@users.noreply.github.com> Date: Sun, 13 Oct 2024 20:38:57 -0500 Subject: [PATCH 03/11] highlight minimessage tags properly in editbox --- .../highlighter/ExpressionHighlighter.java | 7 +- .../highlighter/MiniMessageHighlighter.java | 101 ++++++++++++------ .../hypercube/HypercubeMinimessage.java | 11 +- .../screen/chat/MChatInputSuggester.java | 2 - 4 files changed, 78 insertions(+), 43 deletions(-) diff --git a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java index 423ae5d0..e3b6aab7 100644 --- a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java +++ b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java @@ -100,8 +100,8 @@ public HighlightedExpression format(String input, String partial, IntegerRange r Component text; switch (varItem.getId()) { - case "num", "var", "txt" -> text = highlighter.highlight(input, false); - case "comp" -> text = highlighter.highlight(input, true); + case "num", "var", "txt" -> text = highlighter.highlight(input); + case "comp" -> text = highlighter.highlight(input); default -> { return null; } @@ -143,8 +143,7 @@ private HighlightedExpression formatCommand(String input, CommandType command, i // edit box Component highlighted = highlighter.highlight( - input.substring(start, end), - command.parseMinimessage + input.substring(start, end) ); Component combined = Component.text(input.substring(0, start)).color(NamedTextColor.GRAY).append(highlighted); diff --git a/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java b/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java index 33d2290b..b08a8f73 100644 --- a/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java +++ b/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java @@ -1,76 +1,111 @@ package dev.dfonline.codeclient.dev.highlighter; -import dev.dfonline.codeclient.CodeClient; +import dev.dfonline.codeclient.hypercube.HypercubeMinimessage; import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.minimessage.Context; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.ParsingException; -import net.kyori.adventure.text.minimessage.internal.parser.node.TagNode; -import net.kyori.adventure.text.minimessage.internal.parser.node.TagPart; -import net.kyori.adventure.text.minimessage.internal.parser.node.ValueNode; +import net.kyori.adventure.text.minimessage.internal.parser.Token; +import net.kyori.adventure.text.minimessage.internal.parser.node.*; import net.kyori.adventure.text.minimessage.tag.Tag; import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.kyori.adventure.text.minimessage.tag.standard.StandardTags; import net.kyori.adventure.text.minimessage.tree.Node; -import net.minecraft.client.gui.widget.TextFieldWidget; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.function.Consumer; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; /** * Parses MiniMessage input, but leaves the tags in the message for formatting in the edit box. */ public class MiniMessageHighlighter { - public MiniMessage HIGHLIGHTER = MiniMessage.builder().tags(new ShownTagResolver()).build(); + public MiniMessage HIGHLIGHTER = MiniMessage.builder().tags(TagResolver.resolver( + new ShownTagResolver(), + HypercubeMinimessage.NEWLINE_TAG, + HypercubeMinimessage.SPACE_TAG + )).build(); - public Component highlight(String input, boolean useMiniMessage) { - Node.Root root = HIGHLIGHTER.deserializeToTree(input); + private final String TAG_STYLE = "c:#808080"; + private final String RESET_TAG = ""; - System.out.println(root.input()); + public Component highlight(String input) { - int index = 0; + // handle "resets" + var resets = input.split("(?i)"+RESET_TAG); + if (resets.length > 1 || input.toLowerCase().endsWith(RESET_TAG)) { + Component value = Component.empty(); - handle(root); + Component reset = HIGHLIGHTER.deserialize(String.format("<%s>", TAG_STYLE) + HIGHLIGHTER.escapeTags(RESET_TAG) + String.format("", TAG_STYLE)); + for (String partial : resets) { + value = value.append(highlight(partial)) + .append(reset); + } + return value; + } + Node.Root root = HIGHLIGHTER.deserializeToTree(input); + StringBuilder newInput = new StringBuilder(input.length()); - return Component.text(input); + handle(root, root.input(), newInput, new AtomicInteger()); + return HIGHLIGHTER.deserialize(newInput.toString()); } - - @SuppressWarnings("UnstableApiUsage") - private String handle(Node node) { - String style; + private void handle(Node node, String full, StringBuilder sb, AtomicInteger index) { + String style = TAG_STYLE; + if (node instanceof TagNode tagNode) { - System.out.println(tagNode.tag() + " | " + tagNode.name()); - for (TagPart part : tagNode.parts()) { - System.out.println(part.token() + " : " + part.value()); - } + String tagString = getTokenString(tagNode.token(), full); - style = "gray"; - } - if (node instanceof ValueNode valueNode) { - System.out.println(valueNode.value() + " | " + valueNode.token()); - style = "gray"; + index.addAndGet(tagString.length()); + + appendEscapedTag(sb, tagString, style); + sb.append(tagString); + } else if (node instanceof ValueNode valueNode) { + String value = valueNode.value(); + + index.addAndGet(value.length()); + + sb.append(HIGHLIGHTER.escapeTags(value)); } for (Node child : node.children()) { - System.out.println("- new child"); - handle(child); - System.out.println("- end of child"); + handle(child, full, sb, index); + } + + if (node instanceof TagNode tagNode) { + String closing = String.format("", tagNode.name()); + if (full.startsWith(closing, index.get())) { + index.addAndGet(closing.length()); + sb.append(closing); + appendEscapedTag(sb, closing, style); + } } + } - return null; + private void appendEscapedTag(StringBuilder sb, String tag, String style) { + interpolate(sb, "<", style, ">", HIGHLIGHTER.escapeTags(tag), ""); } + private void interpolate(StringBuilder sb, String... substrings) { + for (String substring : substrings) { + sb.append(substring); + } + } + + @SuppressWarnings("UnstableApiUsage") + private String getTokenString(Token token, String root) { + int start = token.startIndex(); + int end = token.endIndex(); + return root.substring(start, end); + } - private class ShownTagResolver implements TagResolver { + private static class ShownTagResolver implements TagResolver { private final TagResolver standard = TagResolver.resolver( StandardTags.color(), StandardTags.decorations(TextDecoration.BOLD), diff --git a/src/main/java/dev/dfonline/codeclient/hypercube/HypercubeMinimessage.java b/src/main/java/dev/dfonline/codeclient/hypercube/HypercubeMinimessage.java index e139b1c9..1850323d 100644 --- a/src/main/java/dev/dfonline/codeclient/hypercube/HypercubeMinimessage.java +++ b/src/main/java/dev/dfonline/codeclient/hypercube/HypercubeMinimessage.java @@ -9,16 +9,19 @@ public class HypercubeMinimessage { public static final MiniMessage FORMATTER = MiniMessage.builder().tags(Tags.ALL).build(); + public static final TagResolver SPACE_TAG = Tags.SPACE; + public static final TagResolver NEWLINE_TAG = Tags.NEWLINE; + private static class Tags { private static final int MAX_REPETITION_COUNT = 32; // todo: remove when adventure is >5.15.0 (minecraft 1.21) - private static final TagResolver standard = StandardTags.defaults(); + private static final TagResolver STANDARD = StandardTags.defaults(); - private static final TagResolver space = repetitionTagResolver("space", " "); - private static final TagResolver newline = repetitionTagResolver("newline", "\n"); + static final TagResolver SPACE = repetitionTagResolver("space", " "); + static final TagResolver NEWLINE = repetitionTagResolver("newline", "\n"); - static final TagResolver ALL = TagResolver.resolver(standard, space, newline); + static final TagResolver ALL = TagResolver.resolver(STANDARD, SPACE, NEWLINE); private static TagResolver repetitionTagResolver(String name, String literal) { return TagResolver.resolver(name, (arguments, context) -> { diff --git a/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java b/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java index 0f8514c1..adb10e30 100644 --- a/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java +++ b/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java @@ -30,8 +30,6 @@ private void provideRenderText(String partial, int position, CallbackInfoReturna CodeClient.getFeature(ExpressionHighlighter.class).ifPresent((action) -> { if (!action.enabled()) return; - System.out.println(partial + " | "+ textField.getText()); - var range = IntegerRange.of(position, position + partial.length()); ExpressionHighlighter.HighlightedExpression expression = action.format(textField.getText(), partial, range); From 6c3bf6252a5590cc419b884af95f63e038518dcd Mon Sep 17 00:00:00 2001 From: vae <66442608+sylviameows@users.noreply.github.com> Date: Sun, 13 Oct 2024 23:04:31 -0500 Subject: [PATCH 04/11] expression highlights --- .../highlighter/ExpressionHighlighter.java | 139 +++++++++++------- 1 file changed, 89 insertions(+), 50 deletions(-) diff --git a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java index e3b6aab7..08427d50 100644 --- a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java +++ b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java @@ -10,6 +10,7 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tree.Node; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; @@ -18,6 +19,7 @@ import net.minecraft.text.OrderedText; import net.minecraft.text.Style; import net.minecraft.text.TextColor; +import net.minecraft.util.Formatting; import org.apache.commons.lang3.IntegerRange; import org.jetbrains.annotations.Nullable; @@ -48,19 +50,24 @@ public class ExpressionHighlighter extends Feature { "entry" ); + // palette copied from recode, maybe there's a better combination? private final List COLORS = List.of( - TextColor.fromRgb(0xffd600) + TextColor.fromRgb(0xffd42a), + TextColor.fromRgb(0x33ff00), + TextColor.fromRgb(0x00ffe0), + TextColor.fromRgb(0x5e77f7), + TextColor.fromRgb(0xca64fa), + TextColor.fromRgb(0xff4242) ); + private final TextColor ERROR_COLOR = TextColor.fromFormatting(Formatting.RED); @Override public boolean enabled() { return CodeClient.location instanceof Dev; // todo: config } - public record HighlightedExpression(OrderedText text, @Nullable OrderedText preview) { - - } + public record HighlightedExpression(OrderedText text, @Nullable OrderedText preview) {} private String cachedInput = ""; private int cachedPosition = 0; @@ -100,8 +107,8 @@ public HighlightedExpression format(String input, String partial, IntegerRange r Component text; switch (varItem.getId()) { - case "num", "var", "txt" -> text = highlighter.highlight(input); - case "comp" -> text = highlighter.highlight(input); + case "num", "var", "txt" -> text = highlightExpressions(input); + case "comp" -> text = highlightExpressions(highlighter.highlight(input)); default -> { return null; } @@ -142,9 +149,12 @@ private HighlightedExpression formatCommand(String input, CommandType command, i } // edit box - Component highlighted = highlighter.highlight( - input.substring(start, end) - ); + Component highlighted; + if (command.parseMinimessage) { + highlighted = highlightExpressions(highlighter.highlight(input.substring(start, end))); + } else { + highlighted = highlightExpressions(input.substring(start,end)); + } Component combined = Component.text(input.substring(0, start)).color(NamedTextColor.GRAY).append(highlighted); @@ -153,64 +163,93 @@ private HighlightedExpression formatCommand(String input, CommandType command, i } // preview text - Component formatted = formatter.deserialize(input.substring(start, end)); + Component formatted = Component.empty(); + if (command.parseMinimessage) { + formatted = formatter.deserialize(input.substring(start, end)); + } // return new HighlightedExpression(splitComponent(combined, cursor), convert(formatted)); return new HighlightedExpression(subSequence(convert(combined), range), convert(formatted)); } - private OrderedText splitComponent(Component component, int cursor) { - String raw = PlainTextComponentSerializer.plainText().serialize(component); + private Component highlightExpressions(Component component) { + String raw = formatter.serialize(component); - Pattern half = Pattern.compile("^.{"+(cursor-1)+"}"); - Matcher matcher = half.matcher(raw); + return highlightExpressions(raw); + } - if (!matcher.matches()) return OrderedText.concat(convert(component)); + private Component highlightExpressions(String raw) { + StringBuilder sb = new StringBuilder(raw.length()); - Pattern first = Pattern.compile("^"+matcher.group()); - Pattern last = Pattern.compile(matcher.replaceAll("")+"$"); + Pattern pattern = Pattern.compile("(%[a-zA-Z]+\\(?)|\\)|$"); + Matcher matcher = pattern.matcher(raw); - Component firstComponent = component.replaceText(builder -> { - builder.replacement(""); - builder.match(last); - }); - Component secondComponent = component.replaceText(builder -> { - builder.replacement(""); - builder.match(first); - }); + int depth = 0; + int start = 0; - return OrderedText.concat(convert(firstComponent), convert(secondComponent)); - } + while (matcher.find()) { + String value = matcher.group(); + if (Objects.equals(value, ")")) { + if (start != matcher.start()) { + String prev = raw.substring(start, matcher.start()); + sb.append(prev); + } + + if (depth <= 0) { + sb.append(value); + start = matcher.end(); + continue; + } + + var color = getColor(depth); + depth--; + + String style = color.getHexCode(); + sb.append(String.format("", style)); + sb.append(value); - /** takes a given substring of a component. - * @param start inclusive - * @param end exclusive - * @return a component containing what's between the start index and end index. - */ - private Component substr(Component component, int start, int end) { - String raw = PlainTextComponentSerializer.plainText().serialize(component); - String split = raw.substring(start, end); + style = getColor(depth).getHexCode(); + depth--; + sb.append(String.format("", style)); + } else { + depth++; - // this variable reveals what needs to be replaced in the component. - var replace = raw.split(split); + var color = getColor(depth); + if (value.length() > 1 && !CODES.contains(value.replace("%", "").replace("(", ""))) { + color = ERROR_COLOR; + } - if (split.equals(raw)) return component; + if (start != matcher.start()) { + String prev = raw.substring(start, matcher.start()); + sb.append(prev); + } - return component.replaceText(builder -> { - for (String section : replace) { - builder.matchLiteral(section); + String style = color.getHexCode(); + sb.append(String.format("", style)); + sb.append(value); + + if (value.endsWith("(")) { + depth++; + style = getColor(depth).getHexCode(); + sb.append(String.format("", style)); + } else if (depth > 0){ + depth--; + sb.append(String.format("", style)); + } } - builder.replacement(""); - }); + start = matcher.end(); + } + + return formatter.deserialize(sb.toString()); } - /** takes a given substring of a component. - * @param start inclusive - * @return a component containing what's after the start index - */ - private Component substr(Component component, int start) { - String raw = PlainTextComponentSerializer.plainText().serialize(component); - return substr(component, start, raw.length()+1); + private TextColor getColor(int depth) { + if (depth < 1) return getColor(COLORS.size() - depth); + try { + return COLORS.get(depth - 1); + } catch (Exception e) { + return getColor(depth - COLORS.size()); + } } private OrderedText subSequence(OrderedText original, int start, int end) { From 200e414b57f9766da0142848412ea68943bfa890 Mon Sep 17 00:00:00 2001 From: vae <66442608+sylviameows@users.noreply.github.com> Date: Sun, 13 Oct 2024 23:48:31 -0500 Subject: [PATCH 05/11] highlighter config --- .../dfonline/codeclient/config/Config.java | 78 +++++++++++++++++++ .../highlighter/ExpressionHighlighter.java | 15 +++- .../highlighter/MiniMessageHighlighter.java | 10 ++- .../assets/codeclient/lang/en_us.json | 15 +++- 4 files changed, 111 insertions(+), 7 deletions(-) diff --git a/src/main/java/dev/dfonline/codeclient/config/Config.java b/src/main/java/dev/dfonline/codeclient/config/Config.java index a1648975..c4d97de5 100644 --- a/src/main/java/dev/dfonline/codeclient/config/Config.java +++ b/src/main/java/dev/dfonline/codeclient/config/Config.java @@ -81,6 +81,11 @@ public class Config { public CPUDisplayCorner CPUDisplayCornerOption = CPUDisplayCorner.TOP_LEFT; public boolean HideScopeChangeMessages = true; + public boolean HighlighterEnabled = true; // whether expression highlighting is enabled. + public boolean HighlightExpressions = true; // if the highlighter marks up df expressions + public boolean HighlightMiniMessage = true; // if the highlighter marks up minimessage. + public int MiniMessageTagColor = 0x808080; // the color of minimessage tags in the edit box. + public Config() { } @@ -163,6 +168,12 @@ public void save() { object.addProperty("CPUDisplay", CPUDisplay); object.addProperty("CPUDisplayCorner", CPUDisplayCornerOption.name()); object.addProperty("HideScopeChangeMessages", HideScopeChangeMessages); + + object.addProperty("HighlighterEnabled", HighlighterEnabled); + object.addProperty("HighlightExpressions", HighlightExpressions); + object.addProperty("HighlightMiniMessage", HighlightMiniMessage); + object.addProperty("MiniMessageTagColor", MiniMessageTagColor); + FileManager.writeConfig(object.toString()); } catch (Exception e) { CodeClient.LOGGER.info("Couldn't save config: " + e); @@ -755,6 +766,73 @@ public YetAnotherConfigLib getLibConfig() { .build()) .build()) // + // + .group(OptionGroup.createBuilder() + .name(Text.translatable("codeclient.config.chat_highlighter")) + .description(OptionDescription.of( + Text.translatable("codeclient.config.chat_highlighter.description"), + Text.translatable("codeclient.config.chat_highlighter.description.extra") + )) + .option(Option.createBuilder(Boolean.class) + .name(Text.translatable("codeclient.config.chat_highlighter.enable")) + .description(OptionDescription.of( + Text.translatable("codeclient.config.chat_highlighter.enable.description"), + Text.empty(), + Text.translatable("codeclient.config.chat_highlighter.description").setStyle(Style.EMPTY.withColor(Formatting.GRAY)), + Text.translatable("codeclient.config.chat_highlighter.description.extra").setStyle(Style.EMPTY.withColor(Formatting.GRAY)) + )) + .binding( + true, + () -> HighlighterEnabled, + opt -> HighlighterEnabled = opt + ) + .controller(TickBoxControllerBuilder::create) + .build() + ) + .option(Option.createBuilder(Boolean.class) + .name(Text.translatable("codeclient.config.chat_highlighter.with_expressions")) + .description(OptionDescription.of( + Text.translatable("codeclient.config.chat_highlighter.with_expressions.description"), + Text.translatable("codeclient.config.chat_highlighter.with_expressions.description.example") + )) + .binding( + true, + () -> HighlightExpressions, + opt -> HighlightExpressions = opt + ) + .controller(TickBoxControllerBuilder::create) + .build() + ) + .option(Option.createBuilder(Boolean.class) + .name(Text.translatable("codeclient.config.chat_highlighter.with_minimessage")) + .description(OptionDescription.of( + Text.translatable("codeclient.config.chat_highlighter.with_minimessage.description1"), + Text.translatable("codeclient.config.chat_highlighter.with_minimessage.description2") + )) + .binding( + true, + () -> HighlightMiniMessage, + opt -> HighlightMiniMessage = opt + ) + .controller(TickBoxControllerBuilder::create) + .build() + ) + .option(Option.createBuilder(Color.class) + .name(Text.translatable("codeclient.config.chat_highlighter.minimessage_tag_color")) + .description(OptionDescription.of( + Text.translatable("codeclient.config.chat_highlighter.minimessage_tag_color.description") + )) + .binding( + new Color((float) 128 / 255, (float) 128 / 255, (float) 128 / 255), + () -> new Color(MiniMessageTagColor), + opt -> MiniMessageTagColor = opt.getRGB() + ) + .controller(ColorControllerBuilder::create) + .build() + ) + .build() + ) + // // .group(OptionGroup.createBuilder() .name(Text.translatable("codeclient.config.chest_highlight")) diff --git a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java index 08427d50..e22ce29a 100644 --- a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java +++ b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java @@ -2,6 +2,7 @@ import dev.dfonline.codeclient.CodeClient; import dev.dfonline.codeclient.Feature; +import dev.dfonline.codeclient.config.Config; import dev.dfonline.codeclient.hypercube.HypercubeCommands; import dev.dfonline.codeclient.hypercube.HypercubeMinimessage; import dev.dfonline.codeclient.hypercube.item.VarItem; @@ -64,7 +65,7 @@ public class ExpressionHighlighter extends Feature { @Override public boolean enabled() { - return CodeClient.location instanceof Dev; // todo: config + return CodeClient.location instanceof Dev && Config.getConfig().HighlighterEnabled; } public record HighlightedExpression(OrderedText text, @Nullable OrderedText preview) {} @@ -107,8 +108,16 @@ public HighlightedExpression format(String input, String partial, IntegerRange r Component text; switch (varItem.getId()) { - case "num", "var", "txt" -> text = highlightExpressions(input); - case "comp" -> text = highlightExpressions(highlighter.highlight(input)); + case "num", "var", "txt" -> { + if (Config.getConfig().HighlightExpressions) text = highlightExpressions(input); + else text = Component.text(input); + } + case "comp" -> { + if (Config.getConfig().HighlightMiniMessage) text = highlighter.highlight(input); + else text = Component.text(input); + + if (Config.getConfig().HighlightExpressions) text = highlightExpressions(text); + } default -> { return null; } diff --git a/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java b/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java index b08a8f73..c15acd4d 100644 --- a/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java +++ b/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java @@ -1,7 +1,9 @@ package dev.dfonline.codeclient.dev.highlighter; +import dev.dfonline.codeclient.config.Config; import dev.dfonline.codeclient.hypercube.HypercubeMinimessage; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.minimessage.Context; import net.kyori.adventure.text.minimessage.MiniMessage; @@ -29,8 +31,10 @@ public class MiniMessageHighlighter { HypercubeMinimessage.SPACE_TAG )).build(); - private final String TAG_STYLE = "c:#808080"; private final String RESET_TAG = ""; + private String getTagStyle() { + return TextColor.color(Config.getConfig().MiniMessageTagColor).asHexString(); + } public Component highlight(String input) { @@ -39,7 +43,7 @@ public Component highlight(String input) { if (resets.length > 1 || input.toLowerCase().endsWith(RESET_TAG)) { Component value = Component.empty(); - Component reset = HIGHLIGHTER.deserialize(String.format("<%s>", TAG_STYLE) + HIGHLIGHTER.escapeTags(RESET_TAG) + String.format("", TAG_STYLE)); + Component reset = HIGHLIGHTER.deserialize(String.format("<%s>", getTagStyle()) + HIGHLIGHTER.escapeTags(RESET_TAG) + String.format("", getTagStyle())); for (String partial : resets) { value = value.append(highlight(partial)) @@ -57,7 +61,7 @@ public Component highlight(String input) { @SuppressWarnings("UnstableApiUsage") private void handle(Node node, String full, StringBuilder sb, AtomicInteger index) { - String style = TAG_STYLE; + String style = getTagStyle(); if (node instanceof TagNode tagNode) { String tagString = getTokenString(tagNode.token(), full); diff --git a/src/main/resources/assets/codeclient/lang/en_us.json b/src/main/resources/assets/codeclient/lang/en_us.json index 8e91bdf8..b698899d 100644 --- a/src/main/resources/assets/codeclient/lang/en_us.json +++ b/src/main/resources/assets/codeclient/lang/en_us.json @@ -295,5 +295,18 @@ "codeclient.command.uuid": "UUID of %s is %s!", "codeclient.command.name.not_found": "UUID %s was not found! Please check if you misspelled it and try again", "codeclient.command.name": "Name of %s is %s!", - "codeclient.command.name.own": "The UUID is yours! What a surprise!" + "codeclient.command.name.own": "The UUID is yours! What a surprise!", + "codeclient.config.chat_highlighter": "Chat Highlighter", + "codeclient.config.chat_highlighter.description": "Modifies the chat box to display MiniMessage highlighting and marks up DF expressions.", + "codeclient.config.chat_highlighter.description.extra": "Additionally provides a preview above the chat bar when editing styled text.", + "codeclient.config.chat_highlighter.enable": "Enable Chat Highlighter", + "codeclient.config.chat_highlighter.enable.description": "Enables the chat highlighter:", + "codeclient.config.chat_highlighter.with_expressions": "Highlight Expressions", + "codeclient.config.chat_highlighter.with_expressions.description": "When enabled DF expressions will be given special formatting in your chat box.", + "codeclient.config.chat_highlighter.with_expressions.description.example": "e.g. %uuid, %math(3-2), or %var(%uuid score)", + "codeclient.config.chat_highlighter.with_minimessage": "Highlight MiniMessage", + "codeclient.config.chat_highlighter.with_minimessage.description1": "When enabled, a preview will display above the chat bar when editing styled text.", + "codeclient.config.chat_highlighter.with_minimessage.description2": "It will additionally color the edit box to show formatting with the tags still being visible.", + "codeclient.config.chat_highlighter.minimessage_tag_color": "MiniMessage Tag Color", + "codeclient.config.chat_highlighter.minimessage_tag_color.description": "The color MiniMessage tags show up as in the chat box when modifying styled text." } \ No newline at end of file From b062c56cbad301e0a6bd40c1f621a53673a7298c Mon Sep 17 00:00:00 2001 From: vae <66442608+sylviameows@users.noreply.github.com> Date: Mon, 14 Oct 2024 00:20:36 -0500 Subject: [PATCH 06/11] small fixes --- .../codeclient/dev/highlighter/ExpressionHighlighter.java | 8 ++++++-- .../codeclient/mixin/screen/chat/MChatInputSuggester.java | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java index e22ce29a..0b0509f4 100644 --- a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java +++ b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java @@ -160,9 +160,13 @@ private HighlightedExpression formatCommand(String input, CommandType command, i // edit box Component highlighted; if (command.parseMinimessage) { - highlighted = highlightExpressions(highlighter.highlight(input.substring(start, end))); + if (Config.getConfig().HighlightMiniMessage) highlighted = highlighter.highlight(input.substring(start,end)); + else highlighted = Component.text(input.substring(start,end)); + + if (Config.getConfig().HighlightExpressions) highlighted = highlightExpressions(highlighted); } else { - highlighted = highlightExpressions(input.substring(start,end)); + if (Config.getConfig().HighlightExpressions) highlighted = highlightExpressions(input.substring(start,end)); + else highlighted = Component.text(input.substring(start,end)); } Component combined = Component.text(input.substring(0, start)).color(NamedTextColor.GRAY).append(highlighted); diff --git a/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java b/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java index adb10e30..c5e721d3 100644 --- a/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java +++ b/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java @@ -16,6 +16,8 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import java.util.Objects; + @Mixin(ChatInputSuggestor.class) public class MChatInputSuggester { @@ -27,6 +29,7 @@ public class MChatInputSuggester { @Inject(method = "provideRenderText", at = @At("RETURN"), cancellable = true) private void provideRenderText(String partial, int position, CallbackInfoReturnable cir) { + if (Objects.equals(partial, "")) return; CodeClient.getFeature(ExpressionHighlighter.class).ifPresent((action) -> { if (!action.enabled()) return; From 0ee7f1f05f7b8da3b29949e06f1e8e81dd8dd36d Mon Sep 17 00:00:00 2001 From: vae <66442608+sylviameows@users.noreply.github.com> Date: Mon, 14 Oct 2024 00:36:10 -0500 Subject: [PATCH 07/11] complete todos --- .../codeclient/dev/highlighter/ExpressionHighlighter.java | 2 +- .../codeclient/mixin/screen/chat/MChatInputSuggester.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java index 0b0509f4..63e329de 100644 --- a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java +++ b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java @@ -293,7 +293,7 @@ public boolean acceptWithAbsoluteIndex(OrderedText original, CharacterVisitor vi } // draws the preview above the chat bar - public void draw(DrawContext context, int mouseX, int mouseY, OrderedText input /* todo take out of mixin */) { + public void draw(DrawContext context, int mouseX, int mouseY, OrderedText input) { TextRenderer renderer = CodeClient.MC.textRenderer; var screen = CodeClient.MC.currentScreen; diff --git a/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java b/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java index c5e721d3..9f847e13 100644 --- a/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java +++ b/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java @@ -39,7 +39,7 @@ private void provideRenderText(String partial, int position, CallbackInfoReturna if (expression == null) return; preview = expression.preview(); - cir.setReturnValue(expression.text()); // todo: highlighting in the edit box + cir.setReturnValue(expression.text()); }); } From 501d253f5c82a344343e53e7d94a08abc2752009 Mon Sep 17 00:00:00 2001 From: vae <66442608+sylviameows@users.noreply.github.com> Date: Mon, 14 Oct 2024 05:43:45 -0500 Subject: [PATCH 08/11] remove comments in config --- src/main/java/dev/dfonline/codeclient/config/Config.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/dev/dfonline/codeclient/config/Config.java b/src/main/java/dev/dfonline/codeclient/config/Config.java index c4d97de5..c840f089 100644 --- a/src/main/java/dev/dfonline/codeclient/config/Config.java +++ b/src/main/java/dev/dfonline/codeclient/config/Config.java @@ -80,11 +80,10 @@ public class Config { public boolean CPUDisplay = true; public CPUDisplayCorner CPUDisplayCornerOption = CPUDisplayCorner.TOP_LEFT; public boolean HideScopeChangeMessages = true; - - public boolean HighlighterEnabled = true; // whether expression highlighting is enabled. - public boolean HighlightExpressions = true; // if the highlighter marks up df expressions - public boolean HighlightMiniMessage = true; // if the highlighter marks up minimessage. - public int MiniMessageTagColor = 0x808080; // the color of minimessage tags in the edit box. + public boolean HighlighterEnabled = true; + public boolean HighlightExpressions = true; + public boolean HighlightMiniMessage = true; + public int MiniMessageTagColor = 0x808080; public Config() { } From 13e5679998a7695e151a586fb3176b560392544e Mon Sep 17 00:00:00 2001 From: vae <66442608+sylviameows@users.noreply.github.com> Date: Mon, 14 Oct 2024 05:53:14 -0500 Subject: [PATCH 09/11] create consistency with other features --- .../codeclient/dev/highlighter/ExpressionHighlighter.java | 2 +- .../codeclient/mixin/screen/chat/MChatInputSuggester.java | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java index 63e329de..a2c43ab5 100644 --- a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java +++ b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java @@ -65,7 +65,7 @@ public class ExpressionHighlighter extends Feature { @Override public boolean enabled() { - return CodeClient.location instanceof Dev && Config.getConfig().HighlighterEnabled; + return Config.getConfig().HighlighterEnabled; } public record HighlightedExpression(OrderedText text, @Nullable OrderedText preview) {} diff --git a/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java b/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java index 9f847e13..9b670e67 100644 --- a/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java +++ b/src/main/java/dev/dfonline/codeclient/mixin/screen/chat/MChatInputSuggester.java @@ -2,6 +2,7 @@ import dev.dfonline.codeclient.CodeClient; import dev.dfonline.codeclient.dev.highlighter.ExpressionHighlighter; +import dev.dfonline.codeclient.location.Dev; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.ChatInputSuggestor; import net.minecraft.client.gui.widget.TextFieldWidget; @@ -31,7 +32,7 @@ public class MChatInputSuggester { private void provideRenderText(String partial, int position, CallbackInfoReturnable cir) { if (Objects.equals(partial, "")) return; CodeClient.getFeature(ExpressionHighlighter.class).ifPresent((action) -> { - if (!action.enabled()) return; + if (!action.enabled() || !(CodeClient.location instanceof Dev)) return; var range = IntegerRange.of(position, position + partial.length()); ExpressionHighlighter.HighlightedExpression expression = action.format(textField.getText(), partial, range); @@ -46,7 +47,7 @@ private void provideRenderText(String partial, int position, CallbackInfoReturna @Inject(method = "render", at = @At("HEAD")) private void renderPreview(DrawContext context, int mouseX, int mouseY, CallbackInfo ci) { CodeClient.getFeature(ExpressionHighlighter.class).ifPresent((action) -> { - if (!action.enabled() || preview == null) return; + if (!action.enabled() || !(CodeClient.location instanceof Dev) || preview == null) return; action.draw(context, mouseX, mouseY, preview); preview = null; // prevents a preview from showing if the player deletes all text From e4829eda0423258f48300a755c8b5a9a6e9e16ef Mon Sep 17 00:00:00 2001 From: vae <66442608+sylviameows@users.noreply.github.com> Date: Sat, 19 Oct 2024 23:23:39 -0500 Subject: [PATCH 10/11] fix leaking color codes --- .../highlighter/ExpressionHighlighter.java | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java index a2c43ab5..1180541d 100644 --- a/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java +++ b/src/main/java/dev/dfonline/codeclient/dev/highlighter/ExpressionHighlighter.java @@ -24,6 +24,7 @@ import org.apache.commons.lang3.IntegerRange; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Set; @@ -200,6 +201,8 @@ private Component highlightExpressions(String raw) { int depth = 0; int start = 0; + boolean invalid = false; + while (matcher.find()) { String value = matcher.group(); if (Objects.equals(value, ")")) { @@ -217,19 +220,26 @@ private Component highlightExpressions(String raw) { var color = getColor(depth); depth--; - String style = color.getHexCode(); - sb.append(String.format("", style)); + String style; + if (!invalid) { + style = color.getHexCode(); + sb.append(String.format("", style)); + style = getColor(depth).getHexCode(); + } else { + style = ERROR_COLOR.getHexCode(); + sb.append(String.format("", style)); + } sb.append(value); - style = getColor(depth).getHexCode(); depth--; sb.append(String.format("", style)); } else { depth++; var color = getColor(depth); - if (value.length() > 1 && !CODES.contains(value.replace("%", "").replace("(", ""))) { + if ((value.length() > 1 && !CODES.contains(value.replace("%", "").replace("(", "")) && depth <= 1) || invalid) { color = ERROR_COLOR; + invalid = true; } if (start != matcher.start()) { @@ -243,10 +253,13 @@ private Component highlightExpressions(String raw) { if (value.endsWith("(")) { depth++; - style = getColor(depth).getHexCode(); + if (!invalid) { + style = getColor(depth).getHexCode(); + } sb.append(String.format("", style)); } else if (depth > 0){ depth--; + sb.append(String.format("", style)); } } From 1a622e4027627d39046b7b5bc93883106c63dade Mon Sep 17 00:00:00 2001 From: vae <66442608+sylviameows@users.noreply.github.com> Date: Sat, 19 Oct 2024 23:55:57 -0500 Subject: [PATCH 11/11] drafted clearing decorations --- .../highlighter/MiniMessageHighlighter.java | 38 +++++++++++++++---- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java b/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java index c15acd4d..d9ba18fc 100644 --- a/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java +++ b/src/main/java/dev/dfonline/codeclient/dev/highlighter/MiniMessageHighlighter.java @@ -18,8 +18,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.concurrent.atomic.AtomicInteger; -import java.util.regex.Pattern; /** * Parses MiniMessage input, but leaves the tags in the message for formatting in the edit box. @@ -55,12 +55,12 @@ public Component highlight(String input) { Node.Root root = HIGHLIGHTER.deserializeToTree(input); StringBuilder newInput = new StringBuilder(input.length()); - handle(root, root.input(), newInput, new AtomicInteger()); + handle(root, root.input(), newInput, new AtomicInteger(), new ArrayList<>()); return HIGHLIGHTER.deserialize(newInput.toString()); } @SuppressWarnings("UnstableApiUsage") - private void handle(Node node, String full, StringBuilder sb, AtomicInteger index) { + private void handle(Node node, String full, StringBuilder sb, AtomicInteger index, ArrayList decorations) { String style = getTagStyle(); if (node instanceof TagNode tagNode) { @@ -68,7 +68,13 @@ private void handle(Node node, String full, StringBuilder sb, AtomicInteger inde index.addAndGet(tagString.length()); - appendEscapedTag(sb, tagString, style); + appendEscapedTag(sb, tagString, style, decorations); + + String tagName = tagNode.name(); + if (StandardTags.decorations().has(tagName)) { + decorations.add(tagName); + } + sb.append(tagString); } else if (node instanceof ValueNode valueNode) { String value = valueNode.value(); @@ -79,20 +85,36 @@ private void handle(Node node, String full, StringBuilder sb, AtomicInteger inde } for (Node child : node.children()) { - handle(child, full, sb, index); + handle(child, full, sb, index, decorations); } if (node instanceof TagNode tagNode) { - String closing = String.format("", tagNode.name()); + String tagName = tagNode.name(); + String closing = String.format("", tagName); + if (full.startsWith(closing, index.get())) { + if (StandardTags.decorations().has(tagName)) { + decorations.remove(tagName); + } + index.addAndGet(closing.length()); sb.append(closing); - appendEscapedTag(sb, closing, style); + appendEscapedTag(sb, closing, style, decorations); } } } - private void appendEscapedTag(StringBuilder sb, String tag, String style) { + private void appendEscapedTag(StringBuilder sb, String tag, String style, ArrayList decorations) { + /* idk if someone wants to figure this out, it doesn't actually seem to work with multiple decorations. + StringBuilder opening = new StringBuilder(); + + StringBuilder closing = new StringBuilder(); + decorations.forEach(decoration -> { + interpolate(opening, "<", decoration, ">"); + interpolate(closing, ""); + }); + */ + interpolate(sb, "<", style, ">", HIGHLIGHTER.escapeTags(tag), ""); }