From b68153d744178b493dec1f84947332b2d023da89 Mon Sep 17 00:00:00 2001 From: Sekwah Date: Fri, 1 Dec 2023 04:11:29 +0000 Subject: [PATCH] feat: adding auto complete for tags --- .../core/AdvancedPortalsCore.java | 19 ++- .../portal/CreatePortalSubCommand.java | 64 ++++++++- .../core/destination/Destination.java | 2 +- .../core/module/AdvancedPortalsModule.java | 2 + .../core/portal/AdvancedPortal.java | 2 +- .../core/registry/TagRegistry.java | 127 +++++++----------- .../core/tags/activation/DestiTag.java | 32 ++++- .../advancedportals/core/util/TagReader.java | 24 ++++ .../advancedportals/core/warphandler/Tag.java | 20 +++ lang/src/main/resources/lang/en_GB.lang | 2 + 10 files changed, 204 insertions(+), 90 deletions(-) diff --git a/core/src/main/java/com/sekwah/advancedportals/core/AdvancedPortalsCore.java b/core/src/main/java/com/sekwah/advancedportals/core/AdvancedPortalsCore.java index e726797..ebf13cd 100644 --- a/core/src/main/java/com/sekwah/advancedportals/core/AdvancedPortalsCore.java +++ b/core/src/main/java/com/sekwah/advancedportals/core/AdvancedPortalsCore.java @@ -6,9 +6,11 @@ import com.sekwah.advancedportals.core.commands.CommandWithSubCommands; import com.sekwah.advancedportals.core.commands.subcommands.desti.CreateDestiSubCommand; import com.sekwah.advancedportals.core.commands.subcommands.portal.*; import com.sekwah.advancedportals.core.connector.commands.CommandRegister; +import com.sekwah.advancedportals.core.registry.TagRegistry; import com.sekwah.advancedportals.core.serializeddata.DataStorage; import com.sekwah.advancedportals.core.module.AdvancedPortalsModule; import com.sekwah.advancedportals.core.repository.ConfigRepository; +import com.sekwah.advancedportals.core.tags.activation.DestiTag; import com.sekwah.advancedportals.core.util.InfoLogger; import com.sekwah.advancedportals.core.util.Lang; @@ -35,6 +37,9 @@ public class AdvancedPortalsCore { @Inject private ConfigRepository configRepository; + @Inject + private TagRegistry tagRegistry; + public AdvancedPortalsCore(File dataStorageLoc, InfoLogger infoLogger) { this.dataStorage = new DataStorage(dataStorageLoc); this.infoLogger = infoLogger; @@ -61,24 +66,26 @@ public class AdvancedPortalsCore { Lang.loadLanguage(configRepository.getTranslation()); this.registerCommands(); + this.registerTags(); this.infoLogger.log(Lang.translate("logger.pluginenable")); } + private void registerTags() { + this.tagRegistry.registerTag(new DestiTag()); + } + /** * */ public void registerCommands() { this.registerPortalCommand(commandRegister); this.registerDestinationCommand(commandRegister); - - // TODO run annotation grabbing shit } private void registerPortalCommand(CommandRegister commandRegister) { this.portalCommand = new CommandWithSubCommands(this); - // TODO remove once annotations are done this.portalCommand.registerSubCommand("version", new VersionSubCommand()); this.portalCommand.registerSubCommand("langupdate", new LangUpdateSubCommand()); this.portalCommand.registerSubCommand("reload", new ReloadSubCommand()); @@ -95,8 +102,6 @@ public class AdvancedPortalsCore { private void registerDestinationCommand(CommandRegister commandRegister) { this.destiCommand = new CommandWithSubCommands(this); - - // TODO remove once annotations are done this.destiCommand.registerSubCommand("create", new CreateDestiSubCommand()); commandRegister.registerCommand("destination", this.destiCommand); @@ -126,4 +131,8 @@ public class AdvancedPortalsCore { public AdvancedPortalsModule getModule() { return this.module; } + + public TagRegistry getTagRegistry() { + return this.tagRegistry; + } } diff --git a/core/src/main/java/com/sekwah/advancedportals/core/commands/subcommands/portal/CreatePortalSubCommand.java b/core/src/main/java/com/sekwah/advancedportals/core/commands/subcommands/portal/CreatePortalSubCommand.java index bcfb325..446bb60 100644 --- a/core/src/main/java/com/sekwah/advancedportals/core/commands/subcommands/portal/CreatePortalSubCommand.java +++ b/core/src/main/java/com/sekwah/advancedportals/core/commands/subcommands/portal/CreatePortalSubCommand.java @@ -4,6 +4,7 @@ import com.google.inject.Inject; import com.sekwah.advancedportals.core.commands.SubCommand; import com.sekwah.advancedportals.core.connector.containers.CommandSenderContainer; import com.sekwah.advancedportals.core.connector.containers.PlayerContainer; +import com.sekwah.advancedportals.core.registry.TagRegistry; import com.sekwah.advancedportals.core.serializeddata.DataTag; import com.sekwah.advancedportals.core.permissions.PortalPermissions; import com.sekwah.advancedportals.core.portal.AdvancedPortal; @@ -11,6 +12,7 @@ import com.sekwah.advancedportals.core.services.PortalServices; import com.sekwah.advancedportals.core.util.InfoLogger; import com.sekwah.advancedportals.core.util.Lang; import com.sekwah.advancedportals.core.util.TagReader; +import com.sekwah.advancedportals.core.warphandler.Tag; import java.util.ArrayList; import java.util.Arrays; @@ -24,6 +26,9 @@ public class CreatePortalSubCommand implements SubCommand { @Inject InfoLogger infoLogger; + @Inject + TagRegistry tagRegistry; + @Override public void onCommand(CommandSenderContainer sender, String[] args) { if(args.length > 1) { @@ -67,8 +72,63 @@ public class CreatePortalSubCommand implements SubCommand { @Override public List onTabComplete(CommandSenderContainer sender, String[] args) { - // TODO add tab complete for tags - return null; + + if(TagReader.isClosedString(args)) { + return List.of(); + } + + + + List allTags = tagRegistry.getTags(); + List suggestions = new ArrayList<>(); + if(args.length > 0) { + var lastArg = args[args.length - 1]; + // Check if the split results in exactly 2 or if its 1 and ends with : + var split = lastArg.split(":"); + if(split.length == 2 || (split.length == 1 && lastArg.endsWith(":"))) { + // Loop over tags in allTags and check if the first half of split is equal to the tag name or alias + for(Tag tag : allTags) { + if(tag instanceof Tag.AutoComplete autoComplete) { + var tagSuggestions = autoComplete.autoComplete(split.length == 2 ? split[1] : ""); + if(tagSuggestions != null) { + // Loop over suggestions and add split[0] + ":" to the start + for (String tagSuggestion : tagSuggestions) { + suggestions.add(split[0] + ":" + tagSuggestion); + } + } + } + } + // This is returning right but something is going wrong with "desti:A" whenever anything is typed after : + return suggestions; + } + } + + ArrayList portalTags = TagReader.getTagsFromArgs(args); + + allTags.stream().filter(tag -> { + for (DataTag portalTag : portalTags) { + if(portalTag.NAME.equals(tag.getName())) { + return false; + } + // check the tag aliases + for (String alias : tag.getAliases()) { + if(portalTag.NAME.equals(alias)) { + return false; + } + } + } + return true; + }).forEach(tag -> { + suggestions.add(tag.getName()); + suggestions.addAll(Arrays.stream(tag.getAliases()).toList()); + }); + + // Loop over all suggestions and add : to the end + for (int i = 0; i < suggestions.size(); i++) { + suggestions.set(i, suggestions.get(i) + ":"); + } + + return suggestions; } @Override diff --git a/core/src/main/java/com/sekwah/advancedportals/core/destination/Destination.java b/core/src/main/java/com/sekwah/advancedportals/core/destination/Destination.java index 8b56fe6..46a5529 100644 --- a/core/src/main/java/com/sekwah/advancedportals/core/destination/Destination.java +++ b/core/src/main/java/com/sekwah/advancedportals/core/destination/Destination.java @@ -27,7 +27,7 @@ import java.util.Set; public class Destination implements TagTarget { @Inject - TagRegistry tagRegistry; + TagRegistry tagRegistry; @SerializedName("l") private PlayerLocation loc; diff --git a/core/src/main/java/com/sekwah/advancedportals/core/module/AdvancedPortalsModule.java b/core/src/main/java/com/sekwah/advancedportals/core/module/AdvancedPortalsModule.java index 43d78c1..8b44ca4 100644 --- a/core/src/main/java/com/sekwah/advancedportals/core/module/AdvancedPortalsModule.java +++ b/core/src/main/java/com/sekwah/advancedportals/core/module/AdvancedPortalsModule.java @@ -2,6 +2,7 @@ package com.sekwah.advancedportals.core.module; import com.google.inject.*; import com.sekwah.advancedportals.core.AdvancedPortalsCore; +import com.sekwah.advancedportals.core.registry.TagRegistry; import com.sekwah.advancedportals.core.serializeddata.config.Config; import com.sekwah.advancedportals.core.serializeddata.config.ConfigProvider; import com.sekwah.advancedportals.core.serializeddata.DataStorage; @@ -45,6 +46,7 @@ public class AdvancedPortalsModule extends AbstractModule { // Providers bind(Config.class).toProvider(ConfigProvider.class); + bind(TagRegistry.class).asEagerSingleton(); // Delayed Bindings for(DelayedBinding delayedBinding : delayedBindings) { diff --git a/core/src/main/java/com/sekwah/advancedportals/core/portal/AdvancedPortal.java b/core/src/main/java/com/sekwah/advancedportals/core/portal/AdvancedPortal.java index 359ac16..d11fafd 100644 --- a/core/src/main/java/com/sekwah/advancedportals/core/portal/AdvancedPortal.java +++ b/core/src/main/java/com/sekwah/advancedportals/core/portal/AdvancedPortal.java @@ -20,7 +20,7 @@ import java.util.Map; public class AdvancedPortal implements TagTarget { @Inject - transient TagRegistry tagRegistry; + transient TagRegistry tagRegistry; @SerializedName("max") private WorldLocation maxLoc; diff --git a/core/src/main/java/com/sekwah/advancedportals/core/registry/TagRegistry.java b/core/src/main/java/com/sekwah/advancedportals/core/registry/TagRegistry.java index f661264..4406ae2 100644 --- a/core/src/main/java/com/sekwah/advancedportals/core/registry/TagRegistry.java +++ b/core/src/main/java/com/sekwah/advancedportals/core/registry/TagRegistry.java @@ -2,13 +2,9 @@ package com.sekwah.advancedportals.core.registry; import com.google.inject.Inject; import com.sekwah.advancedportals.core.AdvancedPortalsCore; -import com.sekwah.advancedportals.core.portal.AdvancedPortal; import com.sekwah.advancedportals.core.warphandler.Tag; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; +import java.util.*; /** * Allows a portal to register a tag and add a handler. If a plugin wants to add functionality @@ -16,23 +12,18 @@ import java.util.Map; * * @author sekwah41 */ -public class TagRegistry { +public class TagRegistry { @Inject - private AdvancedPortalsCore portalsCore; + AdvancedPortalsCore portalsCore; - /** - * List of tag names which should be in order alphabetically - */ - private ArrayList tags = new ArrayList(); - /** - * Description of tags for help commands (will try to use translation strings before rendering - * Possibly add a way to allow addons to supply extra info to the Lang class in the future. - */ - private Map tagDesc = new HashMap(); - private Map activationHandlers = new HashMap(); - private Map creationHandlers = new HashMap(); - private Map statusHandlers = new HashMap(); + private final ArrayList literalTags = new ArrayList<>(); + + private final ArrayList tags = new ArrayList<>(); + + private final Map activationTags = new HashMap<>(); + private final Map creationTags = new HashMap<>(); + private final Map statusTags = new HashMap<>(); /** * Portals to trigger when a portal is activated @@ -41,7 +32,7 @@ public class TagRegistry { * @return */ public Tag.Activation getActivationHandler(String arg) { - return this.activationHandlers.get(arg); + return this.activationTags.get(arg); } /** @@ -50,7 +41,7 @@ public class TagRegistry { * @return */ public Tag.Creation getCreationHandler(String arg) { - return this.creationHandlers.get(arg); + return this.creationTags.get(arg); } /** @@ -59,81 +50,57 @@ public class TagRegistry { * @return */ public Tag.TagStatus getTagStatusHandler(String arg) { - return this.statusHandlers.get(arg); - } - - - /** - * It is reccomended that you use the taghandlers to add tag functionality. However - * if needed such as extra data for a tag then this is here. - * - * @param tag - * @return if the tag was registered - */ - private boolean registerTag(String tag) { - if (tag.contains(" ")) { - this.portalsCore.getInfoLogger().logWarning("The tag '" - + tag + "' is invalid as it contains spaces."); - return false; - } - if (this.tags.contains(tag)) { - this.portalsCore.getInfoLogger().logWarning("The tag " - + tag + " has already been registered."); - return false; - } - this.tags.add(tag); - Collections.sort(this.tags); - return true; - } - - private boolean registerTag(String tag, String desc) { - if (registerTag(tag)) { - this.tagDesc.put(tag, desc); - return true; - } - return false; - } - - /** - * Returns a non referenced copy of the array list. - * @return - */ - public ArrayList getTags() { - ArrayList newArrayList = new ArrayList<>(); - newArrayList.addAll(this.tags); - return newArrayList; - } - - public boolean isTagRegistered(String tag){ - return this.tagDesc.containsKey(tag); + return this.statusTags.get(arg); } /** * File must extend * @return if the tag has been registered or if it already exists. */ - public boolean registerTag(String tag, Tag tagHandler) { + public boolean registerTag(Tag tag) { - if (tag == null) { + String tagName = tag.getName(); + + this.tags.add(tag); + + // Check literal tags for clashes + if(this.literalTags.contains(tagName)) { + this.portalsCore.getInfoLogger().logWarning("A tag with the name " + tagName + " already exists."); + return false; + } + + for (String alias : tag.getAliases()) { + if(this.literalTags.contains(alias)) { + this.portalsCore.getInfoLogger().logWarning("A tag with the alias " + alias + " already exists."); + return false; + } + } + + // Add name and aliases to literalTags to check for clashes + this.literalTags.add(tagName); + Collections.addAll(this.literalTags, tag.getAliases()); + + if (tagName == null) { this.portalsCore.getInfoLogger().logWarning("A tag cannot be null."); return false; } - if (!this.registerTag(tag)) { - return false; + if (tag instanceof Tag.Activation tagActivation) { + this.activationTags.put(tagName, tagActivation); } - - if (tagHandler instanceof Tag.Activation tagActivation) { - this.activationHandlers.put(tag, tagActivation); + if (tag instanceof Tag.TagStatus tagStatus) { + this.statusTags.put(tagName, tagStatus); } - if (tagHandler instanceof Tag.TagStatus tagStatus) { - this.statusHandlers.put(tag, tagStatus); - } - if (tagHandler instanceof Tag.Creation tagCreation) { - this.creationHandlers.put(tag, tagCreation); + if (tag instanceof Tag.Creation tagCreation) { + this.creationTags.put(tagName, tagCreation); } return true; } + public List getTags() { + // Make a copy of the list to prevent issues with modification + + return this.tags; + } } diff --git a/core/src/main/java/com/sekwah/advancedportals/core/tags/activation/DestiTag.java b/core/src/main/java/com/sekwah/advancedportals/core/tags/activation/DestiTag.java index f9d7265..82cabea 100644 --- a/core/src/main/java/com/sekwah/advancedportals/core/tags/activation/DestiTag.java +++ b/core/src/main/java/com/sekwah/advancedportals/core/tags/activation/DestiTag.java @@ -2,10 +2,14 @@ package com.sekwah.advancedportals.core.tags.activation; import com.sekwah.advancedportals.core.connector.containers.PlayerContainer; import com.sekwah.advancedportals.core.registry.TagTarget; +import com.sekwah.advancedportals.core.util.Lang; import com.sekwah.advancedportals.core.warphandler.ActivationData; import com.sekwah.advancedportals.core.warphandler.Tag; -public class DestiTag implements Tag.Activation { +import java.util.ArrayList; +import java.util.List; + +public class DestiTag implements Tag.Activation, Tag.AutoComplete { private final TagType[] tagTypes = new TagType[]{ TagType.PORTAL }; @@ -14,6 +18,21 @@ public class DestiTag implements Tag.Activation { return tagTypes; } + @Override + public String getName() { + return "destination"; + } + + @Override + public String[] getAliases() { + return new String[]{"desti"}; + } + + @Override + public String description() { + return Lang.translate("tag.desti.description"); + } + @Override public boolean preActivated(TagTarget target, PlayerContainer player, ActivationData activeData, String[] argData) { return false; @@ -28,4 +47,15 @@ public class DestiTag implements Tag.Activation { public boolean activated(TagTarget target, PlayerContainer player, ActivationData activeData, String[] argData) { return false; } + + @Override + public List autoComplete(String argData) { + List autoCompletes = new ArrayList<>(); + // Get all and filter by the argData + + autoCompletes.add("AIR"); + autoCompletes.add("WATER"); + + return autoCompletes; + } } diff --git a/core/src/main/java/com/sekwah/advancedportals/core/util/TagReader.java b/core/src/main/java/com/sekwah/advancedportals/core/util/TagReader.java index 627e233..f80075e 100644 --- a/core/src/main/java/com/sekwah/advancedportals/core/util/TagReader.java +++ b/core/src/main/java/com/sekwah/advancedportals/core/util/TagReader.java @@ -7,6 +7,30 @@ import java.util.HashMap; public class TagReader { + public static boolean isClosedString(String[] args) { + StringBuilder currentValue = new StringBuilder(); + boolean inQuotes = false; + + for (String arg : args) { + if (arg.contains(":") && !inQuotes) { + int colonIndex = arg.indexOf(':'); + currentValue = new StringBuilder(arg.substring(colonIndex + 1)); + inQuotes = currentValue.toString().startsWith("\""); + } else { + if (!currentValue.isEmpty()) { + currentValue.append(" "); + } + currentValue.append(arg); + } + + if (inQuotes && arg.endsWith("\"")) { + inQuotes = false; + } + } + + return inQuotes; + } + public static ArrayList getTagsFromArgs(String[] args) { HashMap> tagMap = new HashMap<>(); StringBuilder currentValue = new StringBuilder(); diff --git a/core/src/main/java/com/sekwah/advancedportals/core/warphandler/Tag.java b/core/src/main/java/com/sekwah/advancedportals/core/warphandler/Tag.java index 8b4dd19..bada7c2 100644 --- a/core/src/main/java/com/sekwah/advancedportals/core/warphandler/Tag.java +++ b/core/src/main/java/com/sekwah/advancedportals/core/warphandler/Tag.java @@ -3,6 +3,8 @@ package com.sekwah.advancedportals.core.warphandler; import com.sekwah.advancedportals.core.connector.containers.PlayerContainer; import com.sekwah.advancedportals.core.registry.TagTarget; +import java.util.List; + /** * If a tag can be used for any of them then either make it cast the target or if it doesn't need a target * then the exact same tag can be registered into both and ignore the portal info. @@ -28,6 +30,24 @@ public interface Tag { */ TagType[] getTagTypes(); + String getName(); + + String[] getAliases(); + + String description(); + + interface AutoComplete extends Tag { + + /** + * This is used to get the auto complete for the tag. This is called when the player is typing the tag. + * + * @param argData + * @return + */ + List autoComplete(String argData); + + } + /** * The events for portal creation and destroying */ diff --git a/lang/src/main/resources/lang/en_GB.lang b/lang/src/main/resources/lang/en_GB.lang index e304986..f24672e 100644 --- a/lang/src/main/resources/lang/en_GB.lang +++ b/lang/src/main/resources/lang/en_GB.lang @@ -116,3 +116,5 @@ items.selector.name=Portal Region Selector items.selector.pos=Select pos %1$s items.interact.left=Left Click items.interact.right=Right Click + +tag.desti.description=Sets the destination of the portal