[b/9260695] Added a preference to enable/disable the smart semicolon.

While the feature is very handy, it causes delays while typing and can
be extremely annoying for some users. The same auto-id functionality is
available through content assist independent of the new preference.

Change-Id: Ic166b7b42d93037a108da073c2b004057d28179e
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/semicolon/SmartSemicolonHandler.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/semicolon/SmartSemicolonHandler.java
index 5c21247..47c6389 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/semicolon/SmartSemicolonHandler.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/semicolon/SmartSemicolonHandler.java
@@ -57,10 +57,6 @@
 public class SmartSemicolonHandler extends SmartInsertHandler {
   private static final Pattern NUMBERS_PATTERN = compile("[\\d]+");
 
-  private static final IUnitOfWork.Void<XtextResource> NULL_UNIT_OF_WORK = new IUnitOfWork.Void<XtextResource>() {
-    @Override public void process(XtextResource resource) {}
-  };
-
   private static Logger logger = Logger.getLogger(SmartSemicolonHandler.class);
 
   @Inject private CommentNodesFinder commentNodesFinder;
@@ -71,13 +67,15 @@
   @Inject private Protobufs protobufs;
   @Inject private Resources resources;
   @Inject private IPreferenceStoreAccess storeAccess;
+  private NumericTagPreferences preferences;
 
   private static final String SEMICOLON = CommonKeyword.SEMICOLON.toString();
 
   @Override protected void insertContent(XtextEditor editor, StyledText styledText) {
+    preferences = new NumericTagPreferences(storeAccess);
     StyledTextAccess styledTextAccess = new StyledTextAccess(styledText);
     String line = styledTextAccess.lineAtCaretOffset();
-    if (line.endsWith(SEMICOLON)) {
+    if (!preferences.isSmartSemicolonEnabled() || line.endsWith(SEMICOLON)) {
       styledTextAccess.insert(SEMICOLON);
       return;
     }
@@ -89,15 +87,11 @@
     final AtomicBoolean shouldInsertSemicolon = new AtomicBoolean(true);
     final IXtextDocument document = editor.getDocument();
     final List<Pair<EObject, Long>> commentsToUpdate = Lists.newLinkedList();
+    final List<Pair<Literal, Long>> indexesToUpdate1 = Lists.newLinkedList();
+    final List<Pair<MessageField, Long>> indexesToUpdate2 = Lists.newLinkedList();
 
-    document.readOnly(NULL_UNIT_OF_WORK); // wait for reconciler to finish its work.
     try {
-      /*
-       * Textual and semantic updates cannot be done in the same IUnitOfWork (throws an 
-       * IllegalStateException), so index updates (semantic) are done first and tracked in the 
-       * commentsToUpdate list, then a 2nd IUnitOfWork processes the comment updates (textual).
-       */
-      document.modify(new IUnitOfWork.Void<XtextResource>() {
+      document.readOnly(new IUnitOfWork.Void<XtextResource>() {
         @Override public void process(XtextResource resource) {
           Protobuf root = resources.rootOf(resource);
           if (!protobufs.isProto2(root) /*|| !resource.getErrors().isEmpty()*/) {
@@ -118,7 +112,7 @@
               Literal literal = (Literal) model;
               if (shouldCalculateIndex(literal, LITERAL__INDEX)) {
                 long index = literals.calculateNewIndexOf(literal);
-                literal.setIndex(index);
+                indexesToUpdate1.add(Tuples.create(literal, index));
                 commentsToUpdate.add(Tuples.create(model, index));
                 shouldInsertSemicolon.set(false);
               }
@@ -127,7 +121,7 @@
               MessageField field = (MessageField) model;
               if (shouldCalculateIndex(field)) {
                 long index = indexedElements.calculateNewIndexFor(field);
-                field.setIndex(index);
+                indexesToUpdate2.add(Tuples.create(field, index));
                 commentsToUpdate.add(Tuples.create(model, index));
                 shouldInsertSemicolon.set(false);
               }
@@ -136,14 +130,34 @@
         }
       });
 
+      /*
+       * Textual and semantic updates cannot be done in the same IUnitOfWork (throws an
+       * IllegalStateException), so index updates (semantic) are done first and tracked in the
+       * commentsToUpdate list, then a 2nd IUnitOfWork processes the comment updates (textual).
+       */
+      if (!indexesToUpdate1.isEmpty() || !indexesToUpdate2.isEmpty()) {
+        document.modify(new IUnitOfWork.Void<XtextResource>() {
+            @Override public void process(XtextResource resource) {
+              for (Pair<Literal, Long> indexUpdate : indexesToUpdate1) {
+                indexUpdate.getFirst().setIndex(indexUpdate.getSecond());
+              }
+
+              for (Pair<MessageField, Long> indexUpdate : indexesToUpdate2) {
+                indexUpdate.getFirst().setIndex(indexUpdate.getSecond());
+              }
+            }
+          });
+      }
+
       if (!commentsToUpdate.isEmpty()) {
         document.modify(new IUnitOfWork.Void<XtextResource>() {
-          @Override public void process(XtextResource resource) {
-            for (Pair<EObject, Long> updateInfo : commentsToUpdate) {
-              updateIndexInCommentOfParent(updateInfo.getFirst(), updateInfo.getSecond(), document);
+            @Override
+            public void process(XtextResource resource) {
+              for (Pair<EObject, Long> updateInfo : commentsToUpdate) {
+                updateIndexInCommentOfParent(updateInfo.getFirst(), updateInfo.getSecond(), document);
+              }
             }
-          }
-        });
+          });
       }
     } catch (Throwable t) {
       shouldInsertSemicolon.set(true);
@@ -158,7 +172,7 @@
     INode node = nodes.firstNodeForFeature(target, indexAttribute);
     return node == null || isEmpty(node.getText());
   }
-  
+
   private boolean shouldCalculateIndex(IndexedElement target) {
     return indexedElements.indexOf(target) <= 0;
   }
@@ -174,7 +188,7 @@
     if (parent == null) {
       return;
     }
-    NumericTagPreferences preferences = new NumericTagPreferences(storeAccess);
+
     for (String pattern : preferences.patterns()) {
       Pair<INode, Matcher> match = commentNodesFinder.matchingCommentNode(parent, pattern);
       if (match == null) {
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/Messages.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/Messages.java
index 4e7c8fa..1b9a06c 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/Messages.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/Messages.java
@@ -24,6 +24,7 @@
   public static String pattern;
   public static String remove;
   public static String testPattern;
+  public static String enableSmartSemicolon;
 
   static {
     Class<Messages> type = Messages.class;
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/Messages.properties b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/Messages.properties
index 47f2798..a97daff 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/Messages.properties
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/Messages.properties
@@ -1,3 +1,4 @@
+enableSmartSemicolon=Enable Smart Semicolon
 add=&Add
 addNewPattern=Add New Pattern
 edit=&Edit
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/NumericTagPreferencePage.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/NumericTagPreferencePage.java
index 18413ec..e0d7e28 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/NumericTagPreferencePage.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/NumericTagPreferencePage.java
@@ -8,16 +8,23 @@
  */
 package com.google.eclipse.protobuf.ui.preferences.editor.numerictag;
 
-import static org.eclipse.jface.window.Window.OK;
-
 import static com.google.eclipse.protobuf.ui.preferences.editor.numerictag.AddOrEditPatternDialog.addPattern;
 import static com.google.eclipse.protobuf.ui.preferences.editor.numerictag.AddOrEditPatternDialog.editPattern;
 import static com.google.eclipse.protobuf.ui.preferences.editor.numerictag.Messages.add;
 import static com.google.eclipse.protobuf.ui.preferences.editor.numerictag.Messages.edit;
+import static com.google.eclipse.protobuf.ui.preferences.editor.numerictag.Messages.enableSmartSemicolon;
 import static com.google.eclipse.protobuf.ui.preferences.editor.numerictag.Messages.pageDescription;
 import static com.google.eclipse.protobuf.ui.preferences.editor.numerictag.Messages.remove;
+import static com.google.eclipse.protobuf.ui.preferences.editor.numerictag.PreferenceNames.ENABLE_SMART_SEMICOLON;
 import static com.google.eclipse.protobuf.ui.preferences.editor.numerictag.PreferenceNames.NUMERIC_TAG_PATTERNS;
+import static com.google.eclipse.protobuf.ui.preferences.pages.binding.BindingToButtonSelection.bindSelectionOf;
 import static com.google.eclipse.protobuf.ui.preferences.pages.binding.BindingToListItems.bindItemsOf;
+import static org.eclipse.jface.window.Window.OK;
+
+import com.google.eclipse.protobuf.ui.preferences.StringSplitter;
+import com.google.eclipse.protobuf.ui.preferences.pages.PreferenceAndPropertyPage;
+import com.google.eclipse.protobuf.ui.preferences.pages.binding.PreferenceBinder;
+import com.google.eclipse.protobuf.ui.preferences.pages.binding.PreferenceFactory;
 
 import org.eclipse.jface.viewers.ListViewer;
 import org.eclipse.swt.SWT;
@@ -30,11 +37,6 @@
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.List;
 
-import com.google.eclipse.protobuf.ui.preferences.StringSplitter;
-import com.google.eclipse.protobuf.ui.preferences.pages.PreferenceAndPropertyPage;
-import com.google.eclipse.protobuf.ui.preferences.pages.binding.PreferenceBinder;
-import com.google.eclipse.protobuf.ui.preferences.pages.binding.PreferenceFactory;
-
 /**
  * Preference page where users can specify the patterns to use to match comments where "the next id" is being tracked.
  *
@@ -43,12 +45,18 @@
 public class NumericTagPreferencePage extends PreferenceAndPropertyPage {
   private static final String PREFERENCE_PAGE_ID = NumericTagPreferencePage.class.getName();
 
+  private Button btnEnableSmartSemicolon;
   private List lstPatterns;
   private Button btnAdd;
   private Button btnEdit;
   private Button btnRemove;
 
   @Override protected void doCreateContents(Composite parent) {
+    btnEnableSmartSemicolon = new Button(parent, SWT.CHECK);
+    btnEnableSmartSemicolon.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1));
+    btnEnableSmartSemicolon.setText(enableSmartSemicolon);
+    new Label(parent, SWT.NONE);
+
     Label lblDescription = new Label(parent, SWT.WRAP);
     GridData gridData = new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1);
     gridData.widthHint = 150; // only expand further if anyone else requires it
@@ -132,6 +140,7 @@
   @Override protected void setupBinding(PreferenceBinder binder, PreferenceFactory factory) {
     StringSplitter splitter = NumericTagPatternSplitter.instance();
     binder.add(bindItemsOf(lstPatterns).to(factory.newStringListPreference(NUMERIC_TAG_PATTERNS, splitter)));
+    binder.add(bindSelectionOf(btnEnableSmartSemicolon).to(factory.newBooleanPreference(ENABLE_SMART_SEMICOLON)));
   }
 
   @Override protected void onProjectSettingsActivation(boolean projectSettingsActive) {}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/NumericTagPreferences.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/NumericTagPreferences.java
index 418b3e5..dd3edb2 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/NumericTagPreferences.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/NumericTagPreferences.java
@@ -8,6 +8,7 @@
  */
 package com.google.eclipse.protobuf.ui.preferences.editor.numerictag;
 
+import static com.google.eclipse.protobuf.ui.preferences.editor.numerictag.PreferenceNames.ENABLE_SMART_SEMICOLON;
 import static com.google.eclipse.protobuf.ui.preferences.editor.numerictag.PreferenceNames.NUMERIC_TAG_PATTERNS;
 
 import java.util.List;
@@ -31,10 +32,15 @@
     return NumericTagPatternSplitter.instance().splitIntoList(value);
   }
 
+  public boolean isSmartSemicolonEnabled() {
+    return store.getBoolean(ENABLE_SMART_SEMICOLON);
+  }
+
   public static class Initializer implements IPreferenceStoreInitializer {
     @Override public void initialize(IPreferenceStoreAccess storeAccess) {
       IPreferenceStore store = storeAccess.getWritablePreferenceStore();
       store.setDefault(NUMERIC_TAG_PATTERNS, "Next[\\s]+Id:[\\s]+[\\d]+");
+      store.setDefault(ENABLE_SMART_SEMICOLON, true);
     }
   }
 }
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/PreferenceNames.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/PreferenceNames.java
index 43ebd1b..67eecd7 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/PreferenceNames.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/editor/numerictag/PreferenceNames.java
@@ -13,6 +13,7 @@
  */
 final class PreferenceNames {
   static final String NUMERIC_TAG_PATTERNS = "numericTag.patterns";
+  static final String ENABLE_SMART_SEMICOLON = "numericTag.enableSmartSemicolon";
 
   private PreferenceNames() {}
 }