Skip to content

SuggestionsBuilder.getRemainingLowerCase() does not work correctly for Unicode special casing rules #92

@Marcono1234

Description

@Marcono1234

Version

1.0.18

Description

This is basically what has been raised as concern in #86 (comment)

The current SuggestionsBuilder code does not correctly handle cases where after lowercasing the string, the length respectively the character indices change.
For language-insensitive conversion (Locale.ROOT) this can currently only occur for İ (U+0130; "Latin Capital Letter I with Dot Above"), see also Unicode Special Casing (but it could affect more code points in the future).

The effect of this is that command nodes and argument types which use SuggestionsBuilder.getRemainingLowerCase() fail to provide command suggestions.
Affected built-in command nodes / argument types:

  • LiteralCommandNode
  • BoolArgumentType

Possible solutions

Manually trying to map between the source and the lowercased string is probably rather difficult, if not impossible.
Instead an alternative might be to use one of the Character methods for lowercasing (toLowerCase(char) or toLowerCase(int)), which maintain a 1:1 relationship. However, that would require manually going through all characters / code points and converting them which is very likely less performant than the String.toLowerCase methods.

Example (Minecraft)

Version: 21w17a

Type in chat:

/execute as @e[name="İ"] 

❌ Notice how no suggestions are shown

Example (code)

public class BrigadierTest {
    public static void main(String[] args) throws CommandSyntaxException {
        List<CommandDispatcher<Object>> dispatchers = Arrays.asList(createWithLiteral(), createWithBool());
        for (CommandDispatcher<Object> dispatcher : dispatchers) {
            for (String s : Arrays.asList("'!'", "'\u0130'")) {
                ParseResults<Object> parseResults = dispatcher.parse("command " + s + " ", null);
                System.out.println(s + ": " + dispatcher.getCompletionSuggestions(parseResults).join());
            }
        }
    }

    private static CommandDispatcher<Object> createWithLiteral() {
        CommandDispatcher<Object> dispatcher = new CommandDispatcher<>();
        dispatcher.register(LiteralArgumentBuilder.literal("command")
                .then(RequiredArgumentBuilder.argument("first", StringArgumentType.string()).executes(c -> {
                    return 1;
                }).then(LiteralArgumentBuilder.literal("second")).executes(c -> {
                    return 1;
                })));
        return dispatcher;
    }

    private static CommandDispatcher<Object> createWithBool() {
        CommandDispatcher<Object> dispatcher = new CommandDispatcher<>();
        dispatcher.register(LiteralArgumentBuilder.literal("command")
                .then(RequiredArgumentBuilder.argument("first", StringArgumentType.string()).executes(c -> {
                    return 1;
                }).then(RequiredArgumentBuilder.argument("second", BoolArgumentType.bool()).executes(c -> {
                    return 1;
                }))));
        return dispatcher;
    }
}

❌ When the first argument contains İ (\u0130) CommandDispatcher does not provide any suggestions

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions