/* * Copyright 2014 MovingBlocks * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.terasology.logic.console.ui; import com.google.common.collect.Collections2; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.terasology.entitySystem.entity.EntityRef; import org.terasology.logic.console.Console; import org.terasology.logic.console.ConsoleColors; import org.terasology.logic.console.CoreMessageType; import org.terasology.logic.console.Message; import org.terasology.logic.console.commandSystem.ConsoleCommand; import org.terasology.logic.console.commandSystem.exceptions.CommandSuggestionException; import org.terasology.logic.players.LocalPlayer; import org.terasology.naming.Name; import org.terasology.rendering.FontColor; import org.terasology.utilities.CamelCaseMatcher; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; /** * A text completion engine with cycle-through functionality * */ public class CyclingTabCompletionEngine implements TabCompletionEngine { private final Console console; private int selectionIndex; private List<String> previousMatches; //Alphabetically ordered list of matches private Message previousMessage; private Collection<String> commandNames; private String query; private LocalPlayer localPlayer; public CyclingTabCompletionEngine(Console console, LocalPlayer localPlayer) { this.console = console; this.localPlayer = localPlayer; } private boolean updateCommandNamesIfNecessary() { Collection<ConsoleCommand> commands = console.getCommands(); if (commandNames != null && commandNames.size() == commands.size()) { return false; } commandNames = Collections2.transform(commands, input -> input.getName().toString()); return true; } private Set<String> findMatches(Name commandName, List<String> commandParameters, ConsoleCommand command, int suggestedIndex) { if (suggestedIndex <= 0) { updateCommandNamesIfNecessary(); return CamelCaseMatcher.getMatches(commandName.toString(), commandNames, true); } else if (command == null) { return null; } List<String> finishedParameters = Lists.newArrayList(); for (int i = 0; i < suggestedIndex - 1; i++) { finishedParameters.add(commandParameters.get(i)); } String currentValue = commandParameters.size() >= suggestedIndex ? commandParameters.get(suggestedIndex - 1) : null; EntityRef sender = localPlayer.getClientEntity(); try { return command.suggest(currentValue, finishedParameters, sender); } catch (CommandSuggestionException e) { String causeMessage = e.getLocalizedMessage(); if (causeMessage == null) { Throwable cause = e.getCause(); causeMessage = cause.getLocalizedMessage(); if (causeMessage == null || causeMessage.isEmpty()) { causeMessage = cause.toString(); if (causeMessage == null || causeMessage.isEmpty()) { return null; } } } console.addMessage("Error when suggesting command: " + causeMessage, CoreMessageType.ERROR); return null; } } @Override public String complete(String rawCommand) { if (rawCommand.length() <= 0) { reset(); previousMessage = new Message("Type 'help' to list all commands."); console.addMessage(previousMessage); return null; } else if (query == null) { query = rawCommand; } String commandNameRaw = console.processCommandName(query); Name commandName = new Name(commandNameRaw); List<String> commandParameters = console.processParameters(query); ConsoleCommand command = console.getCommand(commandName); int suggestedIndex = commandParameters.size() + (query.charAt(query.length() - 1) == ' ' ? 1 : 0); Set<String> matches = findMatches(commandName, commandParameters, command, suggestedIndex); if (matches == null || matches.size() <= 0) { return query; } if (previousMatches == null || !matches.equals(Sets.newHashSet(previousMatches))) { reset(false); if (matches.size() == 1) { return generateResult(matches.iterator().next(), commandName, commandParameters, suggestedIndex); } /* if (matches.length > MAX_CYCLES) { console.addMessage(new Message("Too many hits, please refine your search")); return query; }*/ //TODO Find out a better way to handle too many results while returning useful information previousMatches = Lists.newArrayList(matches); Collections.sort(previousMatches); } StringBuilder matchMessageString = new StringBuilder(); for (int i = 0; i < previousMatches.size(); i++) { if (i > 0) { matchMessageString.append(' '); } String match = previousMatches.get(i); if (selectionIndex == i) { match = FontColor.getColored(match, ConsoleColors.COMMAND); } matchMessageString.append(match); } Message matchMessage = new Message(matchMessageString.toString()); String suggestion = previousMatches.get(selectionIndex); if (previousMessage != null) { console.replaceMessage(previousMessage, matchMessage); } else { console.addMessage(matchMessage); } previousMessage = matchMessage; selectionIndex = (selectionIndex + 1) % previousMatches.size(); return generateResult(suggestion, commandName, commandParameters, suggestedIndex); } private String generateResult(String suggestion, Name commandName, List<String> commandParameters, int suggestedIndex) { if (suggestedIndex <= 0) { return suggestion; } else { StringBuilder result = new StringBuilder(); result.append(commandName.toString()); for (int i = 0; i < suggestedIndex - 1; i++) { result.append(" "); result.append(commandParameters.get(i)); } result.append(" "); result.append(suggestion); return result.toString(); } } private void reset(boolean removeQuery) { if (previousMessage != null) { console.removeMessage(previousMessage); } if (removeQuery) { query = null; } previousMessage = null; previousMatches = null; selectionIndex = 0; } @Override public void reset() { reset(true); } }