前言
我也是刚开始看BukkitAPI不久,说的有不对的地方还请多多担待。
想必大家都知道TabExecutor接口是个什么玩意。
你不知道?那也无妨,想必你知道CommandExecutor接口是什么玩意。
TabExecutor接口在CommandExecutor基础上增加了onTabComplete方法,用于处理命令的TAB补全。
使用了TabExecutor接口的类基本结构如下:
- import org.bukkit.command.Command;
- import org.bukkit.command.CommandSender;
- import org.bukkit.command.TabExecutor;
- import java.util.List;
- public class DemoTabExecutor implements TabExecutor {
- @Override
- public boolean onCommand(CommandSender commandSender, Command command, String s, String[] strings) {
- return false;
- }
- @Override
- public List<String> onTabComplete(CommandSender commandSender, Command command, String s, String[] strings) {
- return null;
- }
- }
从代码中看出,onTabComplete方法返回的是一个List< String >类的对象,List中包含的是用户输入命令时,当前参数可能的补全情况。
例如,玩家输入领地相关指令时,会先输入/res,然后进入第一个参数,此时onTabComplete返回的List内容便是['create','remove']等等,玩家便可以在其中选择自己想要的参数,然后进入下一个参数的选择。
只补全一个参数当然容易,但是想要命令全部补全呢?比如我想要写一个插件,该插件所有命令的所有参数都可以通过TAB补全呢?
问题
举个例子,比如我想建立一个领地,而且归系统所有,假设我们的指令是
- /res create NAME system
其中NAME输入的是这个领地的名字,system是可选参数,代表是系统所有。
于是我们输入/res后,补全的顺序为create,system
当前参数要补全的内容取决于:
1. 目前是第几个参数
2. 上一个参数是什么
3. 应该识别的上一个参数的位置
以/res create NAME system中补全system为例,看图:
这个时候,就知道如何确定什么时候返回system了。
这么多条件,一个一个if/else if/else 就可以了...吗?
大家都知道,一个插件可能有很多指令,而且并不是在插件开始写的时候就完全确定好的,如果用if/else if/else来判断以及返回的话,这样不仅不够优雅,而且为后期维护带来非常大的不便。
我也想过用二维数组,Map之类的来存储所有的情况,直接遍历即可。可是这些数据中有些是字符串,有些是整型,而且最终要返回List对象,想没多久我就举手投降了。
于是我用我半桶水的Java知识,想出了一个易于维护,易于扩展的TAB补全方法。
优雅地实现
Java中有一种类,叫枚举类,不懂的小伙伴可以先百度一下。
https://www.runoob.com/java/java-enum.html
从上面说的原理来看,我们可以把枚举的每个成员当做一种List的返回情况,我们可以先设定好这个类的所有属性
1. 目前是第几个参数
2. 上一个参数是什么
3. 应该识别的上一个参数的位置
4. 返回的List
并且写好构造方法。
TabList.class
- import java.util.Arrays;
- import java.util.List;
- public enum TabList {
- private List<String> list;//返回的List
- private int[] num;//这个参数可以出现的位置
- private int befPos;//识别的上个参数的位置
- private String bef;//上个参数的内容
- private TabList(List<String> list,int befPos, String bef, int[] num){
- this.list = list;
- this.befPos = befPos;
- this.bef = bef;
- this.num = num.clone();
- }
- }
然后,当我们需要新的补全情况时,只需要添加对象即可。
假设第一个参数可能的情况为create, alter, delete,那我们可以这样写:
- FIRST(Arrays.asList("create","delete","alter"),0,null,new int[]{1}),
因为前面没有更多参数,所以上一个参数为null,当然你也可以设置为""。
第二个参数是领地的名字,我们可以补全YourResName来提示用户这里要输入名字,由于我的设定是无论什么指令,第二个参数总是要操作的领地的名字,所以我们bef填null
- RES_NAME(Arrays.asList("YourResName"),0,null,new int[]{2});
- CREATE_SYSTEM_ENABLE(Arrays.asList("system","disable"), 1,"create",new int[]{3});
当然,既然多了一个可选参数,那我们就多一个参数需要补全,为了避免重复,在上一个参数输入system时,我们第四个参数应该返回disable,反之,在上一个参数输入disable时,我们第四个参数应该返回system,所以我们这样写
- CREATE_ENABLE(Arrays.asList("disable"),3,"system",new int[]{4}),
- CREATE_SYSTEM(Arrays.asList("system"),3,"disable",new int[]{4}),
MyCommand.class(使用TabExecutor的那个类)
- public List<String> onTabComplete(CommandSender commandSender, Command command, String s, String[] args) {
- return TabList.returnList(args,args.length,commandSender);
- }
TabList.class
- public int[] getNum() {
- return num;
- }
- public String getBef() {
- return bef;
- }
- public List<String> getList() {
- return list;
- }
- public int getBefPos() {
- return befPos;
- }
- public static List<String> returnList(String[] Para, int curNum, CommandSender sender) {
- for(TabList tab : TabList.values() ){
- if(tab.getBefPos()-1>=Para.length){
- continue;
- }
- if((tab.getBef() == null || tab.getBef().equalsIgnoreCase(Para[tab.getBefPos()-1])) && Arrays.binarySearch(tab.getNum(),curNum)>=0){
- return tab.getList();
- }
- }
- return null;
- }
后续需要添加补全的指令只需要确定好条件新增成员即可,是不是比在if/else if/else中来回修改好多啦!
另外
完整的代码就不贴了。
总感觉我这个办法还是笨重了一点,如果有大佬有更好的方法,麻烦在评论区给个思路,我们也好学习学习~
另外禁止转载,虽然你们转了我也没辙,还请备注好原帖地址。
吐槽一下这个编辑器,明明显示可以输入markdown代码,结果出来跟个鬼一样,还是得自己搞样式......
2020-7-17更新:
看了下评论,感觉还有一些东西没说清楚。其实我只是提供一个用枚举类来实现TAB完全补全的思路。也就是说,枚举类内的属性实际上是可以根据你插件的需求随意更换的。比如你命令参数出现的位置永远只会固定一个,那你可以不用数组,其他属性也是同理。如果你的命令当前的补全只靠这几个属性无法确定,你也可以多加几个属性。
2020-7-17更新:
最近想到了新思路,但是还没有具体的实现。
直接以字符串数组的形式存储每一个命令,再用子串匹配字符串,将剩余的参数以空格为分隔符添加到List中,直到匹配完所有的命令返回List,如果能够实现,这个方式将更易于扩展,我有时间会去试一下的~
