private synchronized void store() {
    File history = getHistoryFile();

    if (history != null) {
      try {
        BufferedWriter bw = new BufferedWriter(new FileWriter(history));
        try {
          for (String line : lines) {
            bw.write(line);
            bw.write("\n");
          }
        } catch (IOException e) {
          Log.log("Failed writing console history.py", e);
        } finally {
          try {
            bw.close();
          } catch (IOException e) {
            Log.log("Failed closing console history.py", e);
          }
        }
      } catch (IOException e) {
        Log.log("Failed creating console history.py", e);
      }
    }
  }
  private synchronized void load() {
    File history = getHistoryFile();

    if (history != null) {
      try {
        BufferedReader br = new BufferedReader(new FileReader(history));
        try {
          String line = br.readLine();
          while (line != null) {
            lines.add(line);
            line = br.readLine();
          }
        } catch (IOException e) {
          Log.log("Failed reading existing console history.py", e);
        } finally {
          try {
            br.close();
          } catch (IOException e) {
            Log.log("Failed closing existing console history.py", e);
          }
        }
      } catch (FileNotFoundException e) {
        Log.logInfo("No existing console history at: " + history, e);
      }
    }
  }
  /** 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());
  }
  /**
   * @return the command line that the user entered.
   * @throws BadLocationException
   */
  public String getCommandLine() {
    int commandLineOffset;
    int commandLineLength;
    try {
      commandLineOffset = getCommandLineOffset();
      commandLineLength = getCommandLineLength();
    } catch (BadLocationException e1) {
      Log.log(e1);
      return "";
    }
    if (commandLineLength < 0) {
      return "";
    }

    try {
      return doc.get(commandLineOffset, commandLineLength);
    } catch (BadLocationException e) {
      String msg =
          new FastStringBuffer(60)
              .append("Error: bad location: offset:")
              .append(commandLineOffset)
              .append(" text:")
              .append(commandLineLength)
              .toString();
      Log.log(msg);
      return "";
    }
  }
 /**
  * Appends some text at the end of the document.
  *
  * @param text the text to be added.
  */
 protected void appendText(String text) {
   int initialOffset = doc.getLength();
   try {
     doc.replace(initialOffset, 0, text);
   } catch (BadLocationException e) {
     Log.log(e);
   }
 }
  /**
   * 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);
  }
  /**
   * Clear the document and show the initial prompt.
   *
   * @param addInitialCommands indicates if the initial commands should be appended to the document.
   */
  public void clear(boolean addInitialCommands) {
    startDisconnected();
    try {
      doc.set(""); // $NON-NLS-1$
      appendInvitation(true);
    } finally {
      stopDisconnected();
    }

    if (addInitialCommands) {
      try {
        doc.replace(doc.getLength(), 0, this.initialCommands);
      } catch (BadLocationException e) {
        Log.log(e);
      }
    }
  }
  /**
   * Checks if the trigger character should actually a
   *
   * @param trigger
   * @param doc
   * @param offset
   * @return
   */
  protected boolean triggerCharAppliesCurrentCompletion(char trigger, IDocument doc, int offset) {
    if (trigger == '.' && !getApplyCompletionOnDot()) {
      // do not apply completion when it's triggered by '.', because that's usually not what's
      // wanted
      // e.g.: if the user writes sys and the current completion is SystemError, pressing '.' will
      // apply
      // the completion, but what the user usually wants is just having sys.xxx and not
      // SystemError.xxx
      try {
        doc.replace(offset, 0, ".");
      } catch (BadLocationException e) {
        Log.log(e);
      }
      return false;
    }

    return true;
  }
  /**
   * Adds some text that came as an output to stdout or stderr to the console.
   *
   * @param out the text that should be added
   * @param stdout true if it came from stdout and also if it came from stderr
   */
  private void addToConsoleView(String out, boolean stdout) {
    if (out.length() == 0) {
      return; // nothing to add!
    }
    int start = doc.getLength();

    IConsoleStyleProvider styleProvider = viewer.getStyleProvider();
    Tuple<List<ScriptStyleRange>, String> style = null;
    if (styleProvider != null) {
      if (stdout) {
        style = styleProvider.createInterpreterOutputStyle(out, start);
      } else { // stderr
        style = styleProvider.createInterpreterErrorStyle(out, start);
      }
      if (style != null) {
        for (ScriptStyleRange s : style.o1) {
          addToPartitioner(s);
        }
      }
    }
    if (style != null) {
      appendText(style.o2);
    }

    TextSelectionUtils ps = new TextSelectionUtils(doc, start);
    int cursorLine = ps.getCursorLine();
    int numberOfLines = doc.getNumberOfLines();

    // right after appending the text, let's notify line trackers
    for (int i = cursorLine; i < numberOfLines; i++) {
      try {
        int offset = ps.getLineOffset(i);
        int endOffset = ps.getEndLineOffset(i);

        Region region = new Region(offset, endOffset - offset);

        for (IConsoleLineTracker lineTracker : this.consoleLineTrackers) {
          lineTracker.lineAppended(region);
        }
      } catch (Exception e) {
        Log.log(e);
      }
    }
  }
  /**
   * Called when Ctrl is selected during the completions
   *
   * @see
   *     org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(org.eclipse.jface.text.ITextViewer,
   *     boolean)
   */
  public void selected(ITextViewer viewer, boolean smartToggle) {
    if (smartToggle) {
      StyledText text = viewer.getTextWidget();
      if (text == null || text.isDisposed()) {
        return;
      }

      int widgetCaret = text.getCaretOffset();
      IDocument document = viewer.getDocument();
      int finalOffset = widgetCaret;

      try {
        if (finalOffset >= document.getLength()) {
          unselected(viewer);
          return;
        }
        char c;
        do {
          c = document.getChar(finalOffset);
          finalOffset++;
        } while (isValidChar(c) && finalOffset < document.getLength());

        if (c == '(') {
          fLastIsPar = true;
        } else {
          fLastIsPar = false;
        }

        if (!isValidChar(c)) {
          finalOffset--;
        }

        this.fLen = finalOffset - widgetCaret;
        this.getPresentationUpdater().updateStyle(viewer, widgetCaret, this.fLen);
      } catch (BadLocationException e) {
        Log.log(e);
      }

    } else {
      unselected(viewer);
    }
  }
  @Override
  public void paintControl(PaintEvent e) {

    if (inDraw || styledText == null || styledText.isDisposed()) {
      return;
    }
    try {
      inDraw = true;
      boolean showIndentGuide = this.indentGuide.getShowIndentGuide();
      if (!showIndentGuide) {
        return;
      }

      int xOffset = styledText.getHorizontalPixel();
      int yOffset = styledText.getTopPixel();

      // Important: call all to cache the new values (instead of doing all inside the or below).
      boolean styledTextContentChanged = getStyledTextContentChangedAndStoreNew();
      boolean clientAreaChanged = getClientAreaChangedAndStoreNew();
      boolean charCountChanged = getCharCountChangedAndStoreNew();
      boolean tabWidthChanged = getTabWidthChangedAndStoreNew();

      boolean redrawAll =
          styledTextContentChanged
              || clientAreaChanged
              || charCountChanged
              || tabWidthChanged
              || xOffset != lastXOffset
              || yOffset != lastYOffset;

      StyledTextContent currentContent = this.content;
      if (currClientArea == null
          || currClientArea.width < 5
          || currClientArea.height < 5
          || currCharCount < 1
          || currentContent == null
          || currTabWidth <= 0) {
        return;
      }
      lastXOffset = xOffset;
      lastYOffset = yOffset;

      int topIndex;
      try {
        topIndex = JFaceTextUtil.getPartialTopIndex(styledText);
      } catch (IllegalArgumentException e1) {
        // Just silence it...
        // java.lang.IllegalArgumentException: Index out of bounds
        // at org.eclipse.swt.SWT.error(SWT.java:4458)
        // at org.eclipse.swt.SWT.error(SWT.java:4392)
        // at org.eclipse.swt.SWT.error(SWT.java:4363)
        // at org.eclipse.swt.custom.StyledText.getOffsetAtLine(StyledText.java:4405)
        // at org.eclipse.jface.text.JFaceTextUtil.getPartialTopIndex(JFaceTextUtil.java:103)
        // at
        // org.python.pydev.shared_ui.editor.VerticalIndentGuidesPainter.paintControl(VerticalIndentGuidesPainter.java:93)
        return;
      }
      int bottomIndex = JFaceTextUtil.getPartialBottomIndex(styledText);
      if (redrawAll) {
        this.lineToVerticalLinesToDraw =
            this.indentGuide.computeVerticalLinesToDrawInRegion(styledText, topIndex, bottomIndex);
        // This is a bit unfortunate: when something changes, we may have to repaint out of the
        // clipping
        // region, but even setting the clipping region (e.gc.setClipping), the clipping region may
        // still
        // be unchanged (because the system said that it only wants to repaint some specific area
        // already
        // and we can't make it bigger -- so, what's left for us is asking for a repaint of the full
        // area
        // in this case).
        if (askFullRedraw) {
          askFullRedraw = false;
          if (Math.abs(currClientArea.height - e.gc.getClipping().height) > 40) {
            // Only do it if the difference is really high (some decorations make it usually a bit
            // lower than
            // the actual client area -- usually around 14 in my tests, but make it a bit higher as
            // the usual
            // difference when a redraw is needed is pretty high).
            RunInUiThread.async(
                new Runnable() {

                  @Override
                  public void run() {
                    StyledText s = styledText;
                    if (s != null && !s.isDisposed()) {
                      s.redraw();
                    }
                  }
                });
          } else {
          }
        }
      }

      if (this.lineToVerticalLinesToDraw != null) {
        try (AutoCloseable temp = configGC(e.gc)) {
          Collection<List<VerticalLinesToDraw>> values = lineToVerticalLinesToDraw.values();
          for (List<VerticalLinesToDraw> list : values) {
            for (VerticalLinesToDraw verticalLinesToDraw : list) {
              verticalLinesToDraw.drawLine(e.gc);
            }
          }
        }
      }
    } catch (Exception e1) {
      Log.log(e1);
    } finally {
      inDraw = false;
    }
  }
  /**
   * Should be called right after adding some text to the console (it'll actually go on, remove the
   * text just added and add it line-by-line in the document so that it can be correctly treated in
   * the console).
   *
   * @param offset the offset where the addition took place
   * @param text the text that should be adedd
   */
  protected void proccessAddition(int offset, String text) {
    // we have to do some gymnastics here to add line-by-line the contents that the user entered.
    // (mostly because it may have been a copy/paste with multi-lines)

    String indentString = "";
    boolean addedNewLine = false;
    boolean addedParen = false;
    boolean addedCloseParen = false;
    int addedLen = text.length();
    if (addedLen == 1) {
      if (text.equals("\r") || text.equals("\n")) {
        addedNewLine = true;

      } else if (text.equals("(")) {
        addedParen = true;

      } else if (text.equals(")")) {
        addedCloseParen = true;
      }

    } else if (addedLen == 2) {
      if (text.equals("\r\n")) {
        addedNewLine = true;
      }
    }

    String delim = getDelimeter();

    int newDeltaCaretPosition = doc.getLength() - (offset + text.length());

    // 1st, remove the text the user just entered (and enter it line-by-line later)
    try {
      // Remove the just entered text
      doc.replace(offset, text.length(), ""); // $NON-NLS-1$
      // Is the current offset in the command line
      // NB we do this after the above as the pasted text may have new lines in it
      boolean offset_in_command_line = offset >= getCommandLineOffset();

      // If the offset isn't in the command line, then just append to the existing
      // command line text
      if (!offset_in_command_line) {
        offset = newDeltaCaretPosition = getCommandLineOffset();
        // Remove any existing command line text and prepend it to the text
        // we're inserting
        text = doc.get(getCommandLineOffset(), getCommandLineLength()) + text;
        doc.replace(getCommandLineOffset(), getCommandLineLength(), "");
      } else {
        // paste is within the command line
        text = text + doc.get(offset, doc.getLength() - offset);
        doc.replace(offset, doc.getLength() - offset, "");
      }
    } catch (BadLocationException e) {
      text = "";
      Log.log(e);
    }

    text = text.replaceAll("\r\n|\n|\r", delim); // $NON-NLS-1$

    // now, add it line-by-line (it won't even get into the loop if there's no
    // new line in the text added).
    int start = 0;
    int index = -1;
    List<String> commands = new ArrayList<String>();
    while ((index = text.indexOf(delim, start)) != -1) {
      String cmd = text.substring(start, index);
      cmd = convertTabs(cmd);
      commands.add(cmd);
      start = index + delim.length();
    }

    final String[] finalIndentString = new String[] {indentString};

    if (commands.size() > 0) {
      // Note that we'll disconnect from the document here and reconnect when the last line is
      // executed.
      startDisconnected();
      String cmd = commands.get(0);
      execCommand(
          addedNewLine,
          delim,
          finalIndentString,
          cmd,
          commands,
          0,
          text,
          addedParen,
          start,
          addedCloseParen,
          newDeltaCaretPosition);
    } else {
      onAfterAllLinesHandled(
          text,
          addedParen,
          start,
          offset,
          addedCloseParen,
          finalIndentString[0],
          newDeltaCaretPosition);
    }
  }