2025-11-24 22:52:51 +03:00

547 lines
26 KiB
Java

/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* com.google.common.base.Strings
* com.google.common.collect.ImmutableList
* com.google.common.collect.Lists
* com.mojang.brigadier.CommandDispatcher
* com.mojang.brigadier.Message
* com.mojang.brigadier.ParseResults
* com.mojang.brigadier.StringReader
* com.mojang.brigadier.context.CommandContextBuilder
* com.mojang.brigadier.context.ParsedArgument
* com.mojang.brigadier.context.SuggestionContext
* com.mojang.brigadier.exceptions.CommandSyntaxException
* com.mojang.brigadier.suggestion.Suggestion
* com.mojang.brigadier.suggestion.Suggestions
* com.mojang.brigadier.suggestion.SuggestionsBuilder
* com.mojang.brigadier.tree.LiteralCommandNode
* org.jspecify.annotations.Nullable
*/
package net.minecraft.client.gui.components;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.mojang.blaze3d.platform.cursor.CursorTypes;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.Message;
import com.mojang.brigadier.ParseResults;
import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.ParsedArgument;
import com.mojang.brigadier.context.SuggestionContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestion;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.tree.LiteralCommandNode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.input.KeyEvent;
import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.client.multiplayer.ClientSuggestionProvider;
import net.minecraft.client.renderer.Rect2i;
import net.minecraft.commands.Commands;
import net.minecraft.commands.SharedSuggestionProvider;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentUtils;
import net.minecraft.network.chat.Style;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.util.Mth;
import net.minecraft.world.phys.Vec2;
import org.jspecify.annotations.Nullable;
public class CommandSuggestions {
private static final Pattern WHITESPACE_PATTERN = Pattern.compile("(\\s+)");
private static final Style UNPARSED_STYLE = Style.EMPTY.withColor(ChatFormatting.RED);
private static final Style LITERAL_STYLE = Style.EMPTY.withColor(ChatFormatting.GRAY);
private static final List<Style> ARGUMENT_STYLES = (List)Stream.of(ChatFormatting.AQUA, ChatFormatting.YELLOW, ChatFormatting.GREEN, ChatFormatting.LIGHT_PURPLE, ChatFormatting.GOLD).map(Style.EMPTY::withColor).collect(ImmutableList.toImmutableList());
private final Minecraft minecraft;
private final Screen screen;
private final EditBox input;
private final Font font;
private final boolean commandsOnly;
private final boolean onlyShowIfCursorPastError;
private final int lineStartOffset;
private final int suggestionLineLimit;
private final boolean anchorToBottom;
private final int fillColor;
private final List<FormattedCharSequence> commandUsage = Lists.newArrayList();
private int commandUsagePosition;
private int commandUsageWidth;
private @Nullable ParseResults<ClientSuggestionProvider> currentParse;
private @Nullable CompletableFuture<Suggestions> pendingSuggestions;
private @Nullable SuggestionsList suggestions;
private boolean allowSuggestions;
private boolean keepSuggestions;
private boolean allowHiding = true;
public CommandSuggestions(Minecraft minecraft, Screen screen, EditBox input, Font font, boolean commandsOnly, boolean onlyShowIfCursorPastError, int lineStartOffset, int suggestionLineLimit, boolean anchorToBottom, int fillColor) {
this.minecraft = minecraft;
this.screen = screen;
this.input = input;
this.font = font;
this.commandsOnly = commandsOnly;
this.onlyShowIfCursorPastError = onlyShowIfCursorPastError;
this.lineStartOffset = lineStartOffset;
this.suggestionLineLimit = suggestionLineLimit;
this.anchorToBottom = anchorToBottom;
this.fillColor = fillColor;
input.addFormatter(this::formatChat);
}
public void setAllowSuggestions(boolean allowSuggestions) {
this.allowSuggestions = allowSuggestions;
if (!allowSuggestions) {
this.suggestions = null;
}
}
public void setAllowHiding(boolean allowHiding) {
this.allowHiding = allowHiding;
}
public boolean keyPressed(KeyEvent event) {
boolean isVisible;
boolean bl = isVisible = this.suggestions != null;
if (isVisible && this.suggestions.keyPressed(event)) {
return true;
}
if (this.screen.getFocused() == this.input && event.isCycleFocus() && (!this.allowHiding || isVisible)) {
this.showSuggestions(true);
return true;
}
return false;
}
public boolean mouseScrolled(double scroll) {
return this.suggestions != null && this.suggestions.mouseScrolled(Mth.clamp(scroll, -1.0, 1.0));
}
public boolean mouseClicked(MouseButtonEvent event) {
return this.suggestions != null && this.suggestions.mouseClicked((int)event.x(), (int)event.y());
}
public void showSuggestions(boolean immediateNarration) {
Suggestions suggestions;
if (this.pendingSuggestions != null && this.pendingSuggestions.isDone() && !(suggestions = this.pendingSuggestions.join()).isEmpty()) {
int maxSuggestionWidth = 0;
for (Suggestion suggestion : suggestions.getList()) {
maxSuggestionWidth = Math.max(maxSuggestionWidth, this.font.width(suggestion.getText()));
}
int x = Mth.clamp(this.input.getScreenX(suggestions.getRange().getStart()), 0, this.input.getScreenX(0) + this.input.getInnerWidth() - maxSuggestionWidth);
int y = this.anchorToBottom ? this.screen.height - 12 : 72;
this.suggestions = new SuggestionsList(x, y, maxSuggestionWidth, this.sortSuggestions(suggestions), immediateNarration);
}
}
public boolean isVisible() {
return this.suggestions != null;
}
public Component getUsageNarration() {
if (this.suggestions != null && this.suggestions.tabCycles) {
if (this.allowHiding) {
return Component.translatable("narration.suggestion.usage.cycle.hidable");
}
return Component.translatable("narration.suggestion.usage.cycle.fixed");
}
if (this.allowHiding) {
return Component.translatable("narration.suggestion.usage.fill.hidable");
}
return Component.translatable("narration.suggestion.usage.fill.fixed");
}
public void hide() {
this.suggestions = null;
}
private List<Suggestion> sortSuggestions(Suggestions suggestions) {
String partialCommand = this.input.getValue().substring(0, this.input.getCursorPosition());
int lastWordIndex = CommandSuggestions.getLastWordIndex(partialCommand);
String lastWord = partialCommand.substring(lastWordIndex).toLowerCase(Locale.ROOT);
ArrayList suggestionList = Lists.newArrayList();
ArrayList partial = Lists.newArrayList();
for (Suggestion suggestion : suggestions.getList()) {
if (suggestion.getText().startsWith(lastWord) || suggestion.getText().startsWith("minecraft:" + lastWord)) {
suggestionList.add(suggestion);
continue;
}
partial.add(suggestion);
}
suggestionList.addAll(partial);
return suggestionList;
}
public void updateCommandInfo() {
boolean startsWithSlash;
String command = this.input.getValue();
if (this.currentParse != null && !this.currentParse.getReader().getString().equals(command)) {
this.currentParse = null;
}
if (!this.keepSuggestions) {
this.input.setSuggestion(null);
this.suggestions = null;
}
this.commandUsage.clear();
StringReader reader = new StringReader(command);
boolean bl = startsWithSlash = reader.canRead() && reader.peek() == '/';
if (startsWithSlash) {
reader.skip();
}
boolean isCommand = this.commandsOnly || startsWithSlash;
int cursorPosition = this.input.getCursorPosition();
if (isCommand) {
int parseStart;
CommandDispatcher<ClientSuggestionProvider> commands = this.minecraft.player.connection.getCommands();
if (this.currentParse == null) {
this.currentParse = commands.parse(reader, (Object)this.minecraft.player.connection.getSuggestionsProvider());
}
int n = parseStart = this.onlyShowIfCursorPastError ? reader.getCursor() : 1;
if (!(cursorPosition < parseStart || this.suggestions != null && this.keepSuggestions)) {
this.pendingSuggestions = commands.getCompletionSuggestions(this.currentParse, cursorPosition);
this.pendingSuggestions.thenRun(() -> {
if (!this.pendingSuggestions.isDone()) {
return;
}
this.updateUsageInfo();
});
}
} else {
String partialCommand = command.substring(0, cursorPosition);
int lastWord = CommandSuggestions.getLastWordIndex(partialCommand);
Collection<String> nonCommandSuggestions = this.minecraft.player.connection.getSuggestionsProvider().getCustomTabSugggestions();
this.pendingSuggestions = SharedSuggestionProvider.suggest(nonCommandSuggestions, new SuggestionsBuilder(partialCommand, lastWord));
}
}
private static int getLastWordIndex(String text) {
if (Strings.isNullOrEmpty((String)text)) {
return 0;
}
int result = 0;
Matcher matcher = WHITESPACE_PATTERN.matcher(text);
while (matcher.find()) {
result = matcher.end();
}
return result;
}
private static FormattedCharSequence getExceptionMessage(CommandSyntaxException e) {
Component message = ComponentUtils.fromMessage(e.getRawMessage());
String context = e.getContext();
if (context == null) {
return message.getVisualOrderText();
}
return Component.translatable("command.context.parse_error", message, e.getCursor(), context).getVisualOrderText();
}
private void updateUsageInfo() {
boolean trailingCharacters = false;
if (this.input.getCursorPosition() == this.input.getValue().length()) {
if (this.pendingSuggestions.join().isEmpty() && !this.currentParse.getExceptions().isEmpty()) {
int literals = 0;
for (Map.Entry entry : this.currentParse.getExceptions().entrySet()) {
CommandSyntaxException exception = (CommandSyntaxException)entry.getValue();
if (exception.getType() == CommandSyntaxException.BUILT_IN_EXCEPTIONS.literalIncorrect()) {
++literals;
continue;
}
this.commandUsage.add(CommandSuggestions.getExceptionMessage(exception));
}
if (literals > 0) {
this.commandUsage.add(CommandSuggestions.getExceptionMessage(CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(this.currentParse.getReader())));
}
} else if (this.currentParse.getReader().canRead()) {
trailingCharacters = true;
}
}
this.commandUsagePosition = 0;
this.commandUsageWidth = this.screen.width;
if (this.commandUsage.isEmpty() && !this.fillNodeUsage(ChatFormatting.GRAY) && trailingCharacters) {
this.commandUsage.add(CommandSuggestions.getExceptionMessage(Commands.getParseException(this.currentParse)));
}
this.suggestions = null;
if (this.allowSuggestions && this.minecraft.options.autoSuggestions().get().booleanValue()) {
this.showSuggestions(false);
}
}
private boolean fillNodeUsage(ChatFormatting color) {
CommandContextBuilder rootContext = this.currentParse.getContext();
SuggestionContext suggestionContext = rootContext.findSuggestionContext(this.input.getCursorPosition());
Map usage = this.minecraft.player.connection.getCommands().getSmartUsage(suggestionContext.parent, (Object)this.minecraft.player.connection.getSuggestionsProvider());
ArrayList lines = Lists.newArrayList();
int longest = 0;
Style usageFormat = Style.EMPTY.withColor(color);
for (Map.Entry entry : usage.entrySet()) {
if (entry.getKey() instanceof LiteralCommandNode) continue;
lines.add(FormattedCharSequence.forward((String)entry.getValue(), usageFormat));
longest = Math.max(longest, this.font.width((String)entry.getValue()));
}
if (!lines.isEmpty()) {
this.commandUsage.addAll(lines);
this.commandUsagePosition = Mth.clamp(this.input.getScreenX(suggestionContext.startPos), 0, this.input.getScreenX(0) + this.input.getInnerWidth() - longest);
this.commandUsageWidth = longest;
return true;
}
return false;
}
private @Nullable FormattedCharSequence formatChat(String text, int offset) {
if (this.currentParse != null) {
return CommandSuggestions.formatText(this.currentParse, text, offset);
}
return null;
}
private static @Nullable String calculateSuggestionSuffix(String contents, String suggestion) {
if (suggestion.startsWith(contents)) {
return suggestion.substring(contents.length());
}
return null;
}
private static FormattedCharSequence formatText(ParseResults<ClientSuggestionProvider> currentParse, String text, int offset) {
int start;
ArrayList parts = Lists.newArrayList();
int unformattedStart = 0;
int nextColor = -1;
CommandContextBuilder context = currentParse.getContext().getLastChild();
for (ParsedArgument argument : context.getArguments().values()) {
int start2;
if (++nextColor >= ARGUMENT_STYLES.size()) {
nextColor = 0;
}
if ((start2 = Math.max(argument.getRange().getStart() - offset, 0)) >= text.length()) break;
int end = Math.min(argument.getRange().getEnd() - offset, text.length());
if (end <= 0) continue;
parts.add(FormattedCharSequence.forward(text.substring(unformattedStart, start2), LITERAL_STYLE));
parts.add(FormattedCharSequence.forward(text.substring(start2, end), ARGUMENT_STYLES.get(nextColor)));
unformattedStart = end;
}
if (currentParse.getReader().canRead() && (start = Math.max(currentParse.getReader().getCursor() - offset, 0)) < text.length()) {
int end = Math.min(start + currentParse.getReader().getRemainingLength(), text.length());
parts.add(FormattedCharSequence.forward(text.substring(unformattedStart, start), LITERAL_STYLE));
parts.add(FormattedCharSequence.forward(text.substring(start, end), UNPARSED_STYLE));
unformattedStart = end;
}
parts.add(FormattedCharSequence.forward(text.substring(unformattedStart), LITERAL_STYLE));
return FormattedCharSequence.composite(parts);
}
public void render(GuiGraphics graphics, int mouseX, int mouseY) {
if (!this.renderSuggestions(graphics, mouseX, mouseY)) {
this.renderUsage(graphics);
}
}
public boolean renderSuggestions(GuiGraphics graphics, int mouseX, int mouseY) {
if (this.suggestions != null) {
this.suggestions.render(graphics, mouseX, mouseY);
return true;
}
return false;
}
public void renderUsage(GuiGraphics graphics) {
int y = 0;
for (FormattedCharSequence line : this.commandUsage) {
int lineY = this.anchorToBottom ? this.screen.height - 14 - 13 - 12 * y : 72 + 12 * y;
graphics.fill(this.commandUsagePosition - 1, lineY, this.commandUsagePosition + this.commandUsageWidth + 1, lineY + 12, this.fillColor);
graphics.drawString(this.font, line, this.commandUsagePosition, lineY + 2, -1);
++y;
}
}
public Component getNarrationMessage() {
if (this.suggestions != null) {
return CommonComponents.NEW_LINE.copy().append(this.suggestions.getNarrationMessage());
}
return CommonComponents.EMPTY;
}
public class SuggestionsList {
private final Rect2i rect;
private final String originalContents;
private final List<Suggestion> suggestionList;
private int offset;
private int current;
private Vec2 lastMouse = Vec2.ZERO;
private boolean tabCycles;
private int lastNarratedEntry;
private SuggestionsList(int x, int y, int width, List<Suggestion> suggestionList, boolean immediateNarration) {
int listX = x - (CommandSuggestions.this.input.isBordered() ? 0 : 1);
int listY = CommandSuggestions.this.anchorToBottom ? y - 3 - Math.min(suggestionList.size(), CommandSuggestions.this.suggestionLineLimit) * 12 : y - (CommandSuggestions.this.input.isBordered() ? 1 : 0);
this.rect = new Rect2i(listX, listY, width + 1, Math.min(suggestionList.size(), CommandSuggestions.this.suggestionLineLimit) * 12);
this.originalContents = CommandSuggestions.this.input.getValue();
this.lastNarratedEntry = immediateNarration ? -1 : 0;
this.suggestionList = suggestionList;
this.select(0);
}
public void render(GuiGraphics graphics, int mouseX, int mouseY) {
Message tooltip;
boolean mouseMoved;
int limit = Math.min(this.suggestionList.size(), CommandSuggestions.this.suggestionLineLimit);
int unselectedColor = -5592406;
boolean hasPrevious = this.offset > 0;
boolean hasNext = this.suggestionList.size() > this.offset + limit;
boolean limited = hasPrevious || hasNext;
boolean bl = mouseMoved = this.lastMouse.x != (float)mouseX || this.lastMouse.y != (float)mouseY;
if (mouseMoved) {
this.lastMouse = new Vec2(mouseX, mouseY);
}
if (limited) {
int x;
graphics.fill(this.rect.getX(), this.rect.getY() - 1, this.rect.getX() + this.rect.getWidth(), this.rect.getY(), CommandSuggestions.this.fillColor);
graphics.fill(this.rect.getX(), this.rect.getY() + this.rect.getHeight(), this.rect.getX() + this.rect.getWidth(), this.rect.getY() + this.rect.getHeight() + 1, CommandSuggestions.this.fillColor);
if (hasPrevious) {
for (x = 0; x < this.rect.getWidth(); ++x) {
if (x % 2 != 0) continue;
graphics.fill(this.rect.getX() + x, this.rect.getY() - 1, this.rect.getX() + x + 1, this.rect.getY(), -1);
}
}
if (hasNext) {
for (x = 0; x < this.rect.getWidth(); ++x) {
if (x % 2 != 0) continue;
graphics.fill(this.rect.getX() + x, this.rect.getY() + this.rect.getHeight(), this.rect.getX() + x + 1, this.rect.getY() + this.rect.getHeight() + 1, -1);
}
}
}
boolean hovered = false;
for (int i = 0; i < limit; ++i) {
Suggestion suggestion = this.suggestionList.get(i + this.offset);
graphics.fill(this.rect.getX(), this.rect.getY() + 12 * i, this.rect.getX() + this.rect.getWidth(), this.rect.getY() + 12 * i + 12, CommandSuggestions.this.fillColor);
if (mouseX > this.rect.getX() && mouseX < this.rect.getX() + this.rect.getWidth() && mouseY > this.rect.getY() + 12 * i && mouseY < this.rect.getY() + 12 * i + 12) {
if (mouseMoved) {
this.select(i + this.offset);
}
hovered = true;
}
graphics.drawString(CommandSuggestions.this.font, suggestion.getText(), this.rect.getX() + 1, this.rect.getY() + 2 + 12 * i, i + this.offset == this.current ? -256 : -5592406);
}
if (hovered && (tooltip = this.suggestionList.get(this.current).getTooltip()) != null) {
graphics.setTooltipForNextFrame(CommandSuggestions.this.font, ComponentUtils.fromMessage(tooltip), mouseX, mouseY);
}
if (this.rect.contains(mouseX, mouseY)) {
graphics.requestCursor(CursorTypes.POINTING_HAND);
}
}
public boolean mouseClicked(int x, int y) {
if (!this.rect.contains(x, y)) {
return false;
}
int line = (y - this.rect.getY()) / 12 + this.offset;
if (line >= 0 && line < this.suggestionList.size()) {
this.select(line);
this.useSuggestion();
}
return true;
}
public boolean mouseScrolled(double scroll) {
int mouseY;
int mouseX = (int)CommandSuggestions.this.minecraft.mouseHandler.getScaledXPos(CommandSuggestions.this.minecraft.getWindow());
if (this.rect.contains(mouseX, mouseY = (int)CommandSuggestions.this.minecraft.mouseHandler.getScaledYPos(CommandSuggestions.this.minecraft.getWindow()))) {
this.offset = Mth.clamp((int)((double)this.offset - scroll), 0, Math.max(this.suggestionList.size() - CommandSuggestions.this.suggestionLineLimit, 0));
return true;
}
return false;
}
public boolean keyPressed(KeyEvent event) {
if (event.isUp()) {
this.cycle(-1);
this.tabCycles = false;
return true;
}
if (event.isDown()) {
this.cycle(1);
this.tabCycles = false;
return true;
}
if (event.isCycleFocus()) {
if (this.tabCycles) {
this.cycle(event.hasShiftDown() ? -1 : 1);
}
this.useSuggestion();
return true;
}
if (event.isEscape()) {
CommandSuggestions.this.hide();
CommandSuggestions.this.input.setSuggestion(null);
return true;
}
return false;
}
public void cycle(int direction) {
this.select(this.current + direction);
int first = this.offset;
int last = this.offset + CommandSuggestions.this.suggestionLineLimit - 1;
if (this.current < first) {
this.offset = Mth.clamp(this.current, 0, Math.max(this.suggestionList.size() - CommandSuggestions.this.suggestionLineLimit, 0));
} else if (this.current > last) {
this.offset = Mth.clamp(this.current + CommandSuggestions.this.lineStartOffset - CommandSuggestions.this.suggestionLineLimit, 0, Math.max(this.suggestionList.size() - CommandSuggestions.this.suggestionLineLimit, 0));
}
}
public void select(int index) {
this.current = index;
if (this.current < 0) {
this.current += this.suggestionList.size();
}
if (this.current >= this.suggestionList.size()) {
this.current -= this.suggestionList.size();
}
Suggestion suggestion = this.suggestionList.get(this.current);
CommandSuggestions.this.input.setSuggestion(CommandSuggestions.calculateSuggestionSuffix(CommandSuggestions.this.input.getValue(), suggestion.apply(this.originalContents)));
if (this.lastNarratedEntry != this.current) {
CommandSuggestions.this.minecraft.getNarrator().saySystemNow(this.getNarrationMessage());
}
}
public void useSuggestion() {
Suggestion suggestion = this.suggestionList.get(this.current);
CommandSuggestions.this.keepSuggestions = true;
CommandSuggestions.this.input.setValue(suggestion.apply(this.originalContents));
int end = suggestion.getRange().getStart() + suggestion.getText().length();
CommandSuggestions.this.input.setCursorPosition(end);
CommandSuggestions.this.input.setHighlightPos(end);
this.select(this.current);
CommandSuggestions.this.keepSuggestions = false;
this.tabCycles = true;
}
private Component getNarrationMessage() {
this.lastNarratedEntry = this.current;
Suggestion suggestion = this.suggestionList.get(this.current);
Message tooltip = suggestion.getTooltip();
if (tooltip != null) {
return Component.translatable("narration.suggestion.tooltip", this.current + 1, this.suggestionList.size(), suggestion.getText(), Component.translationArg(tooltip));
}
return Component.translatable("narration.suggestion", this.current + 1, this.suggestionList.size(), suggestion.getText());
}
}
}