feat: adding auto complete for tags

This commit is contained in:
Sekwah 2023-12-01 04:11:29 +00:00
parent 19d6d8f0fb
commit b68153d744
10 changed files with 204 additions and 90 deletions

View File

@ -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;
}
}

View File

@ -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<String> onTabComplete(CommandSenderContainer sender, String[] args) {
// TODO add tab complete for tags
return null;
if(TagReader.isClosedString(args)) {
return List.of();
}
List<Tag> allTags = tagRegistry.getTags();
List<String> 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<DataTag> 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

View File

@ -27,7 +27,7 @@ import java.util.Set;
public class Destination implements TagTarget {
@Inject
TagRegistry<Destination> tagRegistry;
TagRegistry tagRegistry;
@SerializedName("l")
private PlayerLocation loc;

View File

@ -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) {

View File

@ -20,7 +20,7 @@ import java.util.Map;
public class AdvancedPortal implements TagTarget {
@Inject
transient TagRegistry<AdvancedPortal> tagRegistry;
transient TagRegistry tagRegistry;
@SerializedName("max")
private WorldLocation maxLoc;

View File

@ -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<T> {
public class TagRegistry {
@Inject
private AdvancedPortalsCore portalsCore;
AdvancedPortalsCore portalsCore;
/**
* List of tag names which should be in order alphabetically
*/
private ArrayList<String> 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<String, String> tagDesc = new HashMap();
private Map<String, Tag.Activation> activationHandlers = new HashMap();
private Map<String, Tag.Creation> creationHandlers = new HashMap();
private Map<String, Tag.TagStatus> statusHandlers = new HashMap();
private final ArrayList<String> literalTags = new ArrayList<>();
private final ArrayList<Tag> tags = new ArrayList<>();
private final Map<String, Tag.Activation> activationTags = new HashMap<>();
private final Map<String, Tag.Creation> creationTags = new HashMap<>();
private final Map<String, Tag.TagStatus> statusTags = new HashMap<>();
/**
* Portals to trigger when a portal is activated
@ -41,7 +32,7 @@ public class TagRegistry<T> {
* @return
*/
public Tag.Activation getActivationHandler(String arg) {
return this.activationHandlers.get(arg);
return this.activationTags.get(arg);
}
/**
@ -50,7 +41,7 @@ public class TagRegistry<T> {
* @return
*/
public Tag.Creation getCreationHandler(String arg) {
return this.creationHandlers.get(arg);
return this.creationTags.get(arg);
}
/**
@ -59,81 +50,57 @@ public class TagRegistry<T> {
* @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<String> getTags() {
ArrayList<String> 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<Tag> getTags() {
// Make a copy of the list to prevent issues with modification
return this.tags;
}
}

View File

@ -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<String> autoComplete(String argData) {
List<String> autoCompletes = new ArrayList<>();
// Get all and filter by the argData
autoCompletes.add("AIR");
autoCompletes.add("WATER");
return autoCompletes;
}
}

View File

@ -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<DataTag> getTagsFromArgs(String[] args) {
HashMap<String, ArrayList<String>> tagMap = new HashMap<>();
StringBuilder currentValue = new StringBuilder();

View File

@ -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<String> autoComplete(String argData);
}
/**
* The events for portal creation and destroying
*/

View File

@ -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