CommandSuggestions.java

net.minecraft.client.gui.components.CommandSuggestions

信息

  • 全限定名:net.minecraft.client.gui.components.CommandSuggestions
  • 类型:public class
  • 包:net.minecraft.client.gui.components
  • 源码路径:src/main/java/net/minecraft/client/gui/components/CommandSuggestions.java
  • 起始行号:L57
  • 职责:

    TODO

字段/常量

  • WHITESPACE_PATTERN

    • 类型: Pattern
    • 修饰符: private static final
    • 源码定位: L58
    • 说明:

      TODO

  • UNPARSED_STYLE

    • 类型: Style
    • 修饰符: private static final
    • 源码定位: L59
    • 说明:

      TODO

  • LITERAL_STYLE

    • 类型: Style
    • 修饰符: private static final
    • 源码定位: L60
    • 说明:

      TODO

  • USAGE_FORMAT

    • 类型: Style
    • 修饰符: public static final
    • 源码定位: L61
    • 说明:

      TODO

  • ARGUMENT_STYLES

    • 类型: List<Style>
    • 修饰符: private static final
    • 源码定位: L62
    • 说明:

      TODO

  • LINE_HEIGHT

    • 类型: int
    • 修饰符: public static final
    • 源码定位: L67
    • 说明:

      TODO

  • USAGE_OFFSET_FROM_BOTTOM

    • 类型: int
    • 修饰符: public static final
    • 源码定位: L68
    • 说明:

      TODO

  • COMMANDS_NOT_ALLOWED_TEXT

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L69
    • 说明:

      TODO

  • MESSAGES_NOT_ALLOWED_TEXT

    • 类型: Component
    • 修饰符: private static final
    • 源码定位: L70
    • 说明:

      TODO

  • minecraft

    • 类型: Minecraft
    • 修饰符: private final
    • 源码定位: L71
    • 说明:

      TODO

  • screen

    • 类型: Screen
    • 修饰符: private final
    • 源码定位: L72
    • 说明:

      TODO

  • input

    • 类型: EditBox
    • 修饰符: private final
    • 源码定位: L73
    • 说明:

      TODO

  • font

    • 类型: Font
    • 修饰符: private final
    • 源码定位: L74
    • 说明:

      TODO

  • commandsOnly

    • 类型: boolean
    • 修饰符: private final
    • 源码定位: L75
    • 说明:

      TODO

  • onlyShowIfCursorPastError

    • 类型: boolean
    • 修饰符: private final
    • 源码定位: L76
    • 说明:

      TODO

  • lineStartOffset

    • 类型: int
    • 修饰符: private final
    • 源码定位: L77
    • 说明:

      TODO

  • suggestionLineLimit

    • 类型: int
    • 修饰符: private final
    • 源码定位: L78
    • 说明:

      TODO

  • anchorToBottom

    • 类型: boolean
    • 修饰符: private final
    • 源码定位: L79
    • 说明:

      TODO

  • fillColor

    • 类型: int
    • 修饰符: private final
    • 源码定位: L80
    • 说明:

      TODO

  • commandUsage

    • 类型: List<FormattedCharSequence>
    • 修饰符: private final
    • 源码定位: L81
    • 说明:

      TODO

  • commandUsagePosition

    • 类型: int
    • 修饰符: private
    • 源码定位: L82
    • 说明:

      TODO

  • commandUsageWidth

    • 类型: int
    • 修饰符: private
    • 源码定位: L83
    • 说明:

      TODO

  • currentParse

    • 类型: ParseResults<ClientSuggestionProvider>
    • 修饰符: private
    • 源码定位: L84
    • 说明:

      TODO

  • pendingSuggestions

    • 类型: CompletableFuture<Suggestions>
    • 修饰符: private
    • 源码定位: L85
    • 说明:

      TODO

  • suggestions

    • 类型: CommandSuggestions.SuggestionsList
    • 修饰符: private
    • 源码定位: L86
    • 说明:

      TODO

  • currentParseIsCommand

    • 类型: boolean
    • 修饰符: private
    • 源码定位: L87
    • 说明:

      TODO

  • currentParseIsMessage

    • 类型: boolean
    • 修饰符: private
    • 源码定位: L88
    • 说明:

      TODO

  • allowSuggestions

    • 类型: boolean
    • 修饰符: private
    • 源码定位: L89
    • 说明:

      TODO

  • keepSuggestions

    • 类型: boolean
    • 修饰符: private
    • 源码定位: L90
    • 说明:

      TODO

  • allowHiding

    • 类型: boolean
    • 修饰符: private
    • 源码定位: L91
    • 说明:

      TODO

  • messagesAllowed

    • 类型: boolean
    • 修饰符: private
    • 源码定位: L92
    • 说明:

      TODO

  • commandsAllowed

    • 类型: boolean
    • 修饰符: private
    • 源码定位: L93
    • 说明:

      TODO

