package com.github.otbproject.otbproject.gui; import com.github.otbproject.otbproject.bot.Bot; import com.github.otbproject.otbproject.bot.Control; import com.github.otbproject.otbproject.channel.Channel; import com.github.otbproject.otbproject.channel.ChannelProxy; import com.github.otbproject.otbproject.cli.commands.CmdParser; import com.github.otbproject.otbproject.command.Aliases; import com.github.otbproject.otbproject.command.Commands; import com.github.otbproject.otbproject.fs.FSUtil; import com.github.otbproject.otbproject.messages.internal.InternalMessageSender; import com.github.otbproject.otbproject.util.JsonHandler; import javafx.fxml.FXML; import javafx.scene.control.MenuItem; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.input.KeyEvent; import org.apache.commons.lang3.StringUtils; import java.io.File; import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; public class GuiController { @FXML public TextArea logOutput; @FXML public TextArea commandsOutput; @FXML public TextField commandsInput; @FXML public TextArea cliOutput; @FXML public MenuItem openBaseDir; @FXML public MenuItem quit; @FXML public MenuItem botStart; @FXML public MenuItem botStop; @FXML public MenuItem botRestart; @FXML public MenuItem webOpen; protected final List<String> history = new ArrayList<>(); protected int historyPointer = 0; private List<String> tabCompleteList = Collections.emptyList(); private int tabCompleteIndex = 0; private static final int MAX_HISTORY_SIZE = 100; private static final String HISTORY_PATH = FSUtil.dataDir() + File.separator + FSUtil.GUI_HISTORY_FILE; @FXML public void command(KeyEvent event) { switch (event.getCode()) { case ENTER: String input = commandsInput.getText(); if (input.isEmpty()) { break; } cliOutput.appendText(input + "\n"); commandsInput.clear(); GuiApplication.setInputInactive(); CmdParser.processLineAndThen(input, InternalMessageSender.CLI, s -> GuiUtils.runSafe(() -> cliOutput.appendText((s.isEmpty() ? "" : (s + "\n")) + "> ")), GuiApplication::setInputActive); boolean writeHistory = false; if (history.isEmpty() || !history.get(history.size() - 1).equals(input)) { history.add(input); writeHistory = true; } while (history.size() > MAX_HISTORY_SIZE) { history.remove(0); writeHistory = true; } if (writeHistory) { writeHistory(); } historyPointer = history.size(); notTabCompleting(); break; case UP: if (historyPointer == 0) { break; } --historyPointer; commandsInput.setText(history.get(historyPointer)); commandsInput.positionCaret(commandsInput.getText().length()); event.consume(); notTabCompleting(); break; case DOWN: if (historyPointer == history.size()) { break; } else if (historyPointer == history.size() - 1) { commandsInput.clear(); historyPointer = history.size(); break; } historyPointer++; commandsInput.setText(history.get(historyPointer)); notTabCompleting(); break; case TAB: input = commandsInput.getText(); List<String> parts = Stream.of(input.split(" ")).filter(s -> !s.isEmpty()).collect(Collectors.toList()); if (input.isEmpty() || input.endsWith(" ")) { parts.add(""); } if (parts.size() == 1) { tabComplete(parts, 0, CmdParser.getCommands()); } else if (parts.size() == 2 && CmdParser.getCommands().contains(parts.get(0))) { switch (parts.get(0)) { case CmdParser.CLEAR: tabComplete(parts, 1, CmdParser.ClearTargets.targets); break; case CmdParser.EXEC: case CmdParser.RESET: tabComplete(parts, 1, Control.bot().channelManager().list()); break; case CmdParser.LEAVECHANNEL: tabComplete(parts, 1, Control.bot().channelManager().list(), s -> !Channel.isBotChannel(s)); break; case CmdParser.HELP: tabComplete(parts, 1, CmdParser.getCommands()); break; default: // defaults to no tab completion for first argument } } else if (parts.size() == 3 && parts.get(0).equals(CmdParser.EXEC)) { Bot bot = Control.bot(); Optional<ChannelProxy> optional = bot.channelManager().get(parts.get(1)); if (optional.isPresent() && optional.get().isInChannel()) { ChannelProxy channel = optional.get(); List<String> list = Commands.getCommands(channel.getMainDatabaseWrapper()); list = (list == null) ? new ArrayList<>() : list; addIfNotNull(list, Aliases.getAliases(channel.getMainDatabaseWrapper())); if (Channel.isBotChannel(channel.getName())) { addIfNotNull(list, Commands.getCommands(Control.bot().getBotDB())); addIfNotNull(list, Aliases.getAliases(Control.bot().getBotDB())); } tabComplete(parts, 2, list); } } else { notTabCompleting(); } commandsInput.positionCaret(commandsInput.getText().length()); break; case ESCAPE: commandsInput.clear(); historyPointer = history.size(); notTabCompleting(); break; default: notTabCompleting(); } } private void tabComplete(List<String> parts, int index, Collection<String> completions) { tabComplete(parts, index, completions, s -> true); } private void tabComplete(List<String> parts, int index, Collection<String> completions, Predicate<String> predicate) { if (tabCompleteIndex != 0) { multipleTabComplete(parts, index); return; } tabCompleteList = completions.stream() .filter(string -> StringUtils.startsWithIgnoreCase(string, parts.get(index))) .filter(predicate) .sorted() .collect(Collectors.toList()); if (tabCompleteList.size() == 1) { commandsInput.setText(getInputPartsTillIndex(parts, index) + tabCompleteList.get(0) + " "); } else if (tabCompleteList.size() != 0) { multipleTabComplete(parts, index); } else { notTabCompleting(); } } private void multipleTabComplete(List<String> parts, int index) { if (tabCompleteIndex >= tabCompleteList.size()) { tabCompleteIndex = 0; } commandsInput.setText(getInputPartsTillIndex(parts, index) + tabCompleteList.get(tabCompleteIndex)); tabCompleteIndex++; } private String getInputPartsTillIndex(List<String> parts, int index) { List<String> subList = parts.subList(0, ((index > parts.size()) ? parts.size() : index)); String input = subList.stream().collect(Collectors.joining(" ")); return input + ((input.length() == 0) ? "" : " "); } private void notTabCompleting() { tabCompleteList.clear(); tabCompleteIndex = 0; } private static void addIfNotNull(List<String> l1, List<String> l2) { if (l2 != null) { l1.addAll(l2); } } void writeHistory() { JsonHandler.writeValue(HISTORY_PATH, history); } void readHistory() { JsonHandler.readValue(HISTORY_PATH, String[].class).ifPresent(strings -> history.addAll(Arrays.asList(strings))); historyPointer = history.size(); } }