/* * License: source-license.txt * If this code is used independently, copy the license here. */ package wombat.gui.text; import java.awt.Dimension; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.util.ArrayList; import java.util.List; import java.util.Stack; import javax.swing.AbstractAction; import javax.swing.KeyStroke; import javax.swing.text.BadLocationException; import wombat.gui.frames.MainFrame; import wombat.util.Options; import wombat.util.errors.ErrorManager; /** * Special text area for running REPL text areas. * * When you hit enter, run the command if it's at least possibly valid. * * Use Ctrl-Enter to override this check and guarantee it runs. */ public class REPLTextArea extends SchemeTextArea { private static final long serialVersionUID = 8753865168892947915L; // Store previously entered commands. List<String> commandHistory; int currentCommand = 0; /** * Create a new REPL area. */ public REPLTextArea() { super(false, false); commandHistory = new ArrayList<String>(); setPreferredSize(new Dimension(100, 100)); // Restore previously saved commands if (!Options.SavedHistory.isEmpty()) { for (String cmd : Options.SavedHistory.split("\\`\\|")) { commandHistory.add(cmd); currentCommand++; } } // When the user hits the 'ENTER' key, check for a complete command. code.getInputMap().put( KeyStroke.getKeyStroke("ENTER"), new AbstractAction() { private static final long serialVersionUID = 723647997099071931L; public void actionPerformed(ActionEvent e) { if (MainFrame.Singleton().ToolBarStop.isEnabled()) return; checkRun(); } }); // On CTRL-ENTER (command-ENTER on a mac). code.getInputMap().put( KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ), new AbstractAction() { private static final long serialVersionUID = 723647997099071931L; public void actionPerformed(ActionEvent e) { if (MainFrame.Singleton().ToolBarStop.isEnabled()) return; if (getText().trim().isEmpty()) return; commandHistory.add(getText()); currentCommand = commandHistory.size(); MainFrame.Singleton().doCommand(getText()); setText(""); } }); // When the user hits the up arrow, it they are on the first line, reload the previous command. // On the down arrow go to the next command. code.addKeyListener(new KeyListener() { public void keyPressed(KeyEvent event) { if (event.getKeyCode() == KeyEvent.VK_UP) { if (getText().lastIndexOf("\n", code.getCaretPosition() - 1) == -1) { if (currentCommand == commandHistory.size()) commandHistory.add(getText()); if (currentCommand == 0) return; currentCommand--; setText(commandHistory.get(currentCommand)); code.setCaretPosition(code.getDocument().getLength()); // Don't actually go up. That would be silly. event.consume(); } } if (event.getKeyCode() == KeyEvent.VK_DOWN) { if (getText().indexOf("\n", code.getCaretPosition()) == -1) { if (currentCommand == commandHistory.size()) { setText(""); // Clear at the end } else if (currentCommand == commandHistory.size() - 1) { setText(commandHistory.remove(commandHistory.size() - 1)); } else { currentCommand++; setText(commandHistory.get(currentCommand)); code.setCaretPosition(code.getDocument().getLength()); // Not really necessary because you won't be able to go down anyways, but gogoparallel. event.consume(); } } } } @Override public void keyReleased(KeyEvent arg0) {} @Override public void keyTyped(KeyEvent arg0) { } }); } /** * Get up to max history items. * @param max The most history items to return. * @return A `| delimited history list. */ public String getHistory(int max) { try { int lo = Math.min(commandHistory.size() - 1, Math.max(0, currentCommand - max)); int hi = Math.min(commandHistory.size() - 1, Math.max(0, currentCommand)); if (lo == -1) { return ""; } StringBuffer buf = new StringBuffer(); for (int i = lo; i <= hi; i++) { buf.append(commandHistory.get(i)); buf.append("`|"); } if (lo != hi) return buf.substring(0, buf.length() - 2); else return ""; } catch(Exception e) { ErrorManager.logError("Unable to load saved history: " + e.getMessage()); e.printStackTrace(); return ""; } } /** * Check if the command should run, run it if it should. * * To run: * - Last line * - Brackets are matched */ protected void checkRun() { // The cursor should be on the last line and there should be at least some text. if (!getText().trim().isEmpty() && getText().substring(code.getCaretPosition()).trim().isEmpty()) { // Check to see that we have a matched pair of brackets. Stack<Character> brackets = new Stack<Character>(); char[] cs = getText().toCharArray(); char c; for (int i = 0; i < cs.length; i++) { c = cs[i]; // Skip character literals if (c == '#' && cs[i + 1] == '\\') { i += 2; continue; } // Skip strings if (c == '"') { int end = getText().indexOf('"', i + 1); i += (end - i); } if (c == '(') brackets.push(')'); else if (c == '[') brackets.push(']'); else if (c == ')' || c == ']') if (!brackets.empty() && brackets.peek() == c) brackets.pop(); else return; } // This means we matched them all. if (brackets.empty()) { commandHistory.add(getText()); currentCommand = commandHistory.size(); MainFrame.Singleton().doCommand(getText()); setText(""); return; } } // Either we didn't match the brackets or we aren't on the last line. Insert the return normally. try { code.getDocument().insertString(code.getCaretPosition(), SchemeTextArea.NL, null); } catch (BadLocationException ble) { System.err.println("badwolf"); } tab(); } }