内部类/嵌套类型

  • net.minecraft.client.gui.components.CommandSuggestions.Visitor

    • 类型: class
    • 修饰符: package-private
    • 源码定位: L272
    • 说明:

      TODO

  • net.minecraft.client.gui.components.CommandSuggestions.SuggestionsList

    • 类型: class
    • 修饰符: public
    • 源码定位: L482
    • 说明:

      TODO

构造器

public CommandSuggestions(Minecraft minecraft, Screen screen, EditBox input, Font font, boolean commandsOnly, boolean onlyShowIfCursorPastError, int lineStartOffset, int suggestionLineLimit, boolean anchorToBottom, int fillColor) @ L95

  • 构造器名:CommandSuggestions
  • 源码定位:L95
  • 修饰符:public

参数:

  • minecraft: Minecraft
  • screen: Screen
  • input: EditBox
  • font: Font
  • commandsOnly: boolean
  • onlyShowIfCursorPastError: boolean
  • lineStartOffset: int
  • suggestionLineLimit: int
  • anchorToBottom: boolean
  • fillColor: int

说明:

TODO

方法

下面的方法块按源码顺序生成。

public void setAllowSuggestions(boolean allowSuggestions) @ L120

  • 方法名:setAllowSuggestions
  • 源码定位:L120
  • 返回类型:void
  • 修饰符:public

参数:

  • allowSuggestions: boolean

说明:

TODO

public void setAllowHiding(boolean allowHiding) @ L127

  • 方法名:setAllowHiding
  • 源码定位:L127
  • 返回类型:void
  • 修饰符:public

参数:

  • allowHiding: boolean

说明:

TODO

public void setRestrictions(boolean messagesAllowed, boolean commandsAllowed) @ L131

  • 方法名:setRestrictions
  • 源码定位:L131
  • 返回类型:void
  • 修饰符:public

参数:

  • messagesAllowed: boolean
  • commandsAllowed: boolean

说明:

TODO

public boolean keyPressed(KeyEvent event) @ L136

  • 方法名:keyPressed
  • 源码定位:L136
  • 返回类型:boolean
  • 修饰符:public

参数:

  • event: KeyEvent

说明:

TODO

public boolean mouseScrolled(double scroll) @ L148

  • 方法名:mouseScrolled
  • 源码定位:L148
  • 返回类型:boolean
  • 修饰符:public

参数:

  • scroll: double

说明:

TODO

public boolean mouseClicked(MouseButtonEvent event) @ L152

  • 方法名:mouseClicked
  • 源码定位:L152
  • 返回类型:boolean
  • 修饰符:public

参数:

  • event: MouseButtonEvent

说明:

TODO

public void showSuggestions(boolean immediateNarration) @ L156

  • 方法名:showSuggestions
  • 源码定位:L156
  • 返回类型:void
  • 修饰符:public

参数:

  • immediateNarration: boolean

说明:

TODO

public boolean isVisible() @ L175

  • 方法名:isVisible
  • 源码定位:L175
  • 返回类型:boolean
  • 修饰符:public

