In progress: [Issue 104] Update "Next Id" comment when generating a the
tag number of a field or literal.

Fixed generation of tag numbers when parsing is not finished and user
types ";".
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 f57ed44..3583a3b 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
@@ -24,11 +24,13 @@
 import org.eclipse.xtext.ui.editor.contentassist.ContentAssistContext;
 import org.eclipse.xtext.ui.editor.contentassist.antlr.ParserBasedContentAssistContextFactory;
 import org.eclipse.xtext.ui.editor.model.IXtextDocument;
+import org.eclipse.xtext.ui.editor.syntaxcoloring.HighlightingReconciler;
 import org.eclipse.xtext.util.Pair;
 import org.eclipse.xtext.util.concurrent.IUnitOfWork;
 
 import com.google.eclipse.protobuf.grammar.CommonKeyword;
 import com.google.eclipse.protobuf.protobuf.*;
+import com.google.eclipse.protobuf.protobuf.Enum;
 import com.google.eclipse.protobuf.ui.util.*;
 import com.google.eclipse.protobuf.util.ModelNodes;
 import com.google.inject.Inject;
@@ -46,6 +48,7 @@
 
   @Inject private CommentNodesFinder commentNodesFinder;
   @Inject private Fields fields;
+  @Inject private HighlightingReconciler highlightingReconciler;
   @Inject private Literals literals;
   @Inject private ModelNodes nodes;
   @Inject private ParserBasedContentAssistContextFactory contextFactory;
@@ -60,8 +63,18 @@
     int lineAtOffset = styledText.getLineAtOffset(offset);
     int offsetAtLine = styledText.getOffsetAtLine(lineAtOffset);
     String line = styledText.getLine(lineAtOffset);
-    ContentToInsert newContent = newContent(editor, styledText, line);
-    if (newContent.equals(ContentToInsert.NONE)) return;
+    ContentToInsert newContent = ContentToInsert.RETRY;
+    int retryCount = 2;
+    for (int i = 0; i < retryCount; i++) {
+      if (newContent.equals(ContentToInsert.RETRY)) {
+        newContent = newContent(editor, styledText, line);
+      }
+      if (newContent.equals(ContentToInsert.NONE)) return;
+      if (newContent.equals(ContentToInsert.INSERT_TAG_NUMBER)) {
+        refreshHighlighting(editor);
+        return;
+      }
+    }
     if (newContent.location.equals(Location.END)) {
       offset = offsetAtLine + line.length();
       styledText.setCaretOffset(offset);
@@ -82,6 +95,10 @@
           for (ContentAssistContext c : context) {
             if (isCommentOrString(c.getCurrentNode())) continue;
             EObject model = c.getCurrentModel();
+            if (model instanceof Message || model instanceof Enum || model instanceof Protobuf) {
+              // need to retry, parsing may not be finished yet.
+              return ContentToInsert.RETRY;
+            }
             if (model instanceof FieldOption) {
               FieldOption option = (FieldOption) model;
               model = option.eContainer();
@@ -89,7 +106,7 @@
             if (model instanceof Literal) {
               Literal literal = (Literal) model;
               ContentToInsert content = newContent(literal);
-              if (content.equals(ContentToInsert.NONE)) {
+              if (content.equals(ContentToInsert.INSERT_TAG_NUMBER)) {
                 int index = literals.calculateIndexOf(literal);
                 literal.setIndex(index);
                 updateIndexInCommentOfParent(literal, index, document);
@@ -99,7 +116,7 @@
             if (model instanceof Property) {
               Property property = (Property) model;
               ContentToInsert content = newContent(property);
-              if (content.equals(ContentToInsert.NONE)) {
+              if (content.equals(ContentToInsert.INSERT_TAG_NUMBER)) {
                 int index = fields.calculateTagNumberOf(property);
                 property.setIndex(index);
                 updateIndexInCommentOfParent(property, index, document);
@@ -142,35 +159,55 @@
 
   private ContentToInsert newContent(INode indexNode) {
     boolean hasIndex = indexNode != null && !isEmpty(indexNode.getText());
-    return hasIndex ? new ContentToInsert(SEMICOLON, Location.END) : ContentToInsert.NONE;
+    return hasIndex ? new ContentToInsert(SEMICOLON, Location.END) : ContentToInsert.INSERT_TAG_NUMBER;
   }
 
   private void updateIndexInCommentOfParent(EObject o, int index, IXtextDocument document) {
     EObject parent = o.eContainer();
     if (parent == null) return;
-    String pattern = "Next[\\s]+Id:[\\s]+([\\d])+";
+    String pattern = "Next[\\s]+Id:[\\s]+[\\d]+";
     Pair<INode, Matcher> match = commentNodesFinder.matchingCommentNode(parent, pattern);
     if (match == null) return;
     String originalText = match.getSecond().group();
-    String replacement = originalText.replaceAll("[\\d]+", String.valueOf(index + 1));
+    String replacement = originalText.replaceFirst("[\\d]+", String.valueOf(index + 1));
     INode node = match.getFirst();
+    int offset = node.getTotalOffset() + node.getText().indexOf(originalText);
     try {
-      document.replace(node.getOffset() + node.getText().indexOf(originalText), originalText.length(), replacement);
+      document.replace(offset, originalText.length(), replacement);
     } catch (BadLocationException e) {
       logger.error("Unable to update comment tracking next tag number", e);
     }
   }
 
+  private void refreshHighlighting(XtextEditor editor) {
+    editor.getDocument().readOnly(new IUnitOfWork.Void<XtextResource>() {
+      @Override public void process(XtextResource state) throws Exception {
+        highlightingReconciler.modelChanged(state);
+      }
+    });
+  }
+
   private static class ContentToInsert {
     final String value;
     final Location location;
 
-    static final ContentToInsert NONE = new ContentToInsert("", Location.NONE);
+    static final ContentToInsert NONE = new ContentToInsert();
+    static final ContentToInsert INSERT_TAG_NUMBER = new ContentToInsert();
+    static final ContentToInsert RETRY = new ContentToInsert();
+
+    ContentToInsert() {
+      this("", Location.NONE);
+    }
 
     ContentToInsert(String value, Location location) {
       this.value = value;
       this.location = location;
     }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+      return String.format("ContentToInsert [value=%s, location=%s]", value, location);
+    }
   }
 
   private static enum Location {