In progress: [Issue 89] Proto buffer editor needs to have options to
remove whitespace at end of lines

Added code to figure out edited lines of an editor.
diff --git a/com.google.eclipse.protobuf.ui/META-INF/MANIFEST.MF b/com.google.eclipse.protobuf.ui/META-INF/MANIFEST.MF
index e2473fb..3ba9271 100644
--- a/com.google.eclipse.protobuf.ui/META-INF/MANIFEST.MF
+++ b/com.google.eclipse.protobuf.ui/META-INF/MANIFEST.MF
@@ -17,7 +17,8 @@
  com.ibm.icu,

  org.eclipse.emf.databinding,

  org.eclipse.core.resources,

- org.eclipse.core.filesystem;bundle-version="1.3.100"

+ org.eclipse.core.filesystem;bundle-version="1.3.100",

+ org.eclipse.compare.core;bundle-version="3.5.200"

 Import-Package: org.apache.log4j,

  org.apache.commons.logging

 Bundle-RequiredExecutionEnvironment: J2SE-1.5

diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Editors.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Editors.java
new file mode 100644
index 0000000..c9f70de
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Editors.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2011 Google Inc.
+ * 
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * 
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package com.google.eclipse.protobuf.ui.util;
+
+import static com.google.eclipse.protobuf.ui.ProtobufUiModule.PLUGIN_ID;
+import static org.eclipse.compare.rangedifferencer.RangeDifferencer.findDifferences;
+import static org.eclipse.core.runtime.IStatus.ERROR;
+import static org.eclipse.core.runtime.Status.OK_STATUS;
+import static org.eclipse.core.runtime.SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK;
+
+import com.google.inject.Singleton;
+
+import org.apache.log4j.Logger;
+import org.eclipse.compare.rangedifferencer.*;
+import org.eclipse.core.filebuffers.*;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.runtime.*;
+import org.eclipse.jface.text.*;
+
+import java.util.*;
+
+/**
+ * Utility methods related to editors. Adapted from CDT's {@code org.eclipse.cdt.internal.ui.util.EditorUtility}.
+ * 
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+@Singleton 
+public class Editors {
+
+  private static Logger logger = Logger.getLogger(Editors.class);
+  
+  public static IRegion[] calculateChangedLineRegions(final ITextFileBuffer buffer, final IProgressMonitor monitor)
+      throws CoreException {
+    final IRegion[][] result = new IRegion[1][];
+    final IStatus[] errorStatus = new IStatus[] { OK_STATUS };
+    try {
+      SafeRunner.run(new ISafeRunnable() {
+        public void handleException(Throwable exception) {
+          logger.error(exception.getMessage(), exception);
+          String msg = "An error occurred while calculating the changed regions. See error log for details.";
+          errorStatus[0] = new Status(ERROR, PLUGIN_ID, 0, msg, exception);
+          result[0] = null;
+        }
+
+        public void run() throws Exception {
+          monitor.beginTask("Calculating changed regions", 20);
+          IFileStore fileStore = buffer.getFileStore();
+          ITextFileBufferManager fileBufferManager = FileBuffers.createTextFileBufferManager();
+          fileBufferManager.connectFileStore(fileStore, getSubProgressMonitor(monitor, 15));
+          try {
+            IDocument currentDocument = buffer.getDocument();
+            IDocument oldDocument =
+                ((ITextFileBuffer) fileBufferManager.getFileStoreFileBuffer(fileStore)).getDocument();
+            result[0] = getChangedLineRegions(oldDocument, currentDocument);
+          } finally {
+            fileBufferManager.disconnectFileStore(fileStore, getSubProgressMonitor(monitor, 5));
+            monitor.done();
+          }
+        }
+
+        /*
+         * Returns regions of all lines which differ comparing {@code oldDocument}s content with 
+         * {@code currentDocument}s content. Successive lines are merged into one region.
+         */
+        private IRegion[] getChangedLineRegions(IDocument oldDocument, IDocument currentDocument) {
+          RangeDifference[] differences = 
+              findDifferences(new LineComparator(oldDocument), new LineComparator(currentDocument));
+          List<IRegion> regions = new ArrayList<IRegion>();
+          final int numberOfLines = currentDocument.getNumberOfLines();
+          for (RangeDifference current : differences) {
+            if (current.kind() == RangeDifference.CHANGE) {
+              int startLine = Math.min(current.rightStart(), numberOfLines - 1);
+              int endLine = current.rightEnd() - 1;
+              IRegion startLineRegion;
+              try {
+                startLineRegion = currentDocument.getLineInformation(startLine);
+                if (startLine >= endLine) {
+                  // startLine > endLine indicates a deletion of one or more lines.
+                  // Deletions are ignored except at the end of the document.
+                  if (startLine == endLine
+                      || startLineRegion.getOffset() + startLineRegion.getLength() == currentDocument.getLength()) {
+                    regions.add(startLineRegion);
+                  }
+                  continue;
+                } 
+                IRegion endLineRegion = currentDocument.getLineInformation(endLine);
+                int startOffset = startLineRegion.getOffset();
+                int endOffset = endLineRegion.getOffset() + endLineRegion.getLength();
+                regions.add(new Region(startOffset, endOffset - startOffset));
+              } catch (BadLocationException e) {
+                logger.error(e.getMessage(), e);
+              }
+            }
+          }
+          return regions.toArray(new IRegion[regions.size()]);
+        }
+      });
+    } finally {
+      if (!errorStatus[0].isOK()) throw new CoreException(errorStatus[0]);
+    }
+    return result[0];
+  }
+  
+  private static IProgressMonitor getSubProgressMonitor(IProgressMonitor monitor, int ticks) {
+    if (monitor != null) return new SubProgressMonitor(monitor, ticks, PREPEND_MAIN_LABEL_TO_SUBTASK);
+    return new NullProgressMonitor();
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/LineComparator.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/LineComparator.java
new file mode 100644
index 0000000..0515724
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/LineComparator.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2011 Google Inc.
+ * 
+ * All rights reserved. This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * 
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package com.google.eclipse.protobuf.ui.util;
+
+import org.apache.log4j.Logger;
+import org.eclipse.compare.rangedifferencer.IRangeComparator;
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+
+/**
+ * Adapted from CDT's {@code org.eclipse.cdt.internal.ui.text.LineSeparator}.
+ * 
+ * This implementation of <code>IRangeComparator</code> compares lines of a document. The lines are compared using a DJB 
+ * hash function.
+ */
+class LineComparator implements IRangeComparator {
+
+  private static final long UNKNOWN_HASH = Long.MIN_VALUE;
+
+  private static Logger logger = Logger.getLogger(LineComparator.class);
+  
+  private final IDocument document;
+  private final long[] hashes;
+
+  public LineComparator(IDocument document) {
+    this.document = document;
+    hashes = new long[document.getNumberOfLines()];
+    for (int i = 0; i < hashes.length; i++) {
+      hashes[i] = UNKNOWN_HASH;
+    }
+  }
+
+  public int getRangeCount() {
+    return document.getNumberOfLines();
+  }
+
+  public boolean rangesEqual(int thisIndex, IRangeComparator other, int otherIndex) {
+    try {
+      return getHash(thisIndex) == ((LineComparator) other).getHash(otherIndex);
+    } catch (BadLocationException e) {
+      logger.error(e.getMessage(), e);
+      return false;
+    }
+  }
+
+  public boolean skipRangeComparison(int length, int maxLength, IRangeComparator other) {
+    return false;
+  }
+
+  /**
+   * Returns the hash of the given line.
+   * @param line the number of the line in the document to get the hash for.
+   * @return the hash of the line.
+   * @throws BadLocationException if the line number is invalid.
+   */
+  private int getHash(int line) throws BadLocationException {
+    long hash = hashes[line];
+    if (hash == UNKNOWN_HASH) {
+      IRegion lineRegion = document.getLineInformation(line);
+      String lineContents = document.get(lineRegion.getOffset(), lineRegion.getLength());
+      hash = computeDJBHash(lineContents);
+      hashes[line] = hash;
+    }
+    return (int) hash;
+  }
+
+  /**
+   * Compute a hash using the DJB hash algorithm.
+   * @param s the string for which to compute a hash.
+   * @return the DJB hash value of the {@code String}.
+   */
+  private int computeDJBHash(String s) {
+    int hash = 5381;
+    int length = s.length();
+    for (int i = 0; i < length; i++) {
+      char ch = s.charAt(i);
+      hash = (hash << 5) + hash + ch;
+    }
+    return hash;
+  }
+}