gt: 		for(int i = offset - 1; i >= 0; i--) {
context: /* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
  
  /*
   * JEditTextArea.java - jEdit's text component
   * Copyright (C) 1999 Slava Pestov
   *
   * You may use and modify this package for any purpose. Redistribution is
   * permitted, in both source and binary form, provided that this notice
   * remains intact in all source distributions of this package.
   */
  
  package processing.app.syntax;
  
  import processing.app.*;
  
  import javax.swing.event.*;
  import javax.swing.text.*;
  import javax.swing.undo.*;
  import javax.swing.*;
  
  import java.awt.datatransfer.*;
  import java.awt.event.*;
  import java.awt.*;
  import java.util.Enumeration;
  import java.util.Vector;
  import java.awt.im.InputMethodRequests;
  
  import processing.app.syntax.im.InputMethodSupport;
  
  /**
   * jEdit's text area component. It is more suited for editing program
   * source code than JEditorPane, because it drops the unnecessary features
   * (images, variable-width lines, and so on) and adds a whole bunch of
   * useful goodies such as:
   * <ul>
   * <li>More flexible key binding scheme
   * <li>Supports macro recorders
   * <li>Rectangular selection
   * <li>Bracket highlighting
   * <li>Syntax highlighting
   * <li>Command repetition
   * <li>Block caret can be enabled
   * </ul>
   * It is also faster and doesn't have as many problems. It can be used
   * in other applications; the only other part of jEdit it depends on is
   * the syntax package.<p>
   *
   * To use it in your app, treat it like any other component, for example:
   * <pre>JEditTextArea ta = new JEditTextArea();
   * ta.setTokenMarker(new JavaTokenMarker());
   * ta.setText("public class Test {\n"
   *     + "    public static void main(String[] args) {\n"
   *     + "        System.out.println(\"Hello World\");\n"
   *     + "    }\n"
   *     + "}");</pre>
   *
   * @author Slava Pestov
   * @version $Id: JEditTextArea.java 6123 2010-02-16 21:43:44Z fry $
   */
  public class JEditTextArea extends JComponent
  {
    /**
     * Adding components with this name to the text area will place
     * them left of the horizontal scroll bar. In jEdit, the status
     * bar is added this way.
     */
    public static String LEFT_OF_SCROLLBAR = "los";
  
    /**
     * Creates a new JEditTextArea with the default settings.
     */
    /*
    public JEditTextArea()
    {
      this(TextAreaDefaults.getDefaults());
    }
    */
  
    /**
     * Creates a new JEditTextArea with the specified settings.
     * @param defaults The default settings
     */
    public JEditTextArea(TextAreaDefaults defaults)
    {
      // Enable the necessary events
      enableEvents(AWTEvent.KEY_EVENT_MASK);
  
      // Initialize some misc. stuff
      painter = new TextAreaPainter(this,defaults);
      documentHandler = new DocumentHandler();
      eventListenerList = new EventListenerList();
      caretEvent = new MutableCaretEvent();
      lineSegment = new Segment();
      bracketLine = bracketPosition = -1;
      blink = true;
  
      // Initialize the GUI
      setLayout(new ScrollLayout());
      add(CENTER, painter);
      add(RIGHT, vertical = new JScrollBar(JScrollBar.VERTICAL));
      add(BOTTOM, horizontal = new JScrollBar(JScrollBar.HORIZONTAL));
  
      // Add some event listeners
      vertical.addAdjustmentListener(new AdjustHandler());
      horizontal.addAdjustmentListener(new AdjustHandler());
      painter.addComponentListener(new ComponentHandler());
      painter.addMouseListener(new MouseHandler());
      painter.addMouseMotionListener(new DragHandler());
      addFocusListener(new FocusHandler());
      // send tab keys through to the text area
      // http://dev.processing.org/bugs/show_bug.cgi?id=1267
      setFocusTraversalKeysEnabled(false);
  
      // Load the defaults
      setInputHandler(defaults.inputHandler);
      setDocument(defaults.document);
      editable = defaults.editable;
      caretVisible = defaults.caretVisible;
      caretBlinks = defaults.caretBlinks;
      electricScroll = defaults.electricScroll;
  
      // We don't seem to get the initial focus event?
      focusedComponent = this;
  
      addMouseWheelListener(new MouseWheelListener() {
          public void mouseWheelMoved(MouseWheelEvent e) {
            if (!scrollBarsInitialized) return;
            int amt = e.getWheelRotation();
            vertical.setValue(vertical.getValue() + amt * 3);
          }
        });
    }
  
    /**
    * Inline Input Method Support for Japanese.
    */
    private InputMethodSupport inputMethodSupport = null;
    public InputMethodRequests getInputMethodRequests() {
  		if (inputMethodSupport == null) {
  			inputMethodSupport = new InputMethodSupport(this);
  		}
  		return inputMethodSupport;
    }
  
    /**
     * Get current position of the vertical scroll bar. [fry]
     */
    public int getScrollPosition() {
      return vertical.getValue();
    }
  
  
    /**
     * Set position of the vertical scroll bar. [fry]
     */
    public void setScrollPosition(int what) {
      vertical.setValue(what);
    }
  
  
    /**
     * Returns if this component can be traversed by pressing
     * the Tab key. This returns false.
     */
  //  public final boolean isManagingFocus() {
  //    return true;
  //  }
  
    /**
     * Returns the object responsible for painting this text area.
     */
    public final TextAreaPainter getPainter() {
      return painter;
    }
  
    /**
     * Returns the input handler.
     */
    public final InputHandler getInputHandler() {
      return inputHandler;
    }
  
    /**
     * Sets the input handler.
     * @param inputHandler The new input handler
     */
    public void setInputHandler(InputHandler inputHandler) {
      this.inputHandler = inputHandler;
    }
  
    /**
     * Returns true if the caret is blinking, false otherwise.
     */
    public final boolean isCaretBlinkEnabled() {
      return caretBlinks;
    }
  
    /**
     * Toggles caret blinking.
     * @param caretBlinks True if the caret should blink, false otherwise
     */
    public void setCaretBlinkEnabled(boolean caretBlinks) {
      this.caretBlinks = caretBlinks;
      if(!caretBlinks)
        blink = false;
  
      painter.invalidateSelectedLines();
    }
  
    /**
     * Returns true if the caret is visible, false otherwise.
     */
    public final boolean isCaretVisible() {
      return (!caretBlinks || blink) && caretVisible;
    }
  
    /**
     * Sets if the caret should be visible.
     * @param caretVisible True if the caret should be visible, false
     * otherwise
     */
    public void setCaretVisible(boolean caretVisible) {
      this.caretVisible = caretVisible;
      blink = true;
  
      painter.invalidateSelectedLines();
    }
  
    /**
     * Blinks the caret.
     */
    public final void blinkCaret() {
      if (caretBlinks)  {
        blink = !blink;
        painter.invalidateSelectedLines();
      } else {
        blink = true;
      }
    }
  
    /**
     * Returns the number of lines from the top and button of the
     * text area that are always visible.
     */
    public final int getElectricScroll() {
      return electricScroll;
    }
  
    /**
     * Sets the number of lines from the top and bottom of the text
     * area that are always visible
     * @param electricScroll The number of lines always visible from
     * the top or bottom
     */
    public final void setElectricScroll(int electricScroll) {
      this.electricScroll = electricScroll;
    }
  
  
    /**
     * Updates the state of the scroll bars. This should be called
     * if the number of lines in the document changes, or when the
     * size of the text are changes.
     */
    public void updateScrollBars() {
      if (vertical != null && visibleLines != 0) {
        vertical.setValues(firstLine,visibleLines,0,getLineCount());
        vertical.setUnitIncrement(2);
        vertical.setBlockIncrement(visibleLines);
      }
  
      //if (horizontal != null && width != 0) {
      if ((horizontal != null) && (painter.getWidth() != 0)) {
        //int value = horizontal.getValue();
        //System.out.println("updateScrollBars");
        //int width = painter.getWidth();
        int lineCount = getLineCount();
        int maxLineLength = 0;
        for (int i = 0; i < lineCount; i++) {
          int lineLength = getLineLength(i);
          if (lineLength > maxLineLength) {
            maxLineLength = lineLength;
          }
        }
        int charWidth = painter.getFontMetrics().charWidth('w');
        int width = maxLineLength * charWidth;
        int painterWidth = painter.getWidth();
        //System.out.println("max line len " + maxLineLength);
        //System.out.println("width " + width);
        //System.out.println("text area width " + painter.getWidth());
  
        // this was the default, but it's enormous
        //horizontal.setValues(-horizontalOffset,width,0,width * 5);
  
        // something more reasonable, though this is a bad solution
        //horizontal.setValues(-horizontalOffset,width,0,width * 2);
  
        // in general.. time to start looking at that other syntax pkg
        // since most code should fit the window horizontally, just use
        // the default settings for the width, this is a nicer solution
        // until a better update mechanism can be implemented [fry]
  
        //horizontal.setValues(0, width, 0, width);
                             //0, width - horizontalOffset);
        // works, from pre-75 versions of p5
        //horizontal.setValues(-horizontalOffset, width, 0, width);
  
        // gets weird when writing to the end of lines
        //horizontal.setValues(value, painterWidth, 0, width);
  
        // seems to work, implemented for 0075
        horizontal.setValues(-horizontalOffset, painterWidth, 0, width);
  
        //horizontal.setUnitIncrement(painter.getFontMetrics().charWidth('w'));
        horizontal.setUnitIncrement(charWidth);
        horizontal.setBlockIncrement(width / 2);
      }
    }
  
    /**
     * Returns the line displayed at the text area's origin.
     */
    public final int getFirstLine() {
      return firstLine;
    }
  
    /**
     * Sets the line displayed at the text area's origin without
     * updating the scroll bars.
     */
    public void setFirstLine(int firstLine) {
      if (firstLine == this.firstLine) return;
  
      this.firstLine = firstLine;
      if (firstLine != vertical.getValue()) {
        updateScrollBars();
      }
      painter.repaint();
    }
  
    /**
     * Returns the number of lines visible in this text area.
     */
    public final int getVisibleLines() {
      return visibleLines;
    }
  
    /**
     * Recalculates the number of visible lines. This should not
     * be called directly.
     */
    public final void recalculateVisibleLines() {
      if (painter == null) return;
  
      int height = painter.getHeight();
      int lineHeight = painter.getFontMetrics().getHeight();
      visibleLines = height / lineHeight;
      updateScrollBars();
    }
  
    /**
     * Returns the horizontal offset of drawn lines.
     */
    public final int getHorizontalOffset() {
      return horizontalOffset;
    }
  
    /**
     * Sets the horizontal offset of drawn lines. This can be used to
     * implement horizontal scrolling.
     * @param horizontalOffset offset The new horizontal offset
     */
    public void setHorizontalOffset(int horizontalOffset)
    {
      if(horizontalOffset == this.horizontalOffset)
        return;
      this.horizontalOffset = horizontalOffset;
      if(horizontalOffset != horizontal.getValue())
        updateScrollBars();
      painter.repaint();
    }
  
    /**
     * A fast way of changing both the first line and horizontal
     * offset.
     * @param firstLine The new first line
     * @param horizontalOffset The new horizontal offset
     * @return True if any of the values were changed, false otherwise
     */
    public boolean setOrigin(int firstLine, int horizontalOffset)
    {
      boolean changed = false;
      //int oldFirstLine = this.firstLine;
  
      if(horizontalOffset != this.horizontalOffset)
        {
          this.horizontalOffset = horizontalOffset;
          changed = true;
        }
  
      if(firstLine != this.firstLine)
        {
          this.firstLine = firstLine;
          changed = true;
        }
  
      if(changed)
        {
          updateScrollBars();
          painter.repaint();
        }
  
      return changed;
    }
  
    /**
     * Ensures that the caret is visible by scrolling the text area if
     * necessary.
     * @return True if scrolling was actually performed, false if the
     * caret was already visible
     */
    public boolean scrollToCaret()
    {
      int line = getCaretLine();
      int lineStart = getLineStartOffset(line);
      int offset = Math.max(0,Math.min(getLineLength(line) - 1,
                                       getCaretPosition() - lineStart));
  
      return scrollTo(line,offset);
    }
  
    /**
     * Ensures that the specified line and offset is visible by scrolling
     * the text area if necessary.
     * @param line The line to scroll to
     * @param offset The offset in the line to scroll to
     * @return True if scrolling was actually performed, false if the
     * line and offset was already visible
     */
    public boolean scrollTo(int line, int offset)
    {
      // visibleLines == 0 before the component is realized
      // we can't do any proper scrolling then, so we have
      // this hack...
      if (visibleLines == 0) {
        setFirstLine(Math.max(0,line - electricScroll));
        return true;
      }
  
      int newFirstLine = firstLine;
      int newHorizontalOffset = horizontalOffset;
  
      if(line < firstLine + electricScroll) {
        newFirstLine = Math.max(0,line - electricScroll);
  
      } else if(line + electricScroll >= firstLine + visibleLines) {
        newFirstLine = (line - visibleLines) + electricScroll + 1;
        if(newFirstLine + visibleLines >= getLineCount())
          newFirstLine = getLineCount() - visibleLines;
        if(newFirstLine < 0)
          newFirstLine = 0;
      }
  
      int x = _offsetToX(line,offset);
      int width = painter.getFontMetrics().charWidth('w');
  
      if(x < 0) {
        newHorizontalOffset = Math.min(0,horizontalOffset - x + width + 5);
      } else if(x + width >= painter.getWidth()) {
        newHorizontalOffset = horizontalOffset +
          (painter.getWidth() - x) - width - 5;
      }
  
      return setOrigin(newFirstLine,newHorizontalOffset);
    }
  
    /**
     * Converts a line index to a y co-ordinate.
     * @param line The line
     */
    public int lineToY(int line)
    {
      FontMetrics fm = painter.getFontMetrics();
      return (line - firstLine) * fm.getHeight()
        - (fm.getLeading() + fm.getMaxDescent());
    }
  
    /**
     * Converts a y co-ordinate to a line index.
     * @param y The y co-ordinate
     */
    public int yToLine(int y)
    {
      FontMetrics fm = painter.getFontMetrics();
      int height = fm.getHeight();
      return Math.max(0,Math.min(getLineCount() - 1,
                                 y / height + firstLine));
    }
  
    /**
     * Converts an offset in a line into an x co-ordinate. This is a
     * slow version that can be used any time.
     * @param line The line
     * @param offset The offset, from the start of the line
     */
    public final int offsetToX(int line, int offset)
    {
      // don't use cached tokens
      painter.currentLineTokens = null;
      return _offsetToX(line,offset);
    }
  
    /**
     * Converts an offset in a line into an x co-ordinate. This is a
     * fast version that should only be used if no changes were made
     * to the text since the last repaint.
     * @param line The line
     * @param offset The offset, from the start of the line
     */
    public int _offsetToX(int line, int offset)
    {
      TokenMarker tokenMarker = getTokenMarker();
  
      /* Use painter's cached info for speed */
      FontMetrics fm = painter.getFontMetrics();
  
      getLineText(line,lineSegment);
  
      int segmentOffset = lineSegment.offset;
      int x = horizontalOffset;
  
      /* If syntax coloring is disabled, do simple translation */
      if(tokenMarker == null)
        {
          lineSegment.count = offset;
          return x + Utilities.getTabbedTextWidth(lineSegment,
                                                  fm,x,painter,0);
        }
      /* If syntax coloring is enabled, we have to do this because
       * tokens can vary in width */
      else
        {
          Token tokens;
          if(painter.currentLineIndex == line
             && painter.currentLineTokens != null)
            tokens = painter.currentLineTokens;
          else
            {
              painter.currentLineIndex = line;
              tokens = painter.currentLineTokens
                = tokenMarker.markTokens(lineSegment,line);
            }
  
          //Toolkit toolkit = painter.getToolkit();
          Font defaultFont = painter.getFont();
          SyntaxStyle[] styles = painter.getStyles();
  
          for(;;)
            {
              byte id = tokens.id;
              if(id == Token.END)
                {
                  return x;
                }
  
              if(id == Token.NULL)
                fm = painter.getFontMetrics();
              else
                fm = styles[id].getFontMetrics(defaultFont, this);
  
              int length = tokens.length;
  
              if(offset + segmentOffset < lineSegment.offset + length)
                {
                  lineSegment.count = offset - (lineSegment.offset - segmentOffset);
                  return x + Utilities.getTabbedTextWidth(
                                                          lineSegment,fm,x,painter,0);
                }
              else
                {
                  lineSegment.count = length;
                  x += Utilities.getTabbedTextWidth(
                                                    lineSegment,fm,x,painter,0);
                  lineSegment.offset += length;
                }
              tokens = tokens.next;
            }
        }
    }
  
    /**
     * Converts an x co-ordinate to an offset within a line.
     * @param line The line
     * @param x The x co-ordinate
     */
    public int xToOffset(int line, int x)
    {
      TokenMarker tokenMarker = getTokenMarker();
  
      /* Use painter's cached info for speed */
      FontMetrics fm = painter.getFontMetrics();
  
      getLineText(line,lineSegment);
  
      char[] segmentArray = lineSegment.array;
      int segmentOffset = lineSegment.offset;
      int segmentCount = lineSegment.count;
  
      int width = horizontalOffset;
  
      if(tokenMarker == null)
        {
          for(int i = 0; i < segmentCount; i++)
            {
              char c = segmentArray[i + segmentOffset];
              int charWidth;
              if(c == '\t')
                charWidth = (int)painter.nextTabStop(width,i)
                  - width;
              else
                charWidth = fm.charWidth(c);
  
              if(painter.isBlockCaretEnabled())
                {
                  if(x - charWidth <= width)
                    return i;
                }
              else
                {
                  if(x - charWidth / 2 <= width)
                    return i;
                }
  
              width += charWidth;
            }
  
          return segmentCount;
        }
      else
        {
          Token tokens;
          if(painter.currentLineIndex == line && painter
             .currentLineTokens != null)
            tokens = painter.currentLineTokens;
          else
            {
              painter.currentLineIndex = line;
              tokens = painter.currentLineTokens
                = tokenMarker.markTokens(lineSegment,line);
            }
  
          int offset = 0;
          //Toolkit toolkit = painter.getToolkit();
          Font defaultFont = painter.getFont();
          SyntaxStyle[] styles = painter.getStyles();
  
          for(;;)
            {
              byte id = tokens.id;
              if(id == Token.END)
                return offset;
  
              if(id == Token.NULL)
                fm = painter.getFontMetrics();
              else
                fm = styles[id].getFontMetrics(defaultFont, this);
  
              int length = tokens.length;
  
              for(int i = 0; i < length; i++)
                {
                  char c = segmentArray[segmentOffset + offset + i];
                  int charWidth;
                  if(c == '\t')
                    charWidth = (int)painter.nextTabStop(width,offset + i)
                      - width;
                  else
                    charWidth = fm.charWidth(c);
  
                  if(painter.isBlockCaretEnabled())
                    {
                      if(x - charWidth <= width)
                        return offset + i;
                    }
                  else
                    {
                      if(x - charWidth / 2 <= width)
                        return offset + i;
                    }
  
                  width += charWidth;
                }
  
              offset += length;
              tokens = tokens.next;
            }
        }
    }
  
    /**
     * Converts a point to an offset, from the start of the text.
     * @param x The x co-ordinate of the point
     * @param y The y co-ordinate of the point
     */
    public int xyToOffset(int x, int y)
    {
      int line = yToLine(y);
      int start = getLineStartOffset(line);
      return start + xToOffset(line,x);
    }
  
    /**
     * Returns the document this text area is editing.
     */
    public final SyntaxDocument getDocument()
    {
      return document;
    }
  
    /**
     * Sets the document this text area is editing.
     * @param document The document
     */
    public void setDocument(SyntaxDocument document) {
      if (this.document == document)
        return;
      if (this.document != null)
        this.document.removeDocumentListener(documentHandler);
      this.document = document;
  
      document.addDocumentListener(documentHandler);
  
      select(0, 0);
      updateScrollBars();
      painter.repaint();
    }
  
  
    /**
     * Set document with a twist, includes the old caret
     * and scroll positions, added for p5. [fry]
     */
    public void setDocument(SyntaxDocument document,
                            int start, int stop, int scroll) {
      if (this.document == document)
        return;
      if (this.document != null)
        this.document.removeDocumentListener(documentHandler);
      this.document = document;
  
      document.addDocumentListener(documentHandler);
  
      select(start, stop);
      updateScrollBars();
      setScrollPosition(scroll);
      painter.repaint();
    }
  
  
    /**
     * Returns the document's token marker. Equivalent to calling
     * <code>getDocument().getTokenMarker()</code>.
     */
    public final TokenMarker getTokenMarker()
    {
      return document.getTokenMarker();
    }
  
    /**
     * Sets the document's token marker. Equivalent to caling
     * <code>getDocument().setTokenMarker()</code>.
     * @param tokenMarker The token marker
     */
    public final void setTokenMarker(TokenMarker tokenMarker)
    {
      document.setTokenMarker(tokenMarker);
    }
  
    /**
     * Returns the length of the document. Equivalent to calling
     * <code>getDocument().getLength()</code>.
     */
    public final int getDocumentLength()
    {
      return document.getLength();
    }
  
    /**
     * Returns the number of lines in the document.
     */
    public final int getLineCount()
    {
      return document.getDefaultRootElement().getElementCount();
    }
  
    /**
     * Returns the line containing the specified offset.
     * @param offset The offset
     */
    public final int getLineOfOffset(int offset)
    {
      return document.getDefaultRootElement().getElementIndex(offset);
    }
  
    /**
     * Returns the start offset of the specified line.
     * @param line The line
     * @return The start offset of the specified line, or -1 if the line is
     * invalid
     */
    public int getLineStartOffset(int line)
    {
      Element lineElement = document.getDefaultRootElement()
        .getElement(line);
      if(lineElement == null)
        return -1;
      else
        return lineElement.getStartOffset();
    }
  
    /**
     * Returns the end offset of the specified line.
     * @param line The line
     * @return The end offset of the specified line, or -1 if the line is
     * invalid.
     */
    public int getLineStopOffset(int line)
    {
      Element lineElement = document.getDefaultRootElement()
        .getElement(line);
      if(lineElement == null)
        return -1;
      else
        return lineElement.getEndOffset();
    }
  
    /**
     * Returns the end offset of the specified line, but not past the end of the text
     * @param line The line
     * @return The end offset of the specified line, safe to use for a selection, or -1 if the line is
     * invalid.
     */
    public int getSafeLineStopOffset(int line)
    {
      return Math.min(getLineStopOffset(line),getDocumentLength());
    }
  
    /**
     * Returns the length of the specified line.
     * @param line The line
     */
    public int getLineLength(int line)
    {
      Element lineElement = document.getDefaultRootElement()
        .getElement(line);
      if(lineElement == null)
        return -1;
      else
        return lineElement.getEndOffset()
          - lineElement.getStartOffset() - 1;
    }
  
    /**
     * Returns the entire text of this text area.
     */
    public String getText()
    {
      try
        {
          return document.getText(0,document.getLength());
        }
      catch(BadLocationException bl)
        {
          bl.printStackTrace();
          return null;
        }
    }
  
  
    /**
     * Sets the entire text of this text area.
     */
    public void setText(String text)
    {
      try {
        document.beginCompoundEdit();
        document.remove(0,document.getLength());
        document.insertString(0,text,null);
  
      } catch (BadLocationException bl) {
        bl.printStackTrace();
  
      } finally {
        document.endCompoundEdit();
      }
    }
  
  
    /**
     * Returns the specified substring of the document.
     * @param start The start offset
     * @param len The length of the substring
     * @return The substring, or null if the offsets are invalid
     */
    public final String getText(int start, int len)
    {
      try
        {
          return document.getText(start,len);
        }
      catch(BadLocationException bl)
        {
          bl.printStackTrace();
          return null;
        }
    }
  
    /**
     * Copies the specified substring of the document into a segment.
     * If the offsets are invalid, the segment will contain a null string.
     * @param start The start offset
     * @param len The length of the substring
     * @param segment The segment
     */
    public final void getText(int start, int len, Segment segment)
    {
      try
        {
          document.getText(start,len,segment);
        }
      catch(BadLocationException bl)
        {
          bl.printStackTrace();
          segment.offset = segment.count = 0;
        }
    }
  
    /**
     * Returns the text on the specified line.
     * @param lineIndex The line
     * @return The text, or null if the line is invalid
     */
    public final String getLineText(int lineIndex)
    {
      int start = getLineStartOffset(lineIndex);
      return getText(start,getLineStopOffset(lineIndex) - start - 1);
    }
  
    /**
     * Copies the text on the specified line into a segment. If the line
     * is invalid, the segment will contain a null string.
     * @param lineIndex The line
     */
    public final void getLineText(int lineIndex, Segment segment)
    {
      int start = getLineStartOffset(lineIndex);
      getText(start,getLineStopOffset(lineIndex) - start - 1,segment);
    }
  
    /**
     * Returns the selection start offset.
     */
    public final int getSelectionStart()
    {
      return selectionStart;
    }
  
    /**
     * Returns the offset where the selection starts on the specified
     * line.
     */
    public int getSelectionStart(int line)
    {
      if(line == selectionStartLine)
        return selectionStart;
      else if(rectSelect)
        {
          Element map = document.getDefaultRootElement();
          int start = selectionStart - map.getElement(selectionStartLine)
            .getStartOffset();
  
          Element lineElement = map.getElement(line);
          int lineStart = lineElement.getStartOffset();
          int lineEnd = lineElement.getEndOffset() - 1;
          return Math.min(lineEnd,lineStart + start);
        }
      else
        return getLineStartOffset(line);
    }
  
    /**
     * Returns the selection start line.
     */
    public final int getSelectionStartLine()
    {
      return selectionStartLine;
    }
  
    /**
     * Sets the selection start. The new selection will be the new
     * selection start and the old selection end.
     * @param selectionStart The selection start
     * @see #select(int,int)
     */
    public final void setSelectionStart(int selectionStart)
    {
      select(selectionStart,selectionEnd);
    }
  
    /**
     * Returns the selection end offset.
     */
    public final int getSelectionStop()
    {
      return selectionEnd;
    }
  
    /**
     * Returns the offset where the selection ends on the specified
     * line.
     */
    public int getSelectionStop(int line)
    {
      if(line == selectionEndLine)
        return selectionEnd;
      else if(rectSelect)
        {
          Element map = document.getDefaultRootElement();
          int end = selectionEnd - map.getElement(selectionEndLine)
            .getStartOffset();
  
          Element lineElement = map.getElement(line);
          int lineStart = lineElement.getStartOffset();
          int lineEnd = lineElement.getEndOffset() - 1;
          return Math.min(lineEnd,lineStart + end);
        }
      else
        return getLineStopOffset(line) - 1;
    }
  
    /**
     * Returns the selection end line.
     */
    public final int getSelectionStopLine()
    {
      return selectionEndLine;
    }
  
    /**
     * Sets the selection end. The new selection will be the old
     * selection start and the bew selection end.
     * @param selectionEnd The selection end
     * @see #select(int,int)
     */
    public final void setSelectionEnd(int selectionEnd)
    {
      select(selectionStart,selectionEnd);
    }
  
  
    public final boolean isSelectionActive()
    {
      return(selectionStart != selectionEnd);
    }
  
    /**
     * Returns the caret position. This will either be the selection
     * start or the selection end, depending on which direction the
     * selection was made in.
     */
    public final int getCaretPosition()
    {
      return (biasLeft ? selectionStart : selectionEnd);
    }
  
    /**
     * Returns the caret line.
     */
    public final int getCaretLine()
    {
      return (biasLeft ? selectionStartLine : selectionEndLine);
    }
  
    /**
     * Returns the mark position. This will be the opposite selection
     * bound to the caret position.
     * @see #getCaretPosition()
     */
    public final int getMarkPosition()
    {
      return (biasLeft ? selectionEnd : selectionStart);
    }
  
    /**
     * Returns the mark line.
     */
    public final int getMarkLine()
    {
      return (biasLeft ? selectionEndLine : selectionStartLine);
    }
  
    /**
     * Sets the caret position. The new selection will consist of the
     * caret position only (hence no text will be selected)
     * @param caret The caret position
     * @see #select(int,int)
     */
    public final void setCaretPosition(int caret)
    {
      select(caret,caret);
    }
  
    /**
     * Selects all text in the document.
     */
    public final void selectAll()
    {
      select(0,getDocumentLength());
    }
  
    /**
     * Moves the mark to the caret position.
     */
    public final void selectNone()
    {
      select(getCaretPosition(),getCaretPosition());
    }
  
    /**
     * Selects from the start offset to the end offset. This is the
     * general selection method used by all other selecting methods.
     * The caret position will be start if start &lt; end, and end
     * if end &gt; start.
     * @param start The start offset
     * @param end The end offset
     */
    public void select(int start, int end)
    {
      int newStart, newEnd;
      boolean newBias;
      if(start <= end)
        {
          newStart = start;
          newEnd = end;
          newBias = false;
        }
      else
        {
          newStart = end;
          newEnd = start;
          newBias = true;
        }
  
      if(newStart < 0 || newEnd > getDocumentLength())
        {
          throw new IllegalArgumentException("Bounds out of"
                                             + " range: " + newStart + "," +
                                             newEnd + " [" + getDocumentLength() + "]");
        }
  
      // If the new position is the same as the old, we don't
      // do all this crap, however we still do the stuff at
      // the end (clearing magic position, scrolling)
      if(newStart != selectionStart || newEnd != selectionEnd
         || newBias != biasLeft)
        {
          int newStartLine = getLineOfOffset(newStart);
          int newEndLine = getLineOfOffset(newEnd);
  
          if(painter.isBracketHighlightEnabled())
            {
              if(bracketLine != -1)
                painter.invalidateLine(bracketLine);
              updateBracketHighlight(end);
              if(bracketLine != -1)
                painter.invalidateLine(bracketLine);
            }
  
          painter.invalidateLineRange(selectionStartLine,selectionEndLine);
          painter.invalidateLineRange(newStartLine,newEndLine);
  
          document.addUndoableEdit(new CaretUndo(selectionStart,selectionEnd));
  
          selectionStart = newStart;
          selectionEnd = newEnd;
          selectionStartLine = newStartLine;
          selectionEndLine = newEndLine;
          biasLeft = newBias;
  
          if (newStart != newEnd) {
            Clipboard unixclipboard = getToolkit().getSystemSelection();
            if (unixclipboard != null) {
              String selection = getSelectedText();
              if (selection != null) {
                unixclipboard.setContents(new StringSelection(selection), null);
              }
            }
          }
  
          fireCaretEvent();
        }
  
      // When the user is typing, etc, we don't want the caret
      // to blink
      blink = true;
      caretTimer.restart();
  
      // Disable rectangle select if selection start = selection end
      if(selectionStart == selectionEnd)
        rectSelect = false;
  
      // Clear the `magic' caret position used by up/down
      magicCaret = -1;
  
      scrollToCaret();
  
      // notify the line number feller
      if (editorLineStatus != null) {
        editorLineStatus.set(selectionStartLine, selectionEndLine);
        //System.out.println("why " + selectionStartLine + " " + selectionEndLine);
        //System.out.println(getLineOfOffset(start) + " " +
        //                 getLineOfOffset(end));
      }
    }
    
    private boolean isWordCharacter( char ch, String noWordSep )
    {
  		return Character.isLetterOrDigit(ch) || ch=='_' || noWordSep.indexOf(ch) != -1;
    }
  
  	protected void setNewSelectionWord( int line, int offset )
  	{
  		if (getLineLength(line) == 0) {
  			newSelectionStart = getLineStartOffset(line);
  			newSelectionEnd = newSelectionStart;
  			return;
  		}
  		
  		String noWordSep = (String)document.getProperty("noWordSep");
  		if(noWordSep == null)
  			noWordSep = "";
  
  		String lineText = getLineText(line);
  
  		int wordStart = 0;
  		int wordEnd = lineText.length();
  
  		char ch = lineText.charAt(Math.max(0,offset - 1));
  
  		// special case for whitespace (fry 0122, bug #348)
  		// this is really nasty.. turns out that double-clicking any non-letter
  		// or digit char gets lumped together.. sooo, this quickly gets messy,
  		// because really it needs to check whether the chars are of the same
  		// type.. so a double space or double - might be grouped together,
  		// but what about a +=1? do + and - get grouped but not the 1? blech,
  		// coming back to this later. it's not a difficult fix, just a
  		// time-consuming one to track down all the proper cases.
  		/*
  		if (ch == ' ') {
  			//System.out.println("yeehaa");
  
  			for(int i = offset - 1; i >= 0; i--) {
  				if (lineText.charAt(i) == ' ') {
  					wordStart = i;
  				} else {
  					break;
  				}
  			}
  			for(int i = offset; i < lineText.length(); i++) {
  				if (lineText.charAt(i) == ' ') {
  					wordEnd = i + 1;
  				} else {
  					break;
  				}
  			}
  
  		} else {
  		*/
  
  		// If the user clicked on a non-letter char,
  		// we select the surrounding non-letters
  		boolean selectNoLetter = !isWordCharacter(ch,noWordSep);
  
  