Fixed: [ Issue 86 ] Bugs with automatic field number generation when
pressing semicolon.

Added recognition of comments and strings.
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/SmartSemicolonHandler.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/SmartSemicolonHandler.java
index abc31c1..d6d6190 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/SmartSemicolonHandler.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/SmartSemicolonHandler.java
@@ -11,8 +11,14 @@
 import static com.google.eclipse.protobuf.grammar.CommonKeyword.SEMICOLON;
 import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.*;
 
+import com.google.eclipse.protobuf.protobuf.*;
+import com.google.eclipse.protobuf.ui.util.*;
+import com.google.eclipse.protobuf.util.*;
+import com.google.inject.Inject;
+
 import org.eclipse.emf.ecore.EObject;
 import org.eclipse.swt.custom.StyledText;
+import org.eclipse.xtext.*;
 import org.eclipse.xtext.nodemodel.INode;
 import org.eclipse.xtext.resource.XtextResource;
 import org.eclipse.xtext.ui.editor.XtextEditor;
@@ -21,11 +27,6 @@
 import org.eclipse.xtext.util.concurrent.IUnitOfWork;
 import org.eclipse.xtext.validation.IConcreteSyntaxValidator.InvalidConcreteSyntaxException;
 
-import com.google.eclipse.protobuf.protobuf.*;
-import com.google.eclipse.protobuf.ui.util.*;
-import com.google.eclipse.protobuf.util.ModelNodes;
-import com.google.inject.Inject;
-
 /**
  * Inserts a semicolon at the end of a line, regardless of the current position of the caret in the editor. If the
  * line of code being edited is a property or enum literal and if it does not have an index yet, this handler will
@@ -44,64 +45,97 @@
 
   /** {@inheritDoc} */
   @Override protected void insertContent(XtextEditor editor, StyledText styledText) {
-    int originalCaretOffset = styledText.getCaretOffset();
-    int lineAtOffset = styledText.getLineAtOffset(originalCaretOffset);
+    int offset = styledText.getCaretOffset();
+    int lineAtOffset = styledText.getLineAtOffset(offset);
     int offsetAtLine = styledText.getOffsetAtLine(lineAtOffset);
     String line = styledText.getLine(lineAtOffset);
-    if (line.endsWith(semicolon)) {
-      behaveLikeRegularEditing(styledText, originalCaretOffset);
-      return;
+    ContentToInsert newContent = newContent(editor, styledText, line);
+    if (newContent.equals(ContentToInsert.NONE)) return;
+    if (newContent.location.equals(Location.END)) {
+      offset = offsetAtLine + line.length();
+      styledText.setCaretOffset(offset);
     }
-    String content = contentToInsert(editor, originalCaretOffset);
-    if (content == null) return;
-    int endOfLineOffset = offsetAtLine + line.length();
-    styledText.setCaretOffset(endOfLineOffset);
-    insert(styledText, content, endOfLineOffset);
+    styledText.insert(newContent.value);
+    styledText.setCaretOffset(offset + newContent.value.length());
   }
-
-  private void behaveLikeRegularEditing(StyledText styledText, int caretOffset) {
-    insert(styledText, semicolon, caretOffset);
-  }
-
-  private void insert(StyledText styledText, String content, int caretOffset) {
-    styledText.insert(content);
-    styledText.setCaretOffset(caretOffset + content.length());
-  }
-
-  private String contentToInsert(final XtextEditor editor, final int offset) {
+  
+  private ContentToInsert newContent(final XtextEditor editor, final StyledText styledText, final String line) {
     try {
-      return editor.getDocument().modify(new IUnitOfWork<String, XtextResource>() {
-        public String exec(XtextResource state) {
+      return editor.getDocument().modify(new IUnitOfWork<ContentToInsert, XtextResource>() {
+        public ContentToInsert exec(XtextResource state) {
+          int offset = styledText.getCaretOffset();
           ContentAssistContext[] context = contextFactory.create(editor.getInternalSourceViewer(), offset, state);
-          if (context == null || context.length == 0) return semicolon;
           for (ContentAssistContext c : context) {
+            INode currentNode = c.getCurrentNode();
+            if (nodes.wasCreatedByAnyComment(currentNode) || wasCreatedByString(currentNode)) break;
             EObject model = c.getCurrentModel();
-            if (model instanceof Literal)
-              return contentToInsert((Literal) model);
-            if (model instanceof Property)
-              return contentToInsert((Property) model);
+            if (model instanceof FieldOption) {
+              FieldOption option = (FieldOption) model;
+              model = option.eContainer();
+            }
+            if (line.endsWith(semicolon)) break;
+            if (model instanceof Literal) {
+              Literal literal = (Literal) model;
+              ContentToInsert content = newContent(literal);
+              if (content.equals(ContentToInsert.NONE)) {
+                int index = literals.calculateIndexOf(literal);
+                literal.setIndex(index);
+              }
+              return content;
+            }
+            if (model instanceof Property) {
+              Property property = (Property) model;
+              ContentToInsert content = newContent(property);
+              if (content.equals(ContentToInsert.NONE)) {
+                int index = fields.calculateTagNumberOf(property);
+                property.setIndex(index);
+              }
+              return content;
+            }
           }
-          return semicolon;
+          return new ContentToInsert(semicolon, Location.CURRENT);
         }
       });
-    } catch (InvalidConcreteSyntaxException e) {
-      return null;
+    } catch (InvalidConcreteSyntaxException e) {}
+    return ContentToInsert.NONE;
+  }
+
+  private boolean wasCreatedByString(INode node) {
+    EObject grammarElement = node.getGrammarElement();
+    if (!(grammarElement instanceof RuleCall)) return false;
+    AbstractRule rule = ((RuleCall) grammarElement).getRule();
+    if (!(rule instanceof TerminalRule)) return false;
+    TerminalRule terminalRule = (TerminalRule) rule;
+    return "STRING".equals(terminalRule.getName());
+  }
+
+  private ContentToInsert newContent(Literal literal) {
+    INode indexNode = nodes.firstNodeForFeature(literal, LITERAL__INDEX);
+    return newContent(indexNode);
+  }
+  
+  private ContentToInsert newContent(Property property) {
+    INode indexNode = nodes.firstNodeForFeature(property, FIELD__INDEX);
+    return newContent(indexNode);
+  }
+
+  private ContentToInsert newContent(INode indexNode) {
+    return (indexNode != null) ? new ContentToInsert(semicolon, Location.END) : ContentToInsert.NONE;
+  }
+  
+  private static class ContentToInsert {
+    final String value;
+    final Location location;
+
+    static final ContentToInsert NONE = new ContentToInsert("", Location.NONE);
+    
+    ContentToInsert(String value, Location location) {
+      this.value = value;
+      this.location = location;
     }
   }
-
-  private String contentToInsert(Literal literal) {
-    INode indexNode = nodes.firstNodeForFeature(literal, LITERAL__INDEX);
-    if (indexNode != null) return semicolon;
-    int index = literals.calculateIndexOf(literal);
-    literal.setIndex(index);
-    return null;
-  }
-
-  private String contentToInsert(Property property) {
-    INode indexNode = nodes.firstNodeForFeature(property, FIELD__INDEX);
-    if (indexNode != null) return semicolon;
-    int index = fields.calculateTagNumberOf(property);
-    property.setIndex(index);
-    return null;
+  
+  private static enum Location {
+    NONE, CURRENT, END;
   }
 }
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/documentation/ProtobufDocumentationProvider.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/documentation/ProtobufDocumentationProvider.java
index 97fbe20..15fecd8 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/documentation/ProtobufDocumentationProvider.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/documentation/ProtobufDocumentationProvider.java
@@ -33,7 +33,6 @@
     delegates.add(p2);
   }
 
-
   /** {@inheritDoc} */
   public String getDocumentation(EObject o) {
     for (IEObjectDocumentationProvider p: delegates) {
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/documentation/SingleLineCommentDocumentationProvider.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/documentation/SingleLineCommentDocumentationProvider.java
index 58598c3..1f787f6 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/documentation/SingleLineCommentDocumentationProvider.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/documentation/SingleLineCommentDocumentationProvider.java
@@ -11,16 +11,15 @@
 import static com.google.eclipse.protobuf.util.CommonWords.space;
 import static org.eclipse.xtext.nodemodel.util.NodeModelUtils.getNode;
 
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.xtext.TerminalRule;
-import org.eclipse.xtext.documentation.IEObjectDocumentationProvider;
-import org.eclipse.xtext.nodemodel.*;
-
 import com.google.eclipse.protobuf.protobuf.*;
 import com.google.eclipse.protobuf.scoping.*;
-import com.google.eclipse.protobuf.util.CommonWords;
+import com.google.eclipse.protobuf.util.ModelNodes;
 import com.google.inject.Inject;
 
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.xtext.documentation.IEObjectDocumentationProvider;
+import org.eclipse.xtext.nodemodel.*;
+
 /**
  * Provides single line comments of a protobuf element as its documentation when hovered.
  *
@@ -33,8 +32,7 @@
   private static final String UNIX_NEW_LINE = "\\n";
 
   @Inject private ProtoDescriptorProvider descriptorProvider;
-
-  private static final String RULE_NAME = "SL_COMMENT";
+  @Inject private ModelNodes nodes;
 
   public String getDocumentation(EObject o) {
     String comment = findComment(o);
@@ -48,7 +46,7 @@
     StringBuilder commentBuilder = new StringBuilder();
     for (INode currentNode : node.getAsTreeIterable()) {
       if (currentNode instanceof ILeafNode && !((ILeafNode) currentNode).isHidden()) break;
-      if (currentNode instanceof ILeafNode && isSingleLineCommentTerminalRule(currentNode.getGrammarElement())) {
+      if (currentNode instanceof ILeafNode && nodes.wasCreatedBySingleLineComment(currentNode)) {
         String comment = ((ILeafNode) currentNode).getText();
         commentBuilder.append(cleanUp(comment));
       }
@@ -70,12 +68,6 @@
     return o;
   }
 
-  private boolean isSingleLineCommentTerminalRule(EObject o) {
-    if (!(o instanceof TerminalRule)) return false;
-    TerminalRule rule = (TerminalRule) o;
-    return RULE_NAME.equalsIgnoreCase(rule.getName());
-  }
-
   private String cleanUp(String comment) {
     return comment.replaceFirst(COMMENT_START, "")
                   .replaceAll(WINDOWS_NEW_LINE, space())
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/ModelNodes.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/ModelNodes.java
index a720813..bdd8f79 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/ModelNodes.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/ModelNodes.java
@@ -13,6 +13,7 @@
 import com.google.inject.Singleton;
 
 import org.eclipse.emf.ecore.*;
+import org.eclipse.xtext.TerminalRule;
 import org.eclipse.xtext.nodemodel.INode;
 
 import java.util.List;
@@ -25,9 +26,47 @@
 @Singleton
 public class ModelNodes {
 
-  public INode firstNodeForFeature(EObject semanticObject, EStructuralFeature structuralFeature) {
-    List<INode> nodes = findNodesForFeature(semanticObject, structuralFeature);
+  private static final String SINGLE_LINE_COMMENT_RULE_NAME = "SL_COMMENT";
+
+  /**
+   * Returns the first node that was used to assign values to the given feature for the given object.
+   * @param o the given object.
+   * @param feature the given feature.
+   * @return the first node that was used to assign values to the given feature for the given object, or {@code null} if
+   * a node cannot be found.
+   */
+  public INode firstNodeForFeature(EObject o, EStructuralFeature feature) {
+    List<INode> nodes = findNodesForFeature(o, feature);
     if (nodes.isEmpty()) return null;
     return nodes.get(0);
   }
+  
+  /**
+   * Indicates whether the given node was created by a single- or multi-line comment.
+   * @param node the node to check.
+   * @return {@code true} if the given node was created by a single- or multi-line comment; {@code false} otherwise.
+   */
+  public boolean wasCreatedByAnyComment(INode node) {
+    return wasCreatedByComment(node, SINGLE_LINE_COMMENT_RULE_NAME, "ML_COMMENT");
+  }
+
+  /**
+   * Indicates whether the given node was created by a single-line comment.
+   * @param node the node to check.
+   * @return {@code true} if the given node was created by a single-line comment; {@code false} otherwise.
+   */
+  public boolean wasCreatedBySingleLineComment(INode node) {
+    return wasCreatedByComment(node, SINGLE_LINE_COMMENT_RULE_NAME);
+  }
+  
+  private boolean wasCreatedByComment(INode node, String...commentRuleNames) {
+    EObject o = node.getGrammarElement();
+    if (!(o instanceof TerminalRule)) return false;
+    TerminalRule rule = (TerminalRule) o;
+    String actualName = rule.getName();
+    for (String name : commentRuleNames) {
+      if (name.equalsIgnoreCase(actualName)) return true;
+    }
+    return false;
+  }
 }