diff --git a/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java b/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java
index 008e4ca..1d05ef4 100644
--- a/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java
+++ b/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java
@@ -22,7 +22,9 @@ package me.clip.placeholderapi.replacer;
import java.util.Locale;
import java.util.function.Function;
+
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
+import org.bukkit.ChatColor;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -36,116 +38,101 @@ public final class CharsReplacer implements Replacer {
this.closure = closure;
}
- /**
- * Translates placeholders within the provided text using a high-performance
- * character-scanning approach.
- * *
The method identifies placeholders delimited by the defined {@link Closure}
- * (e.g., %identifier_params% or {identifier_params}). If a placeholder is
- * successfully identified, the provided lookup function is used to fetch the
- * corresponding {@link PlaceholderExpansion}.
- *
- * @param text The raw text containing potential placeholders to be replaced.
- * @param player The {@link OfflinePlayer} to contextually parse the placeholders against.
- * May be {@code null} if no player context is available.
- * @param lookup A function that maps a lowercase identifier string to a registered
- * {@link PlaceholderExpansion}.
- * @return A string with all valid placeholders replaced by their respective values.
- * Returns the original text if no placeholders are found.
- */
+
@NotNull
@Override
public String apply(@NotNull final String text, @Nullable final OfflinePlayer player,
@NotNull final Function lookup) {
- final char head = closure.head;
- int startPlaceholder = text.indexOf(head);
+ final char[] chars = text.toCharArray();
+ final StringBuilder builder = new StringBuilder(text.length());
- if (startPlaceholder == -1) {
- return text;
- }
+ final StringBuilder identifier = new StringBuilder();
+ final StringBuilder parameters = new StringBuilder();
- final int length = text.length();
- final StringBuilder builder = new StringBuilder(length + (length >> 3));
- int cursor = 0;
+ for (int i = 0; i < chars.length; i++) {
+ final char l = chars[i];
- final char tail = closure.tail;
-
- loop: do {
- // Append plain text preceding the placeholder
- if (startPlaceholder > cursor) {
- builder.append(text, cursor, startPlaceholder);
- }
-
- final int endPlaceholder = text.indexOf(tail, startPlaceholder + 1);
-
- if (endPlaceholder == -1) {
- builder.append(text, startPlaceholder, length);
- return builder.toString();
- }
-
- int underscoreIndex = -1;
-
- for (int i = startPlaceholder + 1; i < endPlaceholder; i++) {
- final char current = text.charAt(i);
-
- if (current == ' ') {
- // Invalid placeholder (contains space).
- // Treat the opening symbol as literal text and search for the next one.
- builder.append(head);
- cursor = startPlaceholder + 1;
- startPlaceholder = text.indexOf(head, cursor);
-
- // Safety check: If no more placeholders exist, break to finalize
- if (startPlaceholder == -1) {
- break loop;
- }
- continue loop;
- }
-
- if (current == '_' && underscoreIndex == -1) {
- underscoreIndex = i;
- }
- }
-
- if (underscoreIndex == -1) {
- builder.append(text, startPlaceholder, endPlaceholder + 1);
- cursor = endPlaceholder + 1;
- startPlaceholder = text.indexOf(head, cursor);
+ if (l != closure.head || i + 1 >= chars.length) {
+ builder.append(l);
continue;
}
- String identifier = text.substring(startPlaceholder + 1, underscoreIndex);
- String parameters = "";
+ boolean identified = false;
+ boolean invalid = true;
+ boolean hadSpace = false;
- if (underscoreIndex + 1 < endPlaceholder) {
- parameters = text.substring(underscoreIndex + 1, endPlaceholder);
+ while (++i < chars.length) {
+ final char p = chars[i];
+
+ if (p == ' ' && !identified) {
+ hadSpace = true;
+ break;
+ }
+ if (p == closure.tail) {
+ invalid = false;
+ break;
+ }
+
+ if (p == '_' && !identified) {
+ identified = true;
+ continue;
+ }
+
+ if (identified) {
+ parameters.append(p);
+ } else {
+ identifier.append(p);
+ }
}
- final PlaceholderExpansion expansion = lookup.apply(identifier.toLowerCase(Locale.ROOT));
- String replacement = null;
+ final String identifierString = identifier.toString();
+ final String lowercaseIdentifierString = identifierString.toLowerCase(Locale.ROOT);
+ final String parametersString = parameters.toString();
- if (expansion != null) {
- replacement = expansion.onRequest(player, parameters);
+ identifier.setLength(0);
+ parameters.setLength(0);
+
+ if (invalid) {
+ builder.append(closure.head).append(identifierString);
+
+ if (identified) {
+ builder.append('_').append(parametersString);
+ }
+
+ if (hadSpace) {
+ builder.append(' ');
+ }
+ continue;
}
- if (replacement != null) {
- builder.append(replacement);
- } else {
- // Fallback: Restore original placeholder format
- builder.append(head).append(identifier);
- builder.append('_').append(parameters);
- builder.append(tail);
+ final PlaceholderExpansion placeholder = lookup.apply(lowercaseIdentifierString);
+ if (placeholder == null) {
+ builder.append(closure.head).append(identifierString);
+
+ if (identified) {
+ builder.append('_');
+ }
+
+ builder.append(parametersString).append(closure.tail);
+ continue;
}
- cursor = endPlaceholder + 1;
- startPlaceholder = text.indexOf(head, cursor);
+ final String replacement = placeholder.onRequest(player, parametersString);
+ if (replacement == null) {
+ builder.append(closure.head).append(identifierString);
- } while (startPlaceholder != -1);
+ if (identified) {
+ builder.append('_');
+ }
- if (cursor < length) {
- builder.append(text, cursor, length);
+ builder.append(parametersString).append(closure.tail);
+ continue;
+ }
+
+ builder.append(replacement);
}
return builder.toString();
}
-}
+}
\ No newline at end of file