参数:

说明:

TODO

public Component getUsageNarration() @ L179

  • 方法名:getUsageNarration
  • 源码定位:L179
  • 返回类型:Component
  • 修饰符:public

参数:

说明:

TODO

public void hide() @ L191

  • 方法名:hide
  • 源码定位:L191
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

private List<Suggestion> sortSuggestions(Suggestions suggestions) @ L195

  • 方法名:sortSuggestions
  • 源码定位:L195
  • 返回类型:List
  • 修饰符:private

参数:

  • suggestions: Suggestions

说明:

TODO

public void updateCommandInfo() @ L214

  • 方法名:updateCommandInfo
  • 源码定位:L214
  • 返回类型:void
  • 修饰符:public

参数:

说明:

TODO

private static boolean hasMessageArguments(ParseResults<ClientSuggestionProvider> parseResults) @ L270

  • 方法名:hasMessageArguments
  • 源码定位:L270
  • 返回类型:boolean
  • 修饰符:private static

参数:

  • parseResults: ParseResults

说明:

TODO

private static int getLastWordIndex(String text) @ L290

  • 方法名:getLastWordIndex
  • 源码定位:L290
  • 返回类型:int
  • 修饰符:private static

参数:

  • text: String

说明:

TODO

private static FormattedCharSequence getExceptionMessage(CommandSyntaxException e) @ L305

  • 方法名:getExceptionMessage
  • 源码定位:L305
  • 返回类型:FormattedCharSequence
  • 修饰符:private static

参数:

  • e: CommandSyntaxException

说明:

TODO

private void updateUsageInfo(ParseResults<ClientSuggestionProvider> currentParse, Suggestions suggestions) @ L313

  • 方法名:updateUsageInfo
  • 源码定位:L313
  • 返回类型:void
  • 修饰符:private

参数:

  • currentParse: ParseResults
  • suggestions: Suggestions

说明:

TODO

private List<FormattedCharSequence> fillNodeUsage(SuggestionContext<ClientSuggestionProvider> suggestionContext, Style usageFormat) @ L374

  • 方法名:fillNodeUsage
  • 源码定位:L374
  • 返回类型:List
  • 修饰符:private

参数:

  • suggestionContext: SuggestionContext
  • usageFormat: Style

说明:

TODO

private void recomputeUsageBoxWidth() @ L391

  • 方法名:recomputeUsageBoxWidth
  • 源码定位:L391
  • 返回类型:void
  • 修饰符:private

参数:

说明:

TODO

private FormattedCharSequence formatChat(String text, int offset) @ L401

  • 方法名:formatChat
  • 源码定位:L401
  • 返回类型:FormattedCharSequence
  • 修饰符:private

参数:

  • text: String
  • offset: int

说明:

TODO

private static String calculateSuggestionSuffix(String contents, String suggestion) @ L405

  • 方法名:calculateSuggestionSuffix
  • 源码定位:L405
  • 返回类型:String
  • 修饰符:private static

参数:

  • contents: String
  • suggestion: String

说明:

TODO

private static FormattedCharSequence formatText(ParseResults<ClientSuggestionProvider> currentParse, String text, int offset) @ L409

  • 方法名:formatText
  • 源码定位:L409
  • 返回类型:FormattedCharSequence
  • 修饰符:private static

参数:

  • currentParse: ParseResults
  • text: String
  • offset: int

说明:

TODO

public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY) @ L447

  • 方法名:extractRenderState
  • 源码定位:L447
  • 返回类型:void
  • 修饰符:public

参数:

  • graphics: GuiGraphicsExtractor
  • mouseX: int
  • mouseY: int

说明:

TODO

public boolean extractSuggestions(GuiGraphicsExtractor graphics, int mouseX, int mouseY) @ L453

  • 方法名:extractSuggestions
  • 源码定位:L453
  • 返回类型:boolean
  • 修饰符:public

