diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/SuggestionsProvider.java b/proxy/src/main/java/com/velocitypowered/proxy/command/SuggestionsProvider.java
new file mode 100644
index 000000000..7a1ea903b
--- /dev/null
+++ b/proxy/src/main/java/com/velocitypowered/proxy/command/SuggestionsProvider.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2018 Velocity Contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see
Similar to {@link CommandDispatcher#getCompletionSuggestions(ParseResults)}, except it
+ * avoids fully parsing the given input and performs exactly one requirement predicate check
+ * per considered node.
+ *
+ * @param the type of the command source
+ */
+final class SuggestionsProvider {
+
+ private final CommandDispatcher dispatcher;
+
+ SuggestionsProvider(final CommandDispatcher dispatcher) {
+ this.dispatcher = Preconditions.checkNotNull(dispatcher, "dispatcher");
+ }
+
+ /**
+ * Provides suggestions for the given input and source.
+ *
+ * @param input the partial input
+ * @param source the command source invoking the command
+ * @return a future that completes with the suggestions
+ */
+ public CompletableFuture context = new CommandContextBuilder<>(
+ this.dispatcher, source, this.dispatcher.getRoot(), 0);
+ return this.provideSuggestions(new StringReader(input), context);
+ }
+
+ /**
+ * Provides suggestions for the given input and context.
+ *
+ * @param reader the input reader
+ * @param context an empty context
+ * @return a future that completes with the suggestions
+ */
+ private CompletableFuture context) {
+ final StringRange aliasRange = this.consumeAlias(reader);
+ final String alias = aliasRange.get(reader); // TODO #toLowerCase and test
+ final LiteralCommandNode literal =
+ (LiteralCommandNode) context.getRootNode().getChild(alias);
+
+ final boolean hasArguments = reader.canRead();
+ if (hasArguments) {
+ if (literal == null) {
+ // Input has arguments for non-registered alias
+ return Suggestions.empty();
+ }
+ context.withNode(literal, aliasRange);
+ reader.skip(); // separator
+ // TODO Provide arguments suggestions
+ } else {
+ // TODO Provide alias suggestions
+ }
+ }
+
+ private StringRange consumeAlias(final StringReader reader) {
+ final int firstSep = reader.getString().indexOf(
+ CommandDispatcher.ARGUMENT_SEPARATOR_CHAR, reader.getCursor());
+ final StringRange range = StringRange.between(
+ reader.getCursor(), firstSep == -1 ? reader.getTotalLength() : firstSep);
+ reader.setCursor(range.getEnd());
+ return range;
+ }
+
+ /**
+ * Returns alias suggestions for the given input.
+ *
+ * @param reader the input reader
+ * @param contextSoFar an empty context
+ * @return a future that completes with the suggestions
+ */
+ private CompletableFuture contextSoFar) {
+ final S source = contextSoFar.getSource();
+ final String input = reader.getRead();
+
+ final Collection getRoot() {
+ return this.dispatcher.getRoot();
+ }*/
+}