gt:             int effectiveEndOffset = endOffset + (includesEof ? 1 : 0);
context: /*
   *  Copyright (c) 2012 Sam Harwell
   *  All rights reserved.
   *
   *  The source code of this document is proprietary work, and is not licensed for
   *  distribution. For information about licensing, contact Sam Harwell at:
   *      sam@tunnelvisionlabs.com
   */
  package org.tvl.netbeans.editor.whitespace;
  
  import java.awt.Color;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  import javax.swing.event.DocumentEvent;
  import javax.swing.event.DocumentListener;
  import javax.swing.text.AttributeSet;
  import javax.swing.text.BadLocationException;
  import javax.swing.text.Document;
  import javax.swing.text.MutableAttributeSet;
  import javax.swing.text.SimpleAttributeSet;
  import javax.swing.text.StyleConstants;
  import javax.swing.text.StyledDocument;
  import org.netbeans.api.annotations.common.NonNull;
  import org.netbeans.api.editor.mimelookup.MimeLookup;
  import org.netbeans.api.editor.mimelookup.MimePath;
  import org.netbeans.api.editor.mimelookup.MimeRegistration;
  import org.netbeans.api.editor.settings.FontColorSettings;
  import org.netbeans.spi.editor.highlighting.HighlightsLayer;
  import org.netbeans.spi.editor.highlighting.HighlightsLayerFactory;
  import org.netbeans.spi.editor.highlighting.HighlightsSequence;
  import org.netbeans.spi.editor.highlighting.ZOrder;
  import org.netbeans.spi.editor.highlighting.support.AbstractHighlightsContainer;
  import org.openide.util.Lookup;
  import org.openide.util.Parameters;
  import org.openide.util.WeakListeners;
  
  /**
   *
   * @author Sam Harwell
   */
  public class WhitespaceHighlighter extends AbstractHighlightsContainer {
      // -J-Dorg.tvl.netbeans.editor.whitespace.WhitespaceHighlighter.level=FINE
      private static final Logger LOGGER = Logger.getLogger(WhitespaceHighlighter.class.getName());
  
      private final StyledDocument document;
      private final AttributeSet attributes;
  
      protected WhitespaceHighlighter(@NonNull StyledDocument document) {
          Parameters.notNull("document", document);
  
          this.document = document;
  
          Lookup lookup = MimeLookup.getLookup(MimePath.EMPTY);
          FontColorSettings settings = lookup.lookup(FontColorSettings.class);
          this.attributes = getFontAndColors(settings, "whitespace");
  
          if (this.attributes != null) {
              this.document.addDocumentListener(WeakListeners.document(new DocumentListenerImpl(), document));
          }
      }
  
      protected StyledDocument getDocument() {
          return document;
      }
  
      protected static AttributeSet getFontAndColors(FontColorSettings settings, String category) {
          AttributeSet attributes = settings.getTokenFontColors(category);
          return attributes;
      }
  
      @Override
      public HighlightsSequence getHighlights(int startOffset, int endOffset) {
          if (attributes == null) {
              return HighlightsSequence.EMPTY;
          }
  
          return new HighlightsSequenceImpl(document, startOffset, endOffset, attributes);
      }
  
      @MimeRegistration(mimeType="", service=HighlightsLayerFactory.class)
      public static class LayerFactory implements HighlightsLayerFactory {
  
          @Override
          public HighlightsLayer[] createLayers(Context context) {
              Document document = context.getDocument();
              if (!(document instanceof StyledDocument)) {
                  return new HighlightsLayer[0];
              }
  
              WhitespaceHighlighter highlighter = (WhitespaceHighlighter)document.getProperty(WhitespaceHighlighter.class);
              if (highlighter == null) {
                  highlighter = createHighlighter(context);
                  document.putProperty(WhitespaceHighlighter.class, highlighter);
              }
  
              return new HighlightsLayer[] { HighlightsLayer.create(WhitespaceHighlighter.class.getName(), getPosition(), true, highlighter) };
          }
  
          protected WhitespaceHighlighter createHighlighter(Context context) {
              return new WhitespaceHighlighter((StyledDocument)context.getDocument());
          }
  
          protected ZOrder getPosition() {
              return ZOrder.SYNTAX_RACK.forPosition(1000);
          }
      }
  
      public class DocumentListenerImpl implements DocumentListener {
  
          @Override
          public void insertUpdate(DocumentEvent e) {
              fireHighlightsChange(e.getOffset(), e.getOffset() + e.getLength());
          }
  
          @Override
          public void removeUpdate(DocumentEvent e) {
          }
  
          @Override
          public void changedUpdate(DocumentEvent e) {
              fireHighlightsChange(e.getOffset(), e.getOffset() + e.getLength());
          }
  
      }
  
      protected static class HighlightsSequenceImpl implements HighlightsSequence {
          private static final int BLOCK_SIZE = 1024;
          private static final AttributeSet NEWLINE_ATTRIBUTES;
  
          private final StyledDocument document;
          private final int startOffset;
          private final int endOffset;
          private final AttributeSet attributes;
          private final boolean includesEof;
  
          private int currentOffset;
          private int currentBlockOffset;
          private String currentBlock;
  
          private int currentWhitespaceStart;
          private int currentWhitespaceEnd;
          private boolean currentNewline;
          private boolean finished;
  
          static {
              MutableAttributeSet attributes = new SimpleAttributeSet();
              attributes.addAttribute(StyleConstants.Foreground, new Color(0, 0, 0, 0));
              NEWLINE_ATTRIBUTES = attributes.copyAttributes();
          }
  
          private HighlightsSequenceImpl(@NonNull StyledDocument document, int startOffset, int endOffset, @NonNull AttributeSet attributes) {
              Parameters.notNull("document", document);
              Parameters.notNull("attributes", attributes);
  
              this.document = document;
              this.startOffset = startOffset;
              int documentLength = document.getLength();
              this.endOffset = Math.min(endOffset, documentLength);
              this.includesEof = endOffset >= documentLength;
              this.attributes = attributes;
  
              this.currentOffset = startOffset;
              this.currentBlockOffset = startOffset;
              this.currentBlock = "";
          }
  
          @Override
          public boolean moveNext() {
              if (finished) {
                  return false;
              }
  
              int whitespaceStart = Integer.MAX_VALUE;
              boolean inWhitespace = false;
              boolean newline = false;
  
              int effectiveEndOffset = endOffset + (includesEof ? 1 : 0);
  
              searchLoop:
              while (currentOffset < effectiveEndOffset) {
                  int offsetInBlock = currentOffset - currentBlockOffset;
                  while (offsetInBlock >= currentBlock.length()) {
                      int previousBlockEnd = currentBlockOffset + currentBlock.length();
                      if (!nextBlock()) {
                          break searchLoop;
                      }
  
                      int currentBlockEnd = currentBlockOffset + currentBlock.length();
                      assert currentBlockEnd > previousBlockEnd : "Whitespace search terminated due to lack of forward progress."; // NOI18N
                      if (currentBlockEnd <= previousBlockEnd) {
                          break searchLoop;
                      }
  
                      offsetInBlock = currentOffset - currentBlockOffset;
                  }
  
                  char c = currentBlock.charAt(offsetInBlock);
                  if (inWhitespace) {
                      if (newline && c != '\n' && c != '\r') {
                          break;
                      } else if (!newline && (!Character.isWhitespace(c) || c == '\n' || c == '\r')) {
                          break;
                      }
                  } else if (Character.isWhitespace(c)) {
                      whitespaceStart = currentOffset;
                      inWhitespace = true;
                      newline = c == '\r' || c == '\n';
                  }
                  
                  currentOffset++;
              }
  
              if (currentOffset > whitespaceStart) {
                  currentWhitespaceStart = whitespaceStart;
                  currentWhitespaceEnd = currentOffset;
                  currentNewline = newline;
                  return true;
              } else {
                  currentWhitespaceStart = currentOffset;
                  currentWhitespaceEnd = currentOffset;
                  currentNewline = false;
                  return false;
              }
          }
  
          @Override
          public int getStartOffset() {
              return currentWhitespaceStart;
          }
  
          @Override
          public int getEndOffset() {
              return currentWhitespaceEnd;
          }
  
          @Override
          public AttributeSet getAttributes() {
              return currentNewline ? NEWLINE_ATTRIBUTES : attributes;
          }
  
          private boolean nextBlock() {
  