/** This method should be called after all the lines received were processed. */
  private void onAfterAllLinesHandled(
      final String finalText,
      final boolean finalAddedParen,
      final int finalStart,
      final int finalOffset,
      final boolean finalAddedCloseParen,
      final String finalIndentString,
      final int finalNewDeltaCaretPosition) {
    boolean shiftsCaret = true;
    String newText = finalText.substring(finalStart, finalText.length());
    if (finalAddedParen) {
      String cmdLine = getCommandLine();
      Document parenDoc = new Document(cmdLine + newText);
      int currentOffset = cmdLine.length() + 1;
      DocCmd docCmd = new DocCmd(currentOffset, 0, "(");
      docCmd.shiftsCaret = true;
      try {
        strategy.customizeParenthesis(parenDoc, docCmd);
      } catch (BadLocationException e) {
        Log.log(e);
      }
      newText = docCmd.text + newText.substring(1);
      if (!docCmd.shiftsCaret) {
        shiftsCaret = false;
        setCaretOffset(finalOffset + (docCmd.caretOffset - currentOffset));
      }
    } else if (finalAddedCloseParen) {
      String cmdLine = getCommandLine();
      String existingDoc = cmdLine + finalText.substring(1);
      int cmdLineOffset = cmdLine.length();
      if (existingDoc.length() > cmdLineOffset) {
        Document parenDoc = new Document(existingDoc);
        DocCmd docCmd = new DocCmd(cmdLineOffset, 0, ")");
        docCmd.shiftsCaret = true;
        boolean canSkipOpenParenthesis;
        try {
          canSkipOpenParenthesis = strategy.canSkipCloseParenthesis(parenDoc, docCmd);
        } catch (BadLocationException e) {
          canSkipOpenParenthesis = false;
          Log.log(e);
        }
        if (canSkipOpenParenthesis) {
          shiftsCaret = false;
          setCaretOffset(finalOffset + 1);
          newText = newText.substring(1);
        }
      }
    }

    // and now add the last line (without actually handling it).
    String cmd = finalIndentString + newText;
    cmd = convertTabs(cmd);
    applyStyleToUserAddedText(cmd, doc.getLength());
    appendText(cmd);
    if (shiftsCaret) {
      setCaretOffset(doc.getLength() - finalNewDeltaCaretPosition);
    }

    history.update(getCommandLine());
  }
  /**
   * Process the result that came from pushing some text to the interpreter.
   *
   * @param result the response from the interpreter after sending some command for it to process.
   */
  protected void processResult(final InterpreterResponse result) {
    if (result != null) {
      addToConsoleView(result.out, true);
      addToConsoleView(result.err, false);

      history.commit();
      try {
        offset = getLastLineLength();
      } catch (BadLocationException e) {
        Log.log(e);
      }
    }
    appendInvitation(false);
  }
  /**
   * Here is where we run things not using the UI thread. It's a recursive function. In summary,
   * it'll run each line in the commands received in a new thread, and as each finishes, it calls
   * itself again for the next command. The last command will reconnect to the document.
   *
   * <p>Exceptions had to be locally handled, because they're not well tolerated under this scenario
   * (if on of the callbacks fail, the others won't be executed and we'd get into a situation where
   * the shell becomes unusable).
   */
  private void execCommand(
      final boolean addedNewLine,
      final String delim,
      final String[] finalIndentString,
      final String cmd,
      final List<String> commands,
      final int currentCommand,
      final String text,
      final boolean addedParen,
      final int start,
      final boolean addedCloseParen,
      final int newDeltaCaretPosition) {
    applyStyleToUserAddedText(cmd, doc.getLength());

    // the cmd could be something as '\n'
    appendText(cmd);

    // and the command line the actual contents to be executed at this time
    final String commandLine = getCommandLine();
    history.update(commandLine);

    // handle the command line:
    // When the user presses a return and goes to a new line,  the contents of the current line are
    // sent to
    // the interpreter (and its results properly handled).

    appendText(getDelimeter());
    final boolean finalAddedNewLine = addedNewLine;
    final String finalDelim = delim;

    final ICallback<Object, InterpreterResponse> onResponseReceived =
        new ICallback<Object, InterpreterResponse>() {

          public Object call(final InterpreterResponse arg) {
            // When we receive the response, we must handle it in the UI thread.
            Runnable runnable =
                new Runnable() {

                  public void run() {
                    try {
                      processResult(arg);
                      if (finalAddedNewLine) {
                        IDocument historyDoc = history.getAsDoc();
                        int currHistoryLen = historyDoc.getLength();
                        if (currHistoryLen > 0) {
                          DocCmd docCmd = new DocCmd(currHistoryLen - 1, 0, finalDelim);
                          strategy.customizeNewLine(historyDoc, docCmd);
                          finalIndentString[0] =
                              docCmd.text.replaceAll(
                                  "\\r\\n|\\n|\\r", ""); // remove any new line added!
                          if (currHistoryLen != historyDoc.getLength()) {
                            Log.log(
                                "Error: the document passed to the customizeNewLine should not be changed!");
                          }
                        }
                      }
                    } catch (Throwable e) {
                      // Yeap, it can never fail!
                      Log.log(e);
                    }
                    if (currentCommand + 1 < commands.size()) {
                      execCommand(
                          finalAddedNewLine,
                          finalDelim,
                          finalIndentString,
                          commands.get(currentCommand + 1),
                          commands,
                          currentCommand + 1,
                          text,
                          addedParen,
                          start,
                          addedCloseParen,
                          newDeltaCaretPosition);
                    } else {
                      // last one
                      try {
                        onAfterAllLinesHandled(
                            text,
                            addedParen,
                            start,
                            offset,
                            addedCloseParen,
                            finalIndentString[0],
                            newDeltaCaretPosition);
                      } finally {
                        // We must disconnect
                        stopDisconnected(); // reconnect with the document
                      }
                    }
                  }
                };
            RunInUiThread.async(runnable);
            return null;
          }
        };

    final ICallback<Object, Tuple<String, String>> onContentsReceived =
        new ICallback<Object, Tuple<String, String>>() {

          public Object call(final Tuple<String, String> result) {
            Runnable runnable =
                new Runnable() {

                  public void run() {
                    if (result != null) {
                      addToConsoleView(result.o1, true);
                      addToConsoleView(result.o2, false);
                      revealEndOfDocument();
                    }
                  }
                };
            RunInUiThread.async(runnable);
            return null;
          }
        };
    // Handle the command in a thread that doesn't block the U/I.
    new Thread() {
      public void run() {
        handler.handleCommand(commandLine, onResponseReceived, onContentsReceived);
      }
    }.start();
  }