参数:

  • graphics: GuiGraphicsExtractor
  • mouseX: int
  • mouseY: int

说明:

TODO

public void extractUsage(GuiGraphicsExtractor graphics) @ L462

  • 方法名:extractUsage
  • 源码定位:L462
  • 返回类型:void
  • 修饰符:public

参数:

  • graphics: GuiGraphicsExtractor

说明:

TODO

public Component getNarrationMessage() @ L473

  • 方法名:getNarrationMessage
  • 源码定位:L473
  • 返回类型:Component
  • 修饰符:public

参数:

说明:

TODO

public boolean hasAllowedInput() @ L477

  • 方法名:hasAllowedInput
  • 源码定位:L477
  • 返回类型:boolean
  • 修饰符:public

参数:

说明:

TODO

代码

@OnlyIn(Dist.CLIENT)
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);
    public static final Style USAGE_FORMAT = Style.EMPTY.withColor(ChatFormatting.GRAY);
    private static final List<Style> ARGUMENT_STYLES = Stream.of(
            ChatFormatting.AQUA, ChatFormatting.YELLOW, ChatFormatting.GREEN, ChatFormatting.LIGHT_PURPLE, ChatFormatting.GOLD
        )
        .map(Style.EMPTY::withColor)
        .collect(ImmutableList.toImmutableList());
    public static final int LINE_HEIGHT = 12;
    public static final int USAGE_OFFSET_FROM_BOTTOM = 27;
    private static final Component COMMANDS_NOT_ALLOWED_TEXT = Component.translatable("chat_screen.commands_not_allowed").withStyle(ChatFormatting.RED);
    private static final Component MESSAGES_NOT_ALLOWED_TEXT = Component.translatable("chat_screen.messages_not_allowed").withStyle(ChatFormatting.RED);
    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 CommandSuggestions.@Nullable SuggestionsList suggestions;
    private boolean currentParseIsCommand;
    private boolean currentParseIsMessage;
    private boolean allowSuggestions;
    private boolean keepSuggestions;
    private boolean allowHiding = true;
    private boolean messagesAllowed = true;
    private boolean commandsAllowed = 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 void setRestrictions(boolean messagesAllowed, boolean commandsAllowed) {
        this.messagesAllowed = messagesAllowed;
        this.commandsAllowed = commandsAllowed;
    }
 
    public boolean keyPressed(KeyEvent event) {
        boolean isVisible = this.suggestions != null;
        if (isVisible && this.suggestions.keyPressed(event)) {
            return true;
        } else if (this.screen.getFocused() != this.input || !event.isCycleFocus() || this.allowHiding && !isVisible) {
            return false;
        } else {
            this.showSuggestions(true);
            return true;
        }
    }
 
    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) {
        if (this.pendingSuggestions != null && this.pendingSuggestions.isDone()) {
            Suggestions suggestions = this.pendingSuggestions.join();
            if (!suggestions.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 CommandSuggestions.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) {
            return this.allowHiding
                ? Component.translatable("narration.suggestion.usage.cycle.hidable")
                : Component.translatable("narration.suggestion.usage.cycle.fixed");
        } else {
            return this.allowHiding
                ? Component.translatable("narration.suggestion.usage.fill.hidable")
                : 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 = getLastWordIndex(partialCommand);
        String lastWord = partialCommand.substring(lastWordIndex).toLowerCase(Locale.ROOT);
        List<Suggestion> suggestionList = Lists.newArrayList();
        List<Suggestion> partial = Lists.newArrayList();
 
        for (Suggestion suggestion : suggestions.getList()) {
            if (!suggestion.getText().startsWith(lastWord) && !suggestion.getText().startsWith("minecraft:" + lastWord)) {
                partial.add(suggestion);
            } else {
                suggestionList.add(suggestion);
            }
        }
 
        suggestionList.addAll(partial);
        return suggestionList;
    }
 
    public void updateCommandInfo() {
        String command = this.input.getValue();
        if (this.currentParse != null && !this.currentParse.getReader().getString().equals(command)) {
            this.currentParse = null;
            this.currentParseIsCommand = false;
            this.currentParseIsMessage = false;
        }
 
        if (!this.keepSuggestions) {
            this.input.setSuggestion(null);
            this.suggestions = null;
        }
 
        this.commandUsage.clear();
        StringReader reader = new StringReader(command);
        boolean startsWithSlash = reader.canRead() && reader.peek() == '/';
        if (startsWithSlash) {
            reader.skip();
        }
 
        boolean isCommand = this.commandsOnly || startsWithSlash;
        int cursorPosition = this.input.getCursorPosition();
        if (isCommand) {
            CommandDispatcher<ClientSuggestionProvider> commands = this.minecraft.player.connection.getCommands();
            if (this.currentParse == null) {
                this.currentParse = commands.parse(reader, this.minecraft.player.connection.getSuggestionsProvider());
                this.currentParseIsCommand = true;
                this.currentParseIsMessage = hasMessageArguments(this.currentParse);
            }
 
            int parseStart = this.onlyShowIfCursorPastError ? reader.getCursor() : 1;
            if (cursorPosition >= parseStart && (this.suggestions == null || !this.keepSuggestions)) {
                this.pendingSuggestions = commands.getCompletionSuggestions(this.currentParse, cursorPosition);
                this.pendingSuggestions.thenAccept(suggestionResult -> {
                    if (this.pendingSuggestions.isDone()) {
                        this.updateUsageInfo(this.currentParse, suggestionResult);
                    }
                });
            }
        } else if (!command.isBlank()) {
            this.currentParseIsMessage = true;
            String partialCommand = command.substring(0, cursorPosition);
            int lastWord = getLastWordIndex(partialCommand);
            Collection<String> nonCommandSuggestions = this.minecraft.player.connection.getSuggestionsProvider().getCustomTabSuggestions();
            this.pendingSuggestions = SharedSuggestionProvider.suggest(nonCommandSuggestions, new SuggestionsBuilder(partialCommand, lastWord));
            if (this.currentParseIsMessage && !this.messagesAllowed) {
                this.commandUsage.add(MESSAGES_NOT_ALLOWED_TEXT.getVisualOrderText());
            }
 
            this.recomputeUsageBoxWidth();
            this.commandUsagePosition = 0;
        } else {
            this.pendingSuggestions = null;
        }
    }
 
    private static boolean hasMessageArguments(ParseResults<ClientSuggestionProvider> parseResults) {
        @OnlyIn(Dist.CLIENT)
        class Visitor implements ArgumentVisitor.Output<ClientSuggestionProvider> {
            boolean foundMessageArgument;
 
            @Override
            public <T> void accept(
                CommandContextBuilder<ClientSuggestionProvider> context,
                ArgumentCommandNode<ClientSuggestionProvider, T> argument,
                @Nullable ParsedArgument<ClientSuggestionProvider, T> value
            ) {
                this.foundMessageArgument = this.foundMessageArgument | (value != null && value.getResult() instanceof MessageArgument.Message);
            }
        }
 
        Visitor visitor = new Visitor();
        ArgumentVisitor.visitArguments(parseResults, visitor, false);
        return visitor.foundMessageArgument;
    }
 
    private static int getLastWordIndex(String text) {
        if (Strings.isNullOrEmpty(text)) {
            return 0;
        } else {
            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();
        return context == null
            ? message.getVisualOrderText()
            : Component.translatable("command.context.parse_error", message, e.getCursor(), context).getVisualOrderText();
    }
 
    private void updateUsageInfo(ParseResults<ClientSuggestionProvider> currentParse, Suggestions suggestions) {
        boolean trailingCharacters = false;
        if (this.input.getCursorPosition() == this.input.getValue().length()) {
            if (suggestions.isEmpty() && !currentParse.getExceptions().isEmpty()) {
                int literals = 0;
 
                for (Entry<CommandNode<ClientSuggestionProvider>, CommandSyntaxException> entry : currentParse.getExceptions().entrySet()) {
                    CommandSyntaxException exception = entry.getValue();
                    if (exception.getType() == CommandSyntaxException.BUILT_IN_EXCEPTIONS.literalIncorrect()) {
                        literals++;
                    } else {
                        this.commandUsage.add(getExceptionMessage(exception));
                    }
                }
 
                if (literals > 0) {
                    this.commandUsage
                        .add(
                            getExceptionMessage(
                                CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(currentParse.getReader())
                            )
                        );
                }
            } else if (currentParse.getReader().canRead()) {
                trailingCharacters = true;
            }
        }
 
        SuggestionContext<ClientSuggestionProvider> suggestionContextAtCursor = currentParse.getContext().findSuggestionContext(this.input.getCursorPosition());
        if (this.commandUsage.isEmpty()) {
            List<FormattedCharSequence> usageEntries = this.fillNodeUsage(suggestionContextAtCursor, USAGE_FORMAT);
            if (usageEntries.isEmpty() && trailingCharacters) {
                this.commandUsage.add(getExceptionMessage(Commands.getParseException(currentParse)));
            }
 
            this.commandUsage.addAll(usageEntries);
        }
 
        if (this.currentParseIsCommand && !this.commandsAllowed) {
            this.commandUsage.add(COMMANDS_NOT_ALLOWED_TEXT.getVisualOrderText());
        }
 
        if (this.currentParseIsMessage && !this.messagesAllowed) {
            this.commandUsage.add(MESSAGES_NOT_ALLOWED_TEXT.getVisualOrderText());
        }
 
        this.recomputeUsageBoxWidth();
        if (!this.commandUsage.isEmpty()) {
            this.commandUsagePosition = Mth.clamp(
                this.input.getScreenX(suggestionContextAtCursor.startPos), 0, this.input.getScreenX(0) + this.input.getInnerWidth() - this.commandUsageWidth
            );
        } else {
            this.commandUsagePosition = 0;
        }
 
        this.suggestions = null;
        if (this.allowSuggestions && this.minecraft.options.autoSuggestions().get()) {
            this.showSuggestions(false);
        }
    }
 
    private List<FormattedCharSequence> fillNodeUsage(SuggestionContext<ClientSuggestionProvider> suggestionContext, Style usageFormat) {
        Map<CommandNode<ClientSuggestionProvider>, String> usage = this.minecraft
            .player
            .connection
            .getCommands()
            .getSmartUsage(suggestionContext.parent, this.minecraft.player.connection.getSuggestionsProvider());
        List<FormattedCharSequence> lines = new ArrayList<>();
 
        for (Entry<CommandNode<ClientSuggestionProvider>, String> entry : usage.entrySet()) {
            if (!(entry.getKey() instanceof LiteralCommandNode)) {
                lines.add(FormattedCharSequence.forward(entry.getValue(), usageFormat));
            }
        }
 
        return lines;
    }
 
    private void recomputeUsageBoxWidth() {
        int longest = 0;
 
        for (FormattedCharSequence entry : this.commandUsage) {
            longest = Math.max(longest, this.font.width(entry));
        }
 
        this.commandUsageWidth = longest;
    }
 
    private @Nullable FormattedCharSequence formatChat(String text, int offset) {
        return this.currentParse != null ? formatText(this.currentParse, text, offset) : null;
    }
 
    private static @Nullable String calculateSuggestionSuffix(String contents, String suggestion) {
        return suggestion.startsWith(contents) ? suggestion.substring(contents.length()) : null;
    }
 
    private static FormattedCharSequence formatText(ParseResults<ClientSuggestionProvider> currentParse, String text, int offset) {
        List<FormattedCharSequence> parts = Lists.newArrayList();
        int unformattedStart = 0;
        int nextColor = -1;
        CommandContextBuilder<ClientSuggestionProvider> context = currentParse.getContext().getLastChild();
 
        for (ParsedArgument<ClientSuggestionProvider, ?> argument : context.getArguments().values()) {
            if (++nextColor >= ARGUMENT_STYLES.size()) {
                nextColor = 0;
            }
 
            int start = Math.max(argument.getRange().getStart() - offset, 0);
            if (start >= text.length()) {
                break;
            }
 
            int end = Math.min(argument.getRange().getEnd() - offset, text.length());
            if (end > 0) {
                parts.add(FormattedCharSequence.forward(text.substring(unformattedStart, start), LITERAL_STYLE));
                parts.add(FormattedCharSequence.forward(text.substring(start, end), ARGUMENT_STYLES.get(nextColor)));
                unformattedStart = end;
            }
        }
 
        if (currentParse.getReader().canRead()) {
            int startx = Math.max(currentParse.getReader().getCursor() - offset, 0);
            if (startx < text.length()) {
                int end = Math.min(startx + currentParse.getReader().getRemainingLength(), text.length());
                parts.add(FormattedCharSequence.forward(text.substring(unformattedStart, startx), LITERAL_STYLE));
                parts.add(FormattedCharSequence.forward(text.substring(startx, end), UNPARSED_STYLE));
                unformattedStart = end;
            }
        }
 
        parts.add(FormattedCharSequence.forward(text.substring(unformattedStart), LITERAL_STYLE));
        return FormattedCharSequence.composite(parts);
    }
 
    public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY) {
        if (!this.extractSuggestions(graphics, mouseX, mouseY)) {
            this.extractUsage(graphics);
        }
    }
 
    public boolean extractSuggestions(GuiGraphicsExtractor graphics, int mouseX, int mouseY) {
        if (this.suggestions != null) {
            this.suggestions.extractRenderState(graphics, mouseX, mouseY);
            return true;
        } else {
            return false;
        }
    }
 
    public void extractUsage(GuiGraphicsExtractor graphics) {
        int y = 0;
 
        for (FormattedCharSequence line : this.commandUsage) {
            int lineY = this.anchorToBottom ? this.screen.height - 27 - 12 * y : 72 + 12 * y;
            graphics.fill(this.commandUsagePosition - 1, lineY, this.commandUsagePosition + this.commandUsageWidth + 1, lineY + 12, this.fillColor);
            graphics.text(this.font, line, this.commandUsagePosition, lineY + 2, -1);
            y++;
        }
    }
 
    public Component getNarrationMessage() {
        return (Component)(this.suggestions != null ? CommonComponents.NEW_LINE.copy().append(this.suggestions.getNarrationMessage()) : CommonComponents.EMPTY);
    }
 
    public boolean hasAllowedInput() {
        return this.currentParseIsMessage && !this.messagesAllowed ? false : !this.currentParseIsCommand || this.commandsAllowed;
    }
 
    @OnlyIn(Dist.CLIENT)
    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;
        private boolean tabCycles;
        private int lastNarratedEntry;
 
        private SuggestionsList(int x, int y, int width, List<Suggestion> suggestionList, boolean immediateNarration) {
            Objects.requireNonNull(CommandSuggestions.this);
            super();
            this.lastMouse = Vec2.ZERO;
            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 extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY) {
            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 mouseMoved = this.lastMouse.x != mouseX || this.lastMouse.y != mouseY;
            if (mouseMoved) {
                this.lastMouse = new Vec2(mouseX, mouseY);
            }
 
            if (limited) {
                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 (int x = 0; x < this.rect.getWidth(); x++) {
                        if (x % 2 == 0) {
                            graphics.fill(this.rect.getX() + x, this.rect.getY() - 1, this.rect.getX() + x + 1, this.rect.getY(), -1);
                        }
                    }
                }
 
                if (hasNext) {
                    for (int xx = 0; xx < this.rect.getWidth(); xx++) {
                        if (xx % 2 == 0) {
                            graphics.fill(
                                this.rect.getX() + xx,
                                this.rect.getY() + this.rect.getHeight(),
                                this.rect.getX() + xx + 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.text(
                    CommandSuggestions.this.font,
                    suggestion.getText(),
                    this.rect.getX() + 1,
                    this.rect.getY() + 2 + 12 * i,
                    i + this.offset == this.current ? -256 : -5592406
                );
            }
 
            if (hovered) {
                Message tooltip = this.suggestionList.get(this.current).getTooltip();
                if (tooltip != 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;
            } else {
                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 mouseX = (int)CommandSuggestions.this.minecraft.mouseHandler.getScaledXPos(CommandSuggestions.this.minecraft.getWindow());
            int mouseY = (int)CommandSuggestions.this.minecraft.mouseHandler.getScaledYPos(CommandSuggestions.this.minecraft.getWindow());
            if (this.rect.contains(mouseX, mouseY)) {
                this.offset = Mth.clamp((int)(this.offset - scroll), 0, Math.max(this.suggestionList.size() - CommandSuggestions.this.suggestionLineLimit, 0));
                return true;
            } else {
                return false;
            }
        }
 
        public boolean keyPressed(KeyEvent event) {
            if (event.isUp()) {
                this.cycle(-1);
                this.tabCycles = false;
                return true;
            } else if (event.isDown()) {
                this.cycle(1);
                this.tabCycles = false;
                return true;
            } else if (event.isCycleFocus()) {
                if (this.tabCycles) {
                    this.cycle(event.hasShiftDown() ? -1 : 1);
                }
 
                this.useSuggestion();
                return true;
            } else if (event.isEscape()) {
                CommandSuggestions.this.hide();
                CommandSuggestions.this.input.setSuggestion(null);
                return true;
            } else {
                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.current + this.suggestionList.size();
            }
 
            if (this.current >= this.suggestionList.size()) {
                this.current = 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();
            return tooltip != null
                ? Component.translatable(
                    "narration.suggestion.tooltip", this.current + 1, this.suggestionList.size(), suggestion.getText(), Component.translationArg(tooltip)
                )
                : Component.translatable("narration.suggestion", this.current + 1, this.suggestionList.size(), suggestion.getText());
        }
    }
}

引用的其他类

  • Minecraft

    • 引用位置: 参数/字段
  • Font

    • 引用位置: 参数/字段
  • GuiGraphicsExtractor

    • 引用位置: 参数
  • EditBox

    • 引用位置: 参数/字段
  • Screen

    • 引用位置: 参数/字段
  • KeyEvent

    • 引用位置: 参数
  • MouseButtonEvent

    • 引用位置: 参数
  • ClientSuggestionProvider

    • 引用位置: 参数/字段
  • Rect2i

    • 引用位置: 构造调用
    • 关联成员: Rect2i()
  • ArgumentVisitor

    • 引用位置: 方法调用
    • 关联成员: ArgumentVisitor.visitArguments()
  • Commands

    • 引用位置: 方法调用
    • 关联成员: Commands.getParseException()
  • SharedSuggestionProvider

    • 引用位置: 方法调用
    • 关联成员: SharedSuggestionProvider.suggest()
  • Component

    • 引用位置: 字段/方法调用/返回值
    • 关联成员: Component.translatable(), Component.translationArg()
  • ComponentUtils

    • 引用位置: 方法调用
    • 关联成员: ComponentUtils.fromMessage()
  • Style

    • 引用位置: 参数/字段
  • FormattedCharSequence

    • 引用位置: 字段/方法调用/返回值
    • 关联成员: FormattedCharSequence.composite(), FormattedCharSequence.forward()
  • Mth

    • 引用位置: 方法调用
    • 关联成员: Mth.clamp()
  • TropicalFish

    • 引用位置: 字段/方法调用
    • 关联成员: Pattern.compile()
  • Vec2

    • 引用位置: 构造调用
    • 关联成员: Vec2()