Reference implementation demonstrating contextual argument suggestion for Minecraft Spigot/Paper plugins. Implements Bukkit TabCompleter with multi-level parsing, dynamic providers, and permission-aware filtering.
- Multi-Level State Machine - Depth-aware suggestion generation based on current parsing state
- Dynamic Player Enumeration - Real-time online player name completion with prefix filtering
- Permission-Based Filtering - Contextual suggestions respect permission boundaries
- Temporal Token Parsing - Duration specification with support for s/m/h/d/w units
- Ban Registry Integration - Banned player suggestions for unban commands
- Zero Dependencies - Uses only native Spigot/Paper API
- No Configuration Required - Works out of the box
| Command | Description | Suggestion Levels |
|---|---|---|
/tab ban <player> <reason> [duration] |
Ban a player with optional duration | Player → Duration |
/tab unban <player> |
Remove an existing ban | Banned player list |
/tab kick <player> [reason] |
Kick a player from the server | Online players |
/tab reload |
Reload plugin configuration | None |
| Unit | Suffix | Example | Conversion |
|---|---|---|---|
| Seconds | s |
10s, 30s |
Direct value |
| Minutes | m |
1m, 5m, 15m |
value × 60 |
| Hours | h |
1h, 2h, 12h |
value × 3600 |
| Days | d |
1d, 7d, 30d |
value × 86400 |
| Weeks | w |
1w, 2w, 4w |
value × 604800 |
git clone https://github.com/KaloudasDev/tab-completion.git
cd tab-completion
mvn clean packagecp target/tab-completion-1.0.0.jar /path/to/server/plugins/# Restart your Spigot/Paper server
# Or use plugman for dynamic loading: plugman load tab-completion@Override
public void onEnable() {
getCommand("tab").setExecutor(new AdminCommand());
getCommand("tab").setTabCompleter(new AdminCommandTabCompleter());
}public List<String> onTabComplete(CommandSender sender, Command command,
String alias, String[] args) {
int depth = args.length;
String token = args[depth - 1].toLowerCase();
if (depth == 1) {
return this.filterByPrefix(PRIMARY_COMMANDS.stream(), token);
}
if (depth == 2) {
return this.handleSecondLevelCompletion(args[0].toLowerCase(), token);
}
if (depth == 3 && args[0].equalsIgnoreCase("ban")) {
return this.filterByPrefix(DURATION_TOKENS.stream(), token);
}
return Collections.emptyList();
}The BanManager implements Java Serialization for ban registry persistence with automatic expiration handling:
public class BanManager {
private final Map<UUID, BanEntry> banRegistry = new ConcurrentHashMap<>();
private final File persistenceFile;
public void ban(Player player, String reason, long seconds) {
}
public List<BanEntry> getActiveBans() {
}
}| Node | Description | Default |
|---|---|---|
tabframework.admin |
Full access to all commands and tab completion | op |
tabframework.use |
Basic command access (future use) | true |
| Test Case | Command | Expected Result |
|---|---|---|
| Primary commands | /tab [TAB] |
ban, unban, kick, reload |
| Player completion | /tab ban [TAB] |
List of online players |
| Duration completion | /tab ban Kaloudas [TAB] |
10s, 30s, 1m, 5m, 10m, 30m, 1h, 2h, 6h, 12h, 1d, 2d, 7d, 30d |
| Unban completion | /tab unban [TAB] |
List of banned players |
| Prefix filtering | /tab b[TAB] |
ban |
| Permission restriction | Non-op user executing /tab [TAB] |
No suggestions |
@Test
public void testTabCompletion() {
MockBukkit.mock();
JavaPlugin plugin = MockBukkit.load(TabFrameworkPlugin.class);
TabCompleter completer = new AdminCommandTabCompleter();
List<String> result = completer.onTabComplete(
mock(CommandSender.class),
mock(Command.class),
"tab",
new String[] { "b" }
);
assertTrue(result.contains("ban"));
MockBukkit.unmock();
}All suggestion lists undergo prefix filtering using the startsWith string comparison method against the current user input token.
The Bukkit.getOnlinePlayers() method provides real-time player collection for target selection suggestions.
The getActiveBans() method filters expired entries and returns the current ban list for unban suggestions.
| Issue | Solution |
|---|---|
| No suggestions appear | Verify TabCompleter is registered via setTabCompleter() in onEnable() |
| Wrong suggestions | Check args.length conditions and command aliases |
| Suggestions not filtering | Add startsWith(partial) filter to all streams |
| Permission errors | Verify permission node matches in plugin.yml and code |
| Player names not showing | Ensure Bukkit.getOnlinePlayers() returns non-empty collection |
| Unban shows no players | Verify ban registry has non-expired entries |
| Duration parser fails | Input must match regex ^\d+[smhdw]$ |
- Implement
TabCompleterinterface with depth-aware logic - Register completer via
setTabCompleter()during plugin initialization - Use
args.lengthto determine current parsing depth - Apply
startsWith(currentToken)prefix filtering to all suggestion streams - Return
Collections.emptyList()instead ofnullfor empty results - Validate permissions before generating suggestions
- Leverage
Bukkit.getOnlinePlayers().stream()for dynamic player lists - Use Java 8+ streams with
filter()andcollect()for clean operations - Implement persistent storage with automatic expiration handling
- Test all argument depths with partial and complete inputs
| Server Software | Supported Versions |
|---|---|
| Paper | 1.16.5 – 1.21.4 |
| Spigot | 1.16.5 – 1.21.4 |
| Purpur | 1.16.5 – 1.21.4 |
| Arclight | 1.16.5 – 1.20.4 |
Contributions are welcome. Please ensure:
- Code maintains zero external dependencies beyond Spigot API
- All suggestion levels implement prefix filtering
- Permission checks precede all suggestion generation
- tream operations use appropriate null safety
- Documentation updated for new features
MIT © KaloudasDev