From 091fb2ea9e687a193a0a31f199fc8363cd9546ba Mon Sep 17 00:00:00 2001 From: mbax Date: Mon, 9 Dec 2013 22:47:00 -0500 Subject: [PATCH] Process URLs as clickable. Adds BUKKIT-4917 In Minecraft 1.7, URL processing was removed from the client while the server gained the ability to designate a URL to be launched in response to clicking text. However, this functionality is not implemented in the vanilla server. This commit adds that functionality to messages sent to the client, processing URLs as clickable. Additionally, char array iteration is replaced with regex. --- .../craftbukkit/util/CraftChatMessage.java | 65 +++++++++++-------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java index cc8e715023..66368f4fdb 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftChatMessage.java @@ -3,23 +3,28 @@ package org.bukkit.craftbukkit.util; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import net.minecraft.server.ChatClickable; import net.minecraft.server.ChatComponentText; import net.minecraft.server.ChatModifier; import net.minecraft.server.EnumChatFormat; +import net.minecraft.server.EnumClickAction; import net.minecraft.server.IChatBaseComponent; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap.Builder; public final class CraftChatMessage { - private static class FromString { + private static class StringMessage { private static final Map formatMap; + private static final Pattern INCREMENTAL_PATTERN = Pattern.compile("(" + String.valueOf(org.bukkit.ChatColor.COLOR_CHAR) + "[0-9a-fk-or])|(\\n)|(?:(https?://[^ ][^ ]*?)(?=[\\.\\?!,;:]?(?:[ \\n]|$)))", Pattern.CASE_INSENSITIVE); static { Builder builder = ImmutableMap.builder(); for (EnumChatFormat format : EnumChatFormat.values()) { - builder.put(format.getChar(), format); + builder.put(Character.toLowerCase(format.getChar()), format); } formatMap = builder.build(); } @@ -27,25 +32,29 @@ public final class CraftChatMessage { private final List list = new ArrayList(); private IChatBaseComponent currentChatComponent = new ChatComponentText(""); private ChatModifier modifier = new ChatModifier(); - private StringBuilder builder = new StringBuilder(); private final IChatBaseComponent[] output; + private int currentIndex; + private final String message; - private FromString(String message) { + private StringMessage(String message) { + this.message = message; if (message == null) { output = new IChatBaseComponent[] { currentChatComponent }; return; } list.add(currentChatComponent); - EnumChatFormat format = null; - - for (int i = 0; i < message.length(); i++) { - char currentChar = message.charAt(i); - if (currentChar == '\u00A7' && (i < (message.length() - 1)) && (format = formatMap.get(message.charAt(i + 1))) != null) { - if (builder.length() > 0) { - appendNewComponent(); - } - + Matcher matcher = INCREMENTAL_PATTERN.matcher(message); + String match = null; + while (matcher.find()) { + int groupId = 0; + while ((match = matcher.group(++groupId)) == null) { + // NOOP + } + appendNewComponent(matcher.start(groupId)); + switch (groupId) { + case 1: + EnumChatFormat format = formatMap.get(match.toLowerCase().charAt(1)); if (format == EnumChatFormat.RESET) { modifier = new ChatModifier(); } else if (format.isFormat()) { @@ -71,27 +80,31 @@ public final class CraftChatMessage { } else { // Color resets formatting modifier = new ChatModifier().setColor(format); } - i++; - } else if (currentChar == '\n') { - if (builder.length() > 0) { - appendNewComponent(); - } + break; + case 2: currentChatComponent = null; - } else { - builder.append(currentChar); + break; + case 3: + modifier.a(new ChatClickable(EnumClickAction.OPEN_URL, match)); // Should be setChatClickable + appendNewComponent(matcher.end(groupId)); + modifier.a((ChatClickable) null); } + currentIndex = matcher.end(groupId); } - if (builder.length() > 0) { - appendNewComponent(); + if (currentIndex < message.length()) { + appendNewComponent(message.length()); } output = list.toArray(new IChatBaseComponent[0]); } - private void appendNewComponent() { - IChatBaseComponent addition = new ChatComponentText(builder.toString()).setChatModifier(modifier); - builder = new StringBuilder(); + private void appendNewComponent(int index) { + if (index <= currentIndex) { + return; + } + IChatBaseComponent addition = new ChatComponentText(message.substring(currentIndex, index)).setChatModifier(modifier); + currentIndex = index; modifier = modifier.clone(); if (currentChatComponent == null) { currentChatComponent = new ChatComponentText(""); @@ -106,7 +119,7 @@ public final class CraftChatMessage { } public static IChatBaseComponent[] fromString(String message) { - return new FromString(message).getOutput(); + return new StringMessage(message).getOutput(); } private CraftChatMessage() {