001/* 002 * Copyright (c) 2016-2017 Daniel Ennis (Aikar) - MIT License 003 * 004 * Permission is hereby granted, free of charge, to any person obtaining 005 * a copy of this software and associated documentation files (the 006 * "Software"), to deal in the Software without restriction, including 007 * without limitation the rights to use, copy, modify, merge, publish, 008 * distribute, sublicense, and/or sell copies of the Software, and to 009 * permit persons to whom the Software is furnished to do so, subject to 010 * the following conditions: 011 * 012 * The above copyright notice and this permission notice shall be 013 * included in all copies or substantial portions of the Software. 014 * 015 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 016 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 017 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 018 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 019 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 020 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 021 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 022 */ 023 024package co.aikar.commands; 025 026import com.google.common.collect.ImmutableList; 027import com.google.common.collect.Lists; 028import org.jetbrains.annotations.NotNull; 029 030import java.util.Collection; 031import java.util.HashMap; 032import java.util.List; 033import java.util.Map; 034import java.util.stream.Collectors; 035import java.util.stream.IntStream; 036 037 038@SuppressWarnings({"WeakerAccess", "UnusedReturnValue"}) 039public class CommandCompletions <C extends CommandCompletionContext> { 040 private final CommandManager manager; 041 private Map<String, CommandCompletionHandler> completionMap = new HashMap<>(); 042 043 public CommandCompletions(CommandManager manager) { 044 this.manager = manager; 045 registerAsyncCompletion("nothing", c -> ImmutableList.of()); 046 registerAsyncCompletion("range", (c) -> { 047 String config = c.getConfig(); 048 if (config == null) { 049 return ImmutableList.of(); 050 } 051 final String[] ranges = ACFPatterns.DASH.split(config); 052 int start; 053 int end; 054 if (ranges.length != 2) { 055 start = 0; 056 end = ACFUtil.parseInt(ranges[0], 0); 057 } else { 058 start = ACFUtil.parseInt(ranges[0], 0); 059 end = ACFUtil.parseInt(ranges[1], 0); 060 } 061 return IntStream.rangeClosed(start, end).mapToObj(Integer::toString).collect(Collectors.toList()); 062 }); 063 registerAsyncCompletion("timeunits", (c) -> ImmutableList.of("minutes", "hours", "days", "weeks", "months", "years")); 064 } 065 066 public CommandCompletionHandler registerCompletion(String id, CommandCompletionHandler<C> handler) { 067 return this.completionMap.put("@" + id.toLowerCase(), handler); 068 } 069 070 public CommandCompletionHandler registerAsyncCompletion(String id, AsyncCommandCompletionHandler<C> handler) { 071 return this.completionMap.put("@" + id.toLowerCase(), handler); 072 } 073 074 @NotNull 075 List<String> of(RegisteredCommand cmd, CommandIssuer sender, String[] args, boolean isAsync) { 076 String[] completions = ACFPatterns.SPACE.split(cmd.complete); 077 final int argIndex = args.length - 1; 078 079 String input = args[argIndex]; 080 081 String completion = argIndex < completions.length ? completions[argIndex] : null; 082 if (completion == null && completions.length > 0) { 083 completion = completions[completions.length - 1]; 084 } 085 if (completion == null) { 086 return ImmutableList.of(input); 087 } 088 089 return getCompletionValues(cmd, sender, completion, args, isAsync); 090 } 091 092 List<String> getCompletionValues(RegisteredCommand command, CommandIssuer sender, String completion, String[] args, boolean isAsync) { 093 completion = manager.getCommandReplacements().replace(completion); 094 095 List<String> allCompletions = Lists.newArrayList(); 096 String input = args.length > 0 ? args[args.length - 1] : ""; 097 098 for (String value : ACFPatterns.PIPE.split(completion)) { 099 String[] complete = ACFPatterns.COLONEQUALS.split(value, 2); 100 CommandCompletionHandler handler = this.completionMap.get(complete[0].toLowerCase()); 101 if (handler != null) { 102 if (isAsync && !(handler instanceof AsyncCommandCompletionHandler)) { 103 ACFUtil.sneaky(new SyncCompletionRequired()); 104 return null; 105 } 106 String config = complete.length == 1 ? null : complete[1]; 107 CommandCompletionContext context = manager.createCompletionContext(command, sender, input, config, args); 108 109 try { 110 //noinspection unchecked 111 Collection<String> completions = handler.getCompletions(context); 112 if (completions != null) { 113 allCompletions.addAll(completions); 114 continue; 115 } 116 //noinspection ConstantIfStatement,ConstantConditions 117 if (false) { // Hack to fool compiler. since its sneakily thrown. 118 throw new CommandCompletionTextLookupException(); 119 } 120 } catch (CommandCompletionTextLookupException ignored) { 121 // This should only happen if some other feedback error occured. 122 } catch (Exception e) { 123 command.handleException(sender, Lists.newArrayList(args), e); 124 } 125 // Something went wrong in lookup, fall back to input 126 return ImmutableList.of(input); 127 } else { 128 // Plaintext value 129 allCompletions.add(value); 130 } 131 } 132 return allCompletions; 133 } 134 135 public interface CommandCompletionHandler <C extends CommandCompletionContext> { 136 Collection<String> getCompletions(C context) throws InvalidCommandArgument; 137 } 138 public interface AsyncCommandCompletionHandler <C extends CommandCompletionContext> extends CommandCompletionHandler <C> {} 139 public static class SyncCompletionRequired extends Exception {} 140 141}