Clover coverage report - DrJava Test Coverage (drjava-20120304-r5456)
Coverage timestamp: Sun Mar 4 2012 03:13:23 CST
file stats: LOC: 925   Methods: 79
NCLOC: 562   Classes: 3
 
 Source file Conditionals Statements Methods TOTAL
InteractionsController.java 32.5% 69.3% 57% 60.8%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK
 2    *
 3    * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
 4    * All rights reserved.
 5    *
 6    * Redistribution and use in source and binary forms, with or without
 7    * modification, are permitted provided that the following conditions are met:
 8    * * Redistributions of source code must retain the above copyright
 9    * notice, this list of conditions and the following disclaimer.
 10    * * Redistributions in binary form must reproduce the above copyright
 11    * notice, this list of conditions and the following disclaimer in the
 12    * documentation and/or other materials provided with the distribution.
 13    * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 14    * names of its contributors may be used to endorse or promote products
 15    * derived from this software without specific prior written permission.
 16    *
 17    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20    * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24    * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28    *
 29    * This software is Open Source Initiative approved Open Source Software.
 30    * Open Source Initative Approved is a trademark of the Open Source Initiative.
 31    *
 32    * This file is part of DrJava. Download the current version of this project
 33    * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 34    *
 35    * END_COPYRIGHT_BLOCK*/
 36   
 37    package edu.rice.cs.drjava.ui;
 38   
 39    import java.awt.Toolkit;
 40    import java.awt.Color;
 41    import java.awt.EventQueue;
 42    import java.awt.RenderingHints;
 43    import java.awt.Graphics;
 44    import java.awt.Graphics2D;
 45    import java.awt.event.ActionEvent;
 46    import java.awt.event.KeyEvent;
 47    import java.awt.event.FocusListener;
 48   
 49    import java.io.File;
 50   
 51    import java.util.Vector;
 52    import java.util.ArrayList;
 53    import java.util.EventListener;
 54   
 55    import javax.swing.AbstractAction;
 56    import javax.swing.Action;
 57    import javax.swing.ActionMap;
 58    import javax.swing.BorderFactory;
 59    import javax.swing.InputMap;
 60    import javax.swing.JTextArea;
 61    import javax.swing.KeyStroke;
 62    import javax.swing.border.Border;
 63    import javax.swing.text.AttributeSet;
 64    import javax.swing.text.BadLocationException;
 65    import javax.swing.text.DefaultEditorKit;
 66    import javax.swing.text.MutableAttributeSet;
 67    import javax.swing.text.SimpleAttributeSet;
 68    import javax.swing.text.StyleConstants;
 69   
 70    import javax.swing.text.Document;
 71    import javax.swing.undo.UndoManager;
 72    import javax.swing.undo.CannotUndoException;
 73    import javax.swing.undo.CannotRedoException;
 74    import javax.swing.event.UndoableEditListener;
 75    import javax.swing.event.UndoableEditEvent;
 76   
 77    import edu.rice.cs.drjava.DrJava;
 78    import edu.rice.cs.drjava.config.OptionConstants;
 79    import edu.rice.cs.drjava.config.OptionListener;
 80    import edu.rice.cs.drjava.config.OptionEvent;
 81    import edu.rice.cs.drjava.model.definitions.indent.Indenter;
 82    import edu.rice.cs.drjava.model.repl.InputListener;
 83    import edu.rice.cs.drjava.model.repl.InteractionsDocument;
 84    import edu.rice.cs.drjava.model.repl.InteractionsDJDocument;
 85    import edu.rice.cs.drjava.model.repl.InteractionsListener;
 86    import edu.rice.cs.drjava.model.repl.InteractionsModel;
 87   
 88    import edu.rice.cs.drjava.config.OptionConstants;
 89   
 90    import edu.rice.cs.util.swing.DelegatingAction;
 91   
 92    import edu.rice.cs.plt.lambda.Lambda;
 93    import edu.rice.cs.plt.concurrent.CompletionMonitor;
 94    import edu.rice.cs.util.text.ConsoleDocument;
 95    import edu.rice.cs.util.UnexpectedException;
 96   
 97    import static edu.rice.cs.plt.debug.DebugUtil.debug;
 98    /* TODO: clean up mixed references to _adapter and _doc which point to almost the same thing (an
 99    * InteractionsDJDocument versus an InteractionsDocument. */
 100   
 101    /** This class installs listeners and actions between an InteractionsDocument (the model) and an InteractionsPane
 102    * (the view). We may want to refactor this class into a different package. <p>
 103    * (The PopupConsole was introduced in version 1.29 of this file and subsequently removed.)
 104    *
 105    * @version $Id: InteractionsController.java 5362 2010-08-14 01:49:04Z mgricken $
 106    */
 107    public class InteractionsController extends AbstractConsoleController {
 108   
 109    /* InteractionsDocument _adapter is inherited from AbstractConsoleController. */
 110    /* InteractionsPane _pane is inherited from AbstractConsoleController. */
 111   
 112    private static final String INPUT_ENTERED_NAME = "Input Entered";
 113    private static final String INSERT_NEWLINE_NAME = "Insert Newline";
 114    private static final String INSERT_END_OF_STREAM = "Insert End of Stream";
 115    private static final String UNDO_NAME = "Undo";
 116    private static final String REDO_NAME = "Redo";
 117   
 118    /** Style for System.in box */
 119    public static final String INPUT_BOX_STYLE = "input.box.style";
 120   
 121    /** The symbol used in the document for the input box. */
 122    public static final String INPUT_BOX_SYMBOL = "[DrJava Input Box]";
 123   
 124    /** InteractionsModel to handle interpretation. */
 125    private volatile InteractionsModel _model;
 126   
 127    /** GUI-agnostic interactions document from the model. */
 128    private volatile InteractionsDocument _doc;
 129   
 130    /** Style to use for error messages. */
 131    private volatile SimpleAttributeSet _errStyle;
 132   
 133    /** Style to use for debug messages. */
 134    private final SimpleAttributeSet _debugStyle;
 135   
 136    /** Lambda used to input text into the embedded System.in input box. */
 137    private volatile Lambda<String, String> _insertTextCommand;
 138   
 139    /** Runnable command used to force the System.in input to complete <p>
 140    * <b>NOTE:</b> This command must be executed on swing's event handling thread.
 141    */
 142    private volatile Runnable _inputCompletionCommand;
 143   
 144    /** Runnable command that disables the "Close System.in" menu command. */
 145    private final Runnable _disableCloseSystemInMenuItemCommand;
 146   
 147    /** Default implementation of the insert text in input command */
 148    private static final Lambda<String, String> _defaultInsertTextCommand =
 149    new Lambda<String,String>() {
 150  0 public String value(String input) {
 151  0 throw new UnsupportedOperationException("Cannot insert text. There is no console input in progress");
 152    }
 153    };
 154   
 155    /** Default implementation of the input completion command */
 156    private static final Runnable _defaultInputCompletionCommand =
 157  10 new Runnable() { public void run() { /* Do nothing */ } };
 158   
 159    /** A temporary variable used to hold a box allocated inside getConsoleInput below. */
 160    private volatile InputBox _box;
 161    /** A temporary variable used to hold the result fetched from _box in getConsoleInput below. */
 162    private volatile String _result;
 163    /** A variable indicating whether the input stream has been closed. */
 164    private volatile boolean _endOfStream = false;
 165   
 166    /** Listens for input requests from System.in, displaying an input box as needed. */
 167    protected volatile InputListener _inputListener = new InputListener() {
 168  1 public String getConsoleInput() {
 169  0 if (_endOfStream) return ""; // input stream has been closed, don't ask for more input
 170  1 final CompletionMonitor completionMonitor = new CompletionMonitor();
 171  1 _box = new InputBox(_endOfStream);
 172    // add all focus listeners to the Input Box
 173  1 for(FocusListener fl: _undoRedoInteractionFocusListeners) {
 174  0 _box.addFocusListener(fl);
 175    }
 176   
 177    // Embed the input box into the interactions pane. This operation must be performed in the UI thread
 178  1 EventQueue.invokeLater(new Runnable() { // why EventQueue.invokeLater?
 179  1 public void run() {
 180   
 181    // These commands only run in the event thread
 182  1 final Lambda<String,String> insertTextCommand = _box.makeInsertTextCommand(); // command for testing
 183   
 184  1 final Runnable inputCompletionCommand = new Runnable() { // command for terminating each input interaction
 185  1 public void run() {
 186  ? assert EventQueue.isDispatchThread();
 187    // Reset the commands to their default inactive state
 188  1 _setConsoleInputCommands(_defaultInputCompletionCommand, _defaultInsertTextCommand);
 189   
 190  1 _box.disableInputs();
 191  1 _result = _box.getText();
 192  1 if (_box.wasClosedWithEnter()) {
 193  0 _result += "\n";
 194    }
 195  1 setEndOfStream(_box.isEndOfStream());
 196   
 197    /* Move the cursor back to the end of the interactions pane while preventing _doc from changing in the
 198    * interim. */
 199  1 _pane.setEditable(true);
 200  1 _pane.setCaretPosition(_doc.getLength());
 201  1 _pane.requestFocusInWindow();
 202   
 203    // use undo/redo for the Interactions Pane again
 204  1 _undoAction.setDelegatee(_pane.getUndoAction());
 205  1 _redoAction.setDelegatee(_pane.getRedoAction());
 206   
 207  1 completionMonitor.signal();
 208    }
 209    };
 210   
 211  1 _box.setInputCompletionCommand(inputCompletionCommand);
 212  1 _setConsoleInputCommands(inputCompletionCommand, insertTextCommand);
 213  1 _pane.setEditable(true);
 214   
 215    // create an empty MutableAttributeSet for _box
 216  1 MutableAttributeSet inputAttributes = new SimpleAttributeSet();
 217   
 218    // initialize MutableAttributeSet to the attributes of the _box component
 219  1 StyleConstants.setComponent(inputAttributes, _box);
 220   
 221    /* Insert box in document. */
 222  1 _doc.insertBeforeLastPrompt(" ", InteractionsDocument.DEFAULT_STYLE);
 223   
 224    // bind INPUT_BOX_STYLE to inputAttributes in the associated InteractionsDJDocument
 225  1 _interactionsDJDocument.setDocStyle(INPUT_BOX_STYLE, inputAttributes);
 226   
 227    // and insert the symbol for the input box with the correct style (identifying it as our InputBox)
 228  1 _doc.insertBeforeLastPrompt(INPUT_BOX_SYMBOL, INPUT_BOX_STYLE);
 229   
 230  1 _doc.insertBeforeLastPrompt("\n", InteractionsDocument.DEFAULT_STYLE);
 231   
 232  1 _box.setVisible(true);
 233  1 EventQueue.invokeLater(new Runnable() { public void run() { _box.requestFocusInWindow(); } });
 234   
 235  1 _undoAction.setDelegatee(_box.getUndoAction());
 236  1 _redoAction.setDelegatee(_box.getRedoAction());
 237  1 _pane.setEditable(false);
 238    }
 239    });
 240  1 fireConsoleInputStarted();
 241   
 242    // Wait for the inputCompletionCommand to be invoked
 243  1 completionMonitor.attemptEnsureSignaled();
 244   
 245  1 fireConsoleInputCompleted(_result);
 246   
 247  1 return _result;
 248    }
 249    };
 250   
 251    private ArrayList<ConsoleStateListener> _consoleStateListeners;
 252   
 253    private InteractionsListener _viewListener = new InteractionsListener() {
 254  35 public void interactionStarted() { }
 255  33 public void interactionEnded() { _pane.requestFocusInWindow(); }
 256  0 public void interactionErrorOccurred(int offset, int length) { }
 257   
 258  21 public void interpreterResetting() {
 259  21 assert EventQueue.isDispatchThread();
 260  21 _interactionsDJDocument.clearColoring();
 261  21 _endOfStream = false;
 262    }
 263   
 264  25 public void interpreterReady(File wd) { }
 265  0 public void interpreterResetFailed(Throwable t) { }
 266  1 public void interpreterExited(int status) { }
 267  1 public void interpreterChanged(boolean inProgress) { }
 268  0 public void interactionIncomplete() { }
 269    };
 270   
 271    /** Glue together the given model and a new view.
 272    * @param model An InteractionsModel
 273    * @param adapter InteractionsDJDocument being used by the model's doc
 274    */
 275  150 public InteractionsController(final InteractionsModel model,
 276    InteractionsDJDocument adapter,
 277    Runnable disableCloseSystemInMenuItemCommand) {
 278  150 this(model, adapter, new InteractionsPane(adapter) { // creates InteractionsPane
 279  0 public int getPromptPos() { return model.getDocument().getPromptPos(); }
 280    }, disableCloseSystemInMenuItemCommand);
 281  150 _undoAction.setDelegatee(_pane.getUndoAction());
 282  150 _redoAction.setDelegatee(_pane.getRedoAction());
 283    }
 284   
 285    /** Glue together the given model and view.
 286    * @param model An InteractionsModel
 287    * @param adapter InteractionsDJDocument being used by the model's doc
 288    * @param pane An InteractionsPane
 289    */
 290  165 public InteractionsController(InteractionsModel model,
 291    InteractionsDJDocument adapter,
 292    InteractionsPane pane,
 293    Runnable disableCloseSystemInMenuItemCommand) {
 294  165 super(adapter, pane);
 295  165 _disableCloseSystemInMenuItemCommand = disableCloseSystemInMenuItemCommand;
 296  165 DefaultEditorKit d = InteractionsPane.EDITOR_KIT;
 297   
 298  165 for (Action a : d.getActions()) {
 299  165 if (a.getValue(Action.NAME).equals(DefaultEditorKit.upAction)) defaultUpAction = a;
 300  165 if (a.getValue(Action.NAME).equals(DefaultEditorKit.downAction)) defaultDownAction = a;
 301    }
 302   
 303  165 _model = model;
 304  165 _doc = model.getDocument();
 305  165 _errStyle = new SimpleAttributeSet();
 306  165 _debugStyle = new SimpleAttributeSet();
 307   
 308  165 _model.setInputListener(_inputListener);
 309  165 _model.addListener(_viewListener);
 310  165 _model.setUpPane(pane); // sets the interactions pane within the model and initializes the caret
 311   
 312  165 _inputCompletionCommand = _defaultInputCompletionCommand;
 313  165 _insertTextCommand = _defaultInsertTextCommand;
 314  165 _consoleStateListeners = new ArrayList<ConsoleStateListener>();
 315    // _pane.addCaretListener(new CaretListener() { // Update the cachedCaretPosition
 316    // public void caretUpdate(CaretEvent e) {
 317    // _log.log("Caret Event: " + e + " from source " + e.getSource());
 318    //// setCachedCaretPos(e.getDot());
 319    // }
 320    // });
 321   
 322    // Add key binding option listener for Input Box.
 323    // Done here, not in InputBox's constructor, so we only create one. Otherwise we might
 324    // create one per InputBox, and it would be difficult to remove them again.
 325  165 DrJava.getConfig().addOptionListener(OptionConstants.KEY_UNDO, _keyBindingOptionListener);
 326  165 DrJava.getConfig().addOptionListener(OptionConstants.KEY_REDO, _keyBindingOptionListener);
 327   
 328  165 _init(); // residual superclass initialization
 329    }
 330   
 331  1 public void addConsoleStateListener(ConsoleStateListener listener) {
 332  1 _consoleStateListeners.add(listener);
 333    }
 334   
 335  0 public void removeConsoleStateListener(ConsoleStateListener listener) {
 336  0 _consoleStateListeners.remove(listener);
 337    }
 338   
 339  1 private void fireConsoleInputStarted() {
 340  1 for(ConsoleStateListener listener : _consoleStateListeners) {
 341  1 listener.consoleInputStarted(this);
 342    }
 343    }
 344   
 345  1 private void fireConsoleInputCompleted(String text) {
 346  1 for(ConsoleStateListener listener : _consoleStateListeners) { listener.consoleInputCompleted(text, this); }
 347    }
 348   
 349    /** Sets the end of stream flag. */
 350  1 public void setEndOfStream(boolean tf) {
 351  1 _endOfStream = tf;
 352  1 if (_box != null) { _box.setEndOfStream(tf); }
 353  0 if (tf) { _disableCloseSystemInMenuItemCommand.run(); }
 354    }
 355   
 356   
 357    /** Gets the input listener for console input requests. ONLY used in unit tests.
 358    * @return the input listener for console input requests.
 359    */
 360  1 public InputListener getInputListener() { return _inputListener; }
 361   
 362    /** Forces console input to complete without the user hitting <Enter>. Called by MainFrame when reset is called so
 363    * that this lock is released. This method is thread safe.
 364    * @throws UnsupportedOperationException If the interactions pane is not receiving console input
 365    */
 366  11 public void interruptConsoleInput() { EventQueue.invokeLater(_inputCompletionCommand); }
 367   
 368    /** Inserts text into the console. Can only be called from the event thread. ONLY used in unit tests.
 369    * @param input The text to insert into the console input box
 370    * @throws UnsupportedOperationException If the the interactions pane is not receiving console input
 371    */
 372  1 public void insertConsoleText(String input) { _insertTextCommand.value(input); }
 373   
 374    /** Accessor method for the InteractionsModel.
 375    * @return the interactions model
 376    */
 377  0 public InteractionsModel getInteractionsModel() { return _model; }
 378   
 379    /** Allows the abstract superclass to use the document.
 380    * @return the InteractionsDocument
 381    */
 382  24 public ConsoleDocument getConsoleDoc() { return _doc; }
 383   
 384    /** Accessor method for the InteractionsDocument. */
 385  0 public InteractionsDocument getDocument() { return _doc; }
 386   
 387    /** Adds AttributeSets as named styles to the document adapter. */
 388  165 protected void _addDocumentStyles() {
 389    // Add AbstractConsoleController styles
 390  165 super._addDocumentStyles();
 391   
 392    // Error
 393  165 _errStyle.addAttributes(_defaultStyle);
 394  165 _errStyle.addAttribute(StyleConstants.Foreground,
 395    DrJava.getConfig().getSetting(OptionConstants.INTERACTIONS_ERROR_COLOR));
 396  165 _errStyle.addAttribute(StyleConstants.Bold, Boolean.TRUE);
 397  165 _interactionsDJDocument.setDocStyle(InteractionsDocument.ERROR_STYLE, _errStyle);
 398  165 DrJava.getConfig().addOptionListener(OptionConstants.INTERACTIONS_ERROR_COLOR, new OptionListener<Color>() {
 399  0 public void optionChanged(OptionEvent<Color> oe) {
 400  0 _errStyle.addAttribute(StyleConstants.Foreground, oe.value);
 401    }
 402    });
 403   
 404    // Debug
 405  165 _debugStyle.addAttributes(_defaultStyle);
 406  165 _debugStyle.addAttribute(StyleConstants.Foreground,
 407    DrJava.getConfig().getSetting(OptionConstants.DEBUG_MESSAGE_COLOR));
 408  165 _debugStyle.addAttribute(StyleConstants.Bold, Boolean.TRUE);
 409  165 _interactionsDJDocument.setDocStyle(InteractionsDocument.DEBUGGER_STYLE, _debugStyle);
 410  165 DrJava.getConfig().addOptionListener(OptionConstants.DEBUG_MESSAGE_COLOR, new OptionListener<Color>() {
 411  0 public void optionChanged(OptionEvent<Color> oe) {
 412  0 _debugStyle.addAttribute(StyleConstants.Foreground, oe.value);
 413    }
 414    });
 415    }
 416   
 417    /** Adds listeners to the model. */
 418  165 protected void _setupModel() { _doc.setBeep(_pane.getBeep()); }
 419   
 420    /** Adds actions to the view. */
 421  165 protected void _setupView() {
 422  165 super._setupView();
 423   
 424    // Get proper cross-platform mask.
 425  165 int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
 426   
 427    // Add actions with keystrokes
 428  165 _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), evalAction);
 429  165 _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, java.awt.Event.SHIFT_MASK), newLineAction);
 430  165 _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_B, mask), clearCurrentAction);
 431   
 432    // Up and down need to be bound both for keypad and not
 433  165 _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_KP_UP, 0), moveUpAction);
 434  165 _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), moveUpAction);
 435  165 _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_UP, mask), historyPrevAction);
 436  165 _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_KP_DOWN, 0), moveDownAction);
 437  165 _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), moveDownAction);
 438  165 _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, mask), historyNextAction);
 439  165 _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), historyReverseSearchAction);
 440  165 _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, java.awt.Event.SHIFT_MASK),
 441    historyForwardSearchAction);
 442   
 443    // _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), indentKeyActionTab);
 444    // _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, java.awt.Event.SHIFT_MASK), indentKeyActionLine);
 445   
 446    // Potential additions: actions must be copied from DefinitionsPane
 447    // _pane.addActionForKeyStroke(KeyStroke.getKeyStroke('}'), indentKeyActionCurly);
 448    // _pane.addActionForKeyStroke(KeyStroke.getKeyStroke('{'), indentKeyActionOpenCurly);
 449    // _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(':'), indentKeyActionColon);
 450   
 451    // Left needs to be prevented from rolling cursor back before the prompt.
 452    // Both left and right should lock when caret is before the prompt.
 453    // Caret is allowed before the prompt for the purposes of mouse-based copy-
 454    // and-paste.
 455  165 _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_KP_LEFT, 0), moveLeftAction);
 456  165 _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), moveLeftAction);
 457  165 _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_KP_RIGHT, 0), moveRightAction);
 458  165 _pane.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), moveRightAction);
 459   
 460    // Prevent previous word action from going past the prompt
 461  165 _pane.addActionForKeyStroke(DrJava.getConfig().getSetting(OptionConstants.KEY_PREVIOUS_WORD), prevWordAction);
 462  165 DrJava.getConfig().addOptionListener(OptionConstants.KEY_PREVIOUS_WORD, new OptionListener<Vector<KeyStroke>>() {
 463  0 public void optionChanged(OptionEvent<Vector<KeyStroke>> oe) {
 464  0 _pane.addActionForKeyStroke(DrJava.getConfig().getSetting(OptionConstants.KEY_PREVIOUS_WORD), prevWordAction);
 465    }
 466    });
 467   
 468  165 _pane.addActionForKeyStroke(DrJava.getConfig().getSetting(OptionConstants.KEY_NEXT_WORD), nextWordAction);
 469  165 DrJava.getConfig().addOptionListener(OptionConstants.KEY_NEXT_WORD, new OptionListener<Vector<KeyStroke>>() {
 470  0 public void optionChanged(OptionEvent<Vector<KeyStroke>> oe) {
 471  0 _pane.addActionForKeyStroke(DrJava.getConfig().getSetting(OptionConstants.KEY_NEXT_WORD), nextWordAction);
 472    }
 473    });
 474    }
 475   
 476    /** Sets the commands used to manipulate the console input process. Only runs in the event thread. */
 477  2 private void _setConsoleInputCommands(Runnable inputCompletionCommand, Lambda<String,String> insertTextCommand) {
 478  2 _insertTextCommand = insertTextCommand;
 479  2 _inputCompletionCommand = inputCompletionCommand;
 480    }
 481   
 482    // The fields below were made package private for testing purposes.
 483   
 484    /** Evaluates the interaction on the current line. */
 485    AbstractAction evalAction = new AbstractAction() {
 486  0 public void actionPerformed(ActionEvent e) { _model.interpretCurrentInteraction(); }
 487    };
 488   
 489    // /** Evaluates the current text following the prompt in the interactions document.*/
 490    // private void _evalCurrentInteraction() {
 491    //
 492    // if (! _interactionsDJDocument._inBlockComment()) {
 493    //
 494    // String toEval;
 495    // if (_doc.inProgress()) return; // Don't start a new interaction while one is in progress
 496    //
 497    // String text = _doc.getCurrentInteraction();
 498    // toEval = text.trim();
 499    // if (toEval.startsWith("java ")) toEval = _testClassCall(toEval);
 500    //// System.err.println("Preparing to interpret '" + text + "'");
 501    // _prepareToInterpret(text); // Writes a newLine!
 502    // }
 503    // try { _model.interpret(toEval); }
 504    // catch (Throwable t) { DrJavaErrorHandler.record(t); }
 505    // };
 506    //
 507    // /** Performs pre-interpretation preparation of the interactions document and notifies the view. Must run in the
 508    // * event thread for newline to be inserted at proper time. Assumes that Write Lock is already held. */
 509    // private void _prepareToInterpret(String text) {
 510    // _addNewline();
 511    // _notifyInteractionStarted();
 512    // _doc.setInProgress(true);
 513    // _model.setAddToHistory(text); // _document.addToHistory(text);
 514    // //Do not add to history immediately in case the user is not finished typing when they press return
 515    // }
 516    //
 517    // /** Appends a newLine to _document assuming that the Write Lock is already held. Must run in the event thread. */
 518    // private void _addNewline() { append(StringOps.NEWLINE, InteractionsDocument.DEFAULT_STYLE); }
 519   
 520    /** Recalls the previous command from the history. */
 521    AbstractAction historyPrevAction = new AbstractAction() {
 522  1 public void actionPerformed(ActionEvent e) {
 523  1 if (! _busy()) {
 524  0 if (_doc.recallPreviousInteractionInHistory()) moveToEnd();
 525  1 if (!_isCursorAfterPrompt()) moveToPrompt();
 526    }
 527    }
 528    };
 529   
 530    /** Recalls the next command from the history. */
 531    AbstractAction historyNextAction = new AbstractAction() {
 532  1 public void actionPerformed(ActionEvent e) {
 533  1 if (! _busy() && (_doc.recallNextInteractionInHistory() || !_isCursorAfterPrompt())) moveToPrompt();
 534    }
 535    };
 536   
 537    /** Added feature for up. If the cursor is on the first line of the current interaction, it goes into the history.
 538    * Otherwise, stays within the current interaction
 539    */
 540    AbstractAction moveUpAction = new AbstractAction() {
 541  0 public void actionPerformed(ActionEvent e) {
 542  0 if (! _busy()) {
 543  0 if (_shouldGoIntoHistory(_doc.getPromptPos(), _pane.getCaretPosition()))
 544  0 historyPrevAction.actionPerformed(e);
 545    else {
 546  0 defaultUpAction.actionPerformed(e);
 547  0 if (! _isCursorAfterPrompt()) moveToPrompt();
 548    }
 549    }
 550    }
 551    };
 552   
 553    /** Added feature for down. If the cursor is on the last line of the current interaction, it goes into the history.
 554    * Otherwise, stays within the current interaction
 555    */
 556    AbstractAction moveDownAction = new AbstractAction() {
 557  0 public void actionPerformed(ActionEvent e) {
 558  0 if (! _busy()) {
 559  0 if (_shouldGoIntoHistory(_pane.getCaretPosition(), _interactionsDJDocument.getLength())) {
 560  0 historyNextAction.actionPerformed(e);
 561  0 } else { defaultDownAction.actionPerformed(e); }
 562    }
 563    }
 564    };
 565   
 566    /** Tests whether or not to move into the history. Should be executed in the event thread to ensure
 567    * that caret and prompt positions are in consistent states.
 568    * @return true iff there are no "\n" characters between the start and the end
 569    */
 570  0 private boolean _shouldGoIntoHistory(int start, int end) {
 571  0 if (_isCursorAfterPrompt() && end >= start) {
 572  0 String text = "";
 573  0 try { text = _interactionsDJDocument.getText(start, end - start); }
 574    catch(BadLocationException ble) {
 575  0 throw new UnexpectedException(ble); //The conditional should prevent this from ever happening
 576    }
 577  0 if (text.indexOf("\n") != -1) return false;
 578    }
 579  0 return true;
 580    }
 581   
 582  2 private boolean _isCursorAfterPrompt() { return _pane.getCaretPosition() >= _doc.getPromptPos(); }
 583   
 584    Action defaultUpAction;
 585    Action defaultDownAction;
 586   
 587    /** Reverse searches in the history. */
 588    AbstractAction historyReverseSearchAction = new AbstractAction() {
 589  0 public void actionPerformed(ActionEvent e) {
 590  0 if (!_busy()) {
 591  0 _doc.reverseSearchInteractionsInHistory();
 592  0 moveToEnd();
 593    }
 594    }
 595    };
 596   
 597    /** Forward searches in the history. */
 598    AbstractAction historyForwardSearchAction = new AbstractAction() {
 599  0 public void actionPerformed(ActionEvent e) {
 600  0 if (! _busy()) {
 601  0 _doc.forwardSearchInteractionsInHistory();
 602  0 moveToEnd();
 603    }
 604    }
 605    };
 606   
 607    /** Moves the caret left or wraps around. */
 608    AbstractAction moveLeftAction = new AbstractAction() {
 609  2 public void actionPerformed(ActionEvent e) {
 610  2 if (! _busy()) {
 611  2 int promptPos = _doc.getPromptPos();
 612  2 int pos = _pane.getCaretPosition();
 613  1 if (pos < promptPos) moveToPrompt();
 614  1 else if (pos == promptPos) moveToEnd(); // Wrap around to the end
 615  0 else _pane.setCaretPosition(pos - 1); // pos > promptPos
 616    }
 617    }
 618    };
 619   
 620    /** Moves the caret right or wraps around. */
 621    AbstractAction moveRightAction = new AbstractAction() {
 622  2 public void actionPerformed(ActionEvent e) {
 623  2 int pos = _pane.getCaretPosition();
 624  1 if (pos < _doc.getPromptPos()) moveToEnd();
 625  1 else if (pos >= _doc.getLength()) moveToPrompt(); // Wrap around to the star
 626    else {
 627  0 _pane.setCaretPosition(pos + 1); // position between prompt and end
 628    // setCachedCaretPos(pos + 1);
 629    }
 630    }
 631    };
 632   
 633    /** Skips back one word. Doesn't move past the prompt. */
 634    AbstractAction prevWordAction = new AbstractAction() {
 635  0 public void actionPerformed(ActionEvent e) {
 636  0 int position = _pane.getCaretPosition();
 637  0 int promptPos = _doc.getPromptPos();
 638  0 if (position < promptPos) moveToPrompt();
 639  0 else if (position == promptPos) moveToEnd(); // Wrap around to the end
 640  0 else _pane.getActionMap().get(DefaultEditorKit.previousWordAction).actionPerformed(e);
 641    }
 642    };
 643   
 644    /** Skips forward one word. Doesn't move past the prompt. */
 645    AbstractAction nextWordAction = new AbstractAction() {
 646  0 public void actionPerformed(ActionEvent e) {
 647  0 int position = _pane.getCaretPosition();
 648  0 int promptPos = _doc.getPromptPos();
 649  0 if (position < promptPos) moveToEnd();
 650  0 else if (position >= _doc.getLength()) moveToPrompt(); // Wrap around to the start
 651  0 else _pane.getActionMap().get(DefaultEditorKit.nextWordAction).actionPerformed(e);
 652    }
 653    };
 654   
 655    /** Indents the selected text. */
 656    AbstractAction indentKeyActionTab = new AbstractAction() {
 657  0 public void actionPerformed(ActionEvent e) { _pane.indent(); }
 658    };
 659   
 660    /** Indents in preparation for typing next line */
 661    AbstractAction indentKeyActionLine = new AbstractAction() {
 662  0 public void actionPerformed(ActionEvent e) {
 663  0 _doc.append("\n", null); // null style
 664  0 _pane.indent(Indenter.IndentReason.ENTER_KEY_PRESS); }
 665    };
 666   
 667    private final DelegatingAction _undoAction = new DelegatingAction();
 668    private final DelegatingAction _redoAction = new DelegatingAction();
 669    private final ArrayList<FocusListener> _undoRedoInteractionFocusListeners = new ArrayList<FocusListener>();
 670   
 671    /** Add a focus listener to the Interactions Pane and the Input Box. */
 672  38 public void addFocusListener(FocusListener listener) {
 673  38 _pane.addFocusListener(listener);
 674    // we need to store the focus listeners, because they need to be added to future
 675    // Input Boxes too.
 676  38 _undoRedoInteractionFocusListeners.add(listener);
 677  38 if (_box != null) {
 678  0 for(FocusListener fl: _undoRedoInteractionFocusListeners) {
 679  0 _box.addFocusListener(fl);
 680    }
 681    }
 682    }
 683   
 684    /** @return the undo action. */
 685  1 public Action getUndoAction() { return _undoAction; }
 686   
 687    /** @return the redo action. */
 688  0 public Action getRedoAction() { return _redoAction; }
 689   
 690    /** OptionListener responding to changes for the undo/redo key bindings. */
 691    private final OptionListener<Vector<KeyStroke>> _keyBindingOptionListener = new OptionListener<Vector<KeyStroke>>() {
 692  0 public void optionChanged(OptionEvent<Vector<KeyStroke>> oce) {
 693  0 if (_box != null) { _box.updateKeyBindings(); }
 694    }
 695    };
 696   
 697    /** A box that can be inserted into the interactions pane for separate input. Do not confuse with
 698    * edu.rice.cs.util.swing.InputBox. */
 699    private static class InputBox extends JTextArea {
 700    private static final int BORDER_WIDTH = 1;
 701    private static final int INNER_BUFFER_WIDTH = 3;
 702    private static final int OUTER_BUFFER_WIDTH = 2;
 703    private volatile Color _bgColor = DrJava.getConfig().getSetting(OptionConstants.DEFINITIONS_BACKGROUND_COLOR);
 704    private volatile Color _fgColor = DrJava.getConfig().getSetting(OptionConstants.DEFINITIONS_NORMAL_COLOR);
 705    private volatile Color _sysInColor = DrJava.getConfig().getSetting(OptionConstants.SYSTEM_IN_COLOR);
 706    private volatile boolean _antiAliasText = DrJava.getConfig().getSetting(OptionConstants.TEXT_ANTIALIAS);
 707    private volatile boolean _endOfStream = false;
 708    private volatile boolean _closedWithEnter = false;
 709    private final InputMap _oldInputMap = new InputMap();
 710   
 711  1 public InputBox(boolean endOfStream) {
 712  1 _endOfStream = endOfStream;
 713  1 setForeground(_sysInColor);
 714  1 setBackground(_bgColor);
 715  1 setCaretColor(_fgColor);
 716  1 setBorder(_createBorder());
 717  1 setLineWrap(true);
 718   
 719  1 DrJava.getConfig().addOptionListener(OptionConstants.DEFINITIONS_NORMAL_COLOR,
 720    new OptionListener<Color>() {
 721  0 public void optionChanged(OptionEvent<Color> oe) {
 722  0 _fgColor = oe.value;
 723  0 setBorder(_createBorder());
 724  0 setCaretColor(oe.value);
 725    }
 726    });
 727  1 DrJava.getConfig().addOptionListener(OptionConstants.DEFINITIONS_BACKGROUND_COLOR,
 728    new OptionListener<Color>() {
 729  0 public void optionChanged(OptionEvent<Color> oe) {
 730  0 _bgColor = oe.value;
 731  0 setBorder(_createBorder());
 732  0 setBackground(oe.value);
 733    }
 734    });
 735  1 DrJava.getConfig().addOptionListener(OptionConstants.SYSTEM_IN_COLOR,
 736    new OptionListener<Color>() {
 737  0 public void optionChanged(OptionEvent<Color> oe) {
 738  0 _sysInColor = oe.value;
 739  0 setForeground(oe.value);
 740    }
 741    });
 742  1 DrJava.getConfig().addOptionListener(OptionConstants.TEXT_ANTIALIAS,
 743    new OptionListener<Boolean>() {
 744  0 public void optionChanged(OptionEvent<Boolean> oce) {
 745  0 _antiAliasText = oce.value.booleanValue();
 746  0 InputBox.this.repaint();
 747    }
 748    });
 749   
 750  1 final InputMap im = getInputMap(WHEN_FOCUSED);
 751  1 final ActionMap am = getActionMap();
 752   
 753    // Add the input listener for <Shift+Enter> and <Cntl+Enter>
 754  1 final Action newLineAction = new AbstractAction() {
 755  0 public void actionPerformed(ActionEvent e) { insert("\n", getCaretPosition()); }
 756    };
 757  1 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,java.awt.Event.SHIFT_MASK), INSERT_NEWLINE_NAME);
 758  1 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,java.awt.Event.CTRL_MASK), INSERT_NEWLINE_NAME);
 759  1 am.put(INSERT_NEWLINE_NAME, newLineAction);
 760   
 761    // Link undo/redo to this InputBox
 762    // First clone the InputMap so we can change the keystroke mappings
 763  1 if (im.keys()!=null) { // im.keys() may be null!
 764  2 for(KeyStroke ks: im.keys()) { _oldInputMap.put(ks, im.get(ks)); }
 765    }
 766   
 767  1 final UndoManager undo = new UndoManager();
 768  1 final Document doc = getDocument();
 769   
 770  1 final Action undoAction = new AbstractAction("Undo") {
 771  0 public void actionPerformed(ActionEvent e) {
 772  0 try {
 773  0 if (undo.canUndo()) { undo.undo(); }
 774    }
 775    catch (CannotUndoException cue) { }
 776  0 setEnabled(undo.canUndo() && isEditable());
 777  0 am.get(REDO_NAME).setEnabled(undo.canRedo() && isEditable());
 778    }
 779    };
 780  1 am.put(UNDO_NAME, undoAction);
 781  1 final Action redoAction = new AbstractAction("Redo") {
 782  0 public void actionPerformed(ActionEvent e) {
 783  0 try {
 784  0 if (undo.canRedo()) { undo.redo(); }
 785    }
 786    catch (CannotRedoException cue) { }
 787  0 undoAction.setEnabled(undo.canUndo() && isEditable());
 788  0 setEnabled(undo.canRedo() && isEditable());
 789    }
 790    };
 791  1 am.put(REDO_NAME, redoAction);
 792   
 793  1 updateKeyBindings();
 794   
 795    // Listen for undo and redo events
 796  1 doc.addUndoableEditListener(new UndoableEditListener() {
 797  1 public void undoableEditHappened(UndoableEditEvent evt) {
 798  1 undo.addEdit(evt.getEdit());
 799  1 undoAction.setEnabled(undo.canUndo() && isEditable());
 800  1 redoAction.setEnabled(undo.canRedo() && isEditable());
 801    }
 802    });
 803  1 undoAction.setEnabled(undo.canUndo() && isEditable());
 804  1 redoAction.setEnabled(undo.canRedo() && isEditable());
 805    }
 806   
 807    /** Update the key bindings for undo and redo. */
 808  1 public void updateKeyBindings() {
 809    // first restore old InputMap.
 810  1 final InputMap im = getInputMap(WHEN_FOCUSED);
 811  1 if (im.keys()!=null) { // im.keys() may be null!
 812  2 for(KeyStroke ks: im.keys()) { im.remove(ks); }
 813    }
 814  1 if (_oldInputMap.keys()!=null) { // keys() may return null!
 815  2 for(KeyStroke ks: _oldInputMap.keys()) { im.put(ks, _oldInputMap.get(ks)); }
 816    }
 817   
 818  1 for(KeyStroke ks: DrJava.getConfig().getSetting(OptionConstants.KEY_UNDO)) { im.put(ks, UNDO_NAME); }
 819  1 for(KeyStroke ks: DrJava.getConfig().getSetting(OptionConstants.KEY_REDO)) { im.put(ks, REDO_NAME); }
 820    }
 821   
 822    /** Returns true if this stream has been closed. */
 823  1 public boolean isEndOfStream() { return _endOfStream; }
 824   
 825    /** Setter for end of stream flag. */
 826  1 public void setEndOfStream(boolean tf) { _endOfStream = tf; }
 827   
 828    /** Was Enter pressed? */
 829  1 public boolean wasClosedWithEnter() { return _closedWithEnter; }
 830   
 831  1 private Border _createBorder() {
 832  1 Border outerouter = BorderFactory.createLineBorder(_bgColor, OUTER_BUFFER_WIDTH);
 833  1 Border outer = BorderFactory.createLineBorder(_fgColor, BORDER_WIDTH);
 834  1 Border inner = BorderFactory.createLineBorder(_bgColor, INNER_BUFFER_WIDTH);
 835  1 Border temp = BorderFactory.createCompoundBorder(outer, inner);
 836  1 return BorderFactory.createCompoundBorder(outerouter, temp);
 837    }
 838   
 839    /** Enable anti-aliased text by overriding paintComponent. */
 840  0 protected void paintComponent(Graphics g) {
 841  0 if (_antiAliasText && g instanceof Graphics2D) {
 842  0 Graphics2D g2d = (Graphics2D) g;
 843  0 g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
 844    }
 845  0 super.paintComponent(g);
 846    }
 847   
 848    /** Specifies what to do when the <Enter> or <Ctrl+D> keys are hit. */
 849  1 void setInputCompletionCommand(final Runnable command) {
 850  1 final InputMap im = getInputMap(WHEN_FOCUSED);
 851  1 im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,0), INPUT_ENTERED_NAME);
 852  1 for(KeyStroke k: DrJava.getConfig().getSetting(OptionConstants.KEY_CLOSE_SYSTEM_IN)) im.put(k, INSERT_END_OF_STREAM);
 853   
 854  1 final ActionMap am = getActionMap();
 855  1 am.put(INPUT_ENTERED_NAME, new AbstractAction() {
 856  0 public void actionPerformed(ActionEvent e) {
 857  0 _closedWithEnter = true; // add newline later
 858  0 command.run();
 859    }
 860    });
 861   
 862    // Add the input listener for <Ctrl+D>
 863  1 am.put(INSERT_END_OF_STREAM, new AbstractAction() {
 864  0 public void actionPerformed(ActionEvent e) {
 865  0 _endOfStream = true;
 866  0 command.run();
 867    }
 868    });
 869    }
 870   
 871    /** Generates a lambda that can be used to insert text into this input box. Only runs in event thread.
 872    * @return A lambda that inserts the given text into the textbox when applied
 873    */
 874  1 Lambda<String,String> makeInsertTextCommand() {
 875  1 return new Lambda<String, String>() {
 876  1 public String value(String input) {
 877  1 insert(input, getCaretPosition());
 878  1 return input;
 879    }
 880    };
 881    }
 882   
 883    /** Behaves somewhat like setEnable(false) in that it disables all
 884    * input to the text box, but it does not change the appearance of the text.
 885    */
 886  1 void disableInputs() {
 887  1 setEditable(false);
 888   
 889  1 ActionMap am = getActionMap();
 890  1 Action action;
 891   
 892  1 action = am.get(INPUT_ENTERED_NAME);
 893  1 if (action != null) action.setEnabled(false);
 894   
 895  1 action = am.get(INSERT_NEWLINE_NAME);
 896  1 if (action != null) action.setEnabled(false);
 897   
 898  1 getCaret().setVisible(false);
 899    }
 900   
 901    /** @return the undo action. */
 902  1 public Action getUndoAction() { return getActionMap().get(UNDO_NAME); }
 903   
 904    /** @return the redo action. */
 905  1 public Action getRedoAction() { return getActionMap().get(REDO_NAME); }
 906    }
 907   
 908    /** A listener interface that allows for others outside the interactions controller to be notified when the input
 909    * console is enabled in the interactions pane.
 910    */
 911    public interface ConsoleStateListener extends EventListener {
 912   
 913    /** Called when the input console is started in the interactions pane. <p>
 914    * This method is called from the thread that initiated the console input,
 915    */
 916    public void consoleInputStarted(InteractionsController c);
 917   
 918    /** Called when the console input is complete. <p>
 919    * This method is called from the thread that initiated the console input.
 920    * @param result The text that was inputted to the console
 921    */
 922    public void consoleInputCompleted(String result, InteractionsController c);
 923   
 924    }
 925    }