-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
16 changed files
with
319 additions
and
215 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
package ink.glowing.text; | ||
|
||
import ink.glowing.text.modifier.ClickModifier; | ||
import ink.glowing.text.modifier.ColorModifier; | ||
import ink.glowing.text.modifier.FontModifier; | ||
import ink.glowing.text.modifier.HoverModifier; | ||
import ink.glowing.text.modifier.ModifiersResolver; | ||
import ink.glowing.text.utils.Utils; | ||
import net.kyori.adventure.text.Component; | ||
import net.kyori.adventure.text.ComponentIteratorType; | ||
import net.kyori.adventure.text.KeybindComponent; | ||
import net.kyori.adventure.text.ScoreComponent; | ||
import net.kyori.adventure.text.SelectorComponent; | ||
import net.kyori.adventure.text.TextComponent; | ||
import net.kyori.adventure.text.TranslatableComponent; | ||
import net.kyori.adventure.text.serializer.ComponentSerializer; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.regex.Pattern; | ||
|
||
import static ink.glowing.text.utils.Utils.SECTION; | ||
|
||
public enum InkyMessage implements ComponentSerializer<Component, Component, String> { | ||
INSTANCE; | ||
|
||
private static final Pattern ESCAPE_PATTERN = Pattern.compile("[&\\]()]"); | ||
private static final Pattern UNESCAPE_PATTERN = Pattern.compile("\\\\([&\\]()])"); | ||
|
||
private static final ModifiersResolver DEFAULT_MODIFIERS = new ModifiersResolver( | ||
ColorModifier.INSTANCE, | ||
HoverModifier.INSTANCE, | ||
ClickModifier.INSTANCE, | ||
FontModifier.INSTANCE | ||
); | ||
|
||
public @NotNull Component deserialize(@NotNull String input, @NotNull ModifiersResolver modsResolver) { | ||
List<RichText> richTexts = new ArrayList<>(); | ||
String oldText = input; | ||
String newText = parseRich(input, richTexts, modsResolver); | ||
while (!newText.equals(oldText)) { | ||
oldText = newText; | ||
newText = parseRich(oldText, richTexts, modsResolver); | ||
} | ||
return new RichText(newText, List.of()).render(richTexts).component().compact(); | ||
} | ||
|
||
private static @NotNull String parseRich(@NotNull String input, @NotNull List<RichText> richTexts, @NotNull ModifiersResolver modsResolver) { | ||
int closeIndex = input.indexOf("]("); | ||
while (Utils.isEscaped(input, closeIndex)) closeIndex = input.indexOf("](", closeIndex + 1); | ||
if (closeIndex == -1) return input; | ||
int startIndex = -1; | ||
for (int index = closeIndex - 1; index > 0; index--) { | ||
if (input.charAt(index) == '[' && input.charAt(index - 1) == '&' && !Utils.isEscaped(input, index - 1)) { | ||
startIndex = index - 1; | ||
break; | ||
} | ||
} | ||
if (startIndex == -1) return input; | ||
int modStart = closeIndex + 1; | ||
int modEnd = -1; | ||
for (int index = modStart + 1; index < input.length(); index++) { | ||
char ch = input.charAt(index); | ||
if (ch == ')' && !Utils.isEscaped(input, index)) { | ||
if (index + 1 == input.length() || input.charAt(index + 1) != '(') { | ||
modEnd = index; | ||
} | ||
} | ||
} | ||
++modEnd; | ||
richTexts.add(new RichText( | ||
input.substring(startIndex + 2, closeIndex), | ||
modsResolver.parseModifiers(input.substring(modStart, modEnd)) | ||
)); | ||
return input.substring(0, startIndex) + SECTION + (richTexts.size() - 1) + SECTION + input.substring(modEnd); | ||
} | ||
|
||
public static @NotNull String escapeAll(@NotNull String text) { | ||
return ESCAPE_PATTERN.matcher(text).replaceAll(result -> "\\\\" + result.group()); | ||
} | ||
|
||
public static @NotNull String unescapeAll(@NotNull String text) { | ||
return UNESCAPE_PATTERN.matcher(text).replaceAll(result -> result.group(1)); | ||
} | ||
|
||
public static @NotNull String escapeSingularSlash(@NotNull String text) { | ||
StringBuilder builder = new StringBuilder(text.length()); | ||
boolean escaped = false; | ||
for (int i = 0; i < text.length(); i++) { | ||
char ch = text.charAt(i); | ||
if (escaped) { | ||
escaped = false; | ||
if ("&]()\\".indexOf(ch) == -1) { | ||
builder.append("\\"); | ||
} | ||
} else if (ch == '\\') { | ||
escaped = true; | ||
} | ||
builder.append(ch); | ||
} | ||
if (escaped) builder.append('\\'); | ||
return builder.toString(); | ||
} | ||
|
||
@Override | ||
public @NotNull Component deserialize(@NotNull String text) { | ||
return deserialize(text, DEFAULT_MODIFIERS); | ||
} | ||
|
||
@Override | ||
public @NotNull String serialize(@NotNull Component component) { | ||
StringBuilder builder = new StringBuilder(); | ||
for (var child : component.iterable(ComponentIteratorType.BREADTH_FIRST)) { | ||
serialize(builder, child); | ||
} | ||
return builder.toString(); | ||
} | ||
|
||
private void serialize(@NotNull StringBuilder builder, @NotNull Component component) { | ||
if (component instanceof TextComponent text) { | ||
builder.append(text.content()); | ||
} else if (component instanceof TranslatableComponent translatable) { | ||
builder.append(translatable.key()); | ||
} else if (component instanceof KeybindComponent keybind) { | ||
builder.append(keybind.keybind()); | ||
} else if (component instanceof ScoreComponent score) { | ||
builder.append(score.objective()); | ||
} else if (component instanceof SelectorComponent selector) { | ||
builder.append(selector.pattern()); | ||
} else { | ||
builder.append('?'); | ||
} | ||
} | ||
} |
44 changes: 27 additions & 17 deletions
44
.../ink/glowing/adventure/text/RichText.java → src/main/java/ink/glowing/text/RichText.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,72 +1,82 @@ | ||
package ink.glowing.adventure.text; | ||
package ink.glowing.text; | ||
|
||
import ink.glowing.adventure.modifier.Modifier; | ||
import ink.glowing.adventure.utils.AdventureUtils; | ||
import ink.glowing.text.modifier.Modifier; | ||
import ink.glowing.text.utils.Utils; | ||
import net.kyori.adventure.text.Component; | ||
import net.kyori.adventure.text.format.Style; | ||
import net.kyori.adventure.text.format.TextColor; | ||
import org.jetbrains.annotations.NotNull; | ||
|
||
import java.util.List; | ||
|
||
import static ink.glowing.adventure.utils.AdventureUtils.SPECIAL_CHAR; | ||
import static ink.glowing.text.utils.Utils.SECTION_CHAR; | ||
import static net.kyori.adventure.text.Component.text; | ||
|
||
public class RichText { | ||
public static final RichText EMPTY = new RichText("", List.of()) { | ||
@Override | ||
public @NotNull IncludedText render(List<RichText> richTexts) { | ||
return new IncludedText(Component.empty(), Style.empty()); | ||
public @NotNull RichText.Included render(List<RichText> richTexts) { | ||
return new Included(Component.empty(), Style.empty()); | ||
} | ||
}; | ||
|
||
private final String text; | ||
private final boolean hasSlashes; | ||
private final List<Modifier.Prepared> modifiers; | ||
|
||
public RichText(@NotNull String text, @NotNull List<Modifier.Prepared> modifiers) { | ||
this.text = text; | ||
this.hasSlashes = text.indexOf('\\') != -1; | ||
this.modifiers = modifiers; | ||
} | ||
|
||
public @NotNull IncludedText render(List<RichText> richTexts) { | ||
public @NotNull RichText.Included render(List<RichText> richTexts) { | ||
Style lastStyle = Style.empty(); | ||
Component result = Component.empty(); | ||
int lastAppend = 0; | ||
for (int i = 0; i < text.length(); i++) { | ||
char ch = text.charAt(i); | ||
if (ch == SPECIAL_CHAR) { | ||
if (ch == SECTION_CHAR) { | ||
result = result.append(text(text.substring(lastAppend, i)).style(lastStyle)); | ||
int start = i + 1; | ||
//noinspection StatementWithEmptyBody | ||
while (text.charAt(++i) != SPECIAL_CHAR); | ||
IncludedText included = richTexts.get(Integer.parseInt(text.substring(start, i))).render(richTexts); | ||
while (text.charAt(++i) != SECTION_CHAR); | ||
Included included = richTexts.get(Integer.parseInt(text.substring(start, i))).render(richTexts); | ||
result = result.append(included.component()); | ||
if (!included.lastStyle().isEmpty()) lastStyle = included.lastStyle(); | ||
lastAppend = i + 1; | ||
} else if (ch == '&') { | ||
if (i + 1 == text.length()) continue; | ||
if (i + 1 == text.length() || Utils.isEscaped(text, i)) continue; | ||
char styleChar = text.charAt(i + 1); | ||
if (styleChar == '#' && i + 8 < text.length()) { | ||
String colorStr = text.substring(i + 1, i + 8); | ||
if (!AdventureUtils.isHexColor(colorStr)) continue; | ||
result = result.append(text(text.substring(lastAppend, i)).style(lastStyle)); | ||
if (!Utils.isHexColor(colorStr)) continue; | ||
result = makeComponent(result, lastAppend, i, lastStyle); | ||
lastStyle = lastStyle.color(TextColor.fromHexString(colorStr)); | ||
lastAppend = (i += 7) + 1; | ||
} else { | ||
Style newStyle = AdventureUtils.mergeLegacyStyle(styleChar, lastStyle); | ||
Style newStyle = Utils.mergeLegacyStyle(styleChar, lastStyle); | ||
if (newStyle == null) continue; | ||
result = result.append(text(text.substring(lastAppend, i)).style(lastStyle)); | ||
result = makeComponent(result, lastAppend, i, lastStyle); | ||
lastStyle = newStyle; | ||
lastAppend = (++i) + 1; | ||
} | ||
} | ||
} | ||
if (lastAppend < text.length()) { | ||
result = result.append(text(text.substring(lastAppend)).style(lastStyle)); | ||
result = makeComponent(result, lastAppend, text.length(), lastStyle); | ||
} | ||
for (var rawMod : modifiers) { | ||
result = rawMod.modify(result, richTexts); | ||
} | ||
return new IncludedText(result, lastStyle); | ||
return new Included(result, lastStyle); | ||
} | ||
|
||
private @NotNull Component makeComponent(Component comp, int start, int end, Style style) { | ||
String substring = text.substring(start, end); | ||
if (hasSlashes) substring = InkyMessage.unescapeAll(substring); | ||
return comp.append(text(substring).style(style)); | ||
} | ||
|
||
private record Included(@NotNull Component component, @NotNull Style lastStyle) {} | ||
} |
Oops, something went wrong.