Fixed: [ Issue 75 ] Add support for enum-level options
https://code.google.com/p/protobuf-dt/issues/detail?id=75

Added support for enum-level options (included content assist.)
Fixed content assist for nan option.
diff --git a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/matchers/EnumHasLiterals.java b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/matchers/EnumHasLiterals.java
index 57cd397..e768799 100644
--- a/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/matchers/EnumHasLiterals.java
+++ b/com.google.eclipse.protobuf.test/src/com/google/eclipse/protobuf/junit/matchers/EnumHasLiterals.java
@@ -8,13 +8,15 @@
  */
 package com.google.eclipse.protobuf.junit.matchers;
 
+import static org.eclipse.xtext.EcoreUtil2.getAllContentsOfType;
+
 import java.util.*;
 
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
 
+import com.google.eclipse.protobuf.protobuf.*;
 import com.google.eclipse.protobuf.protobuf.Enum;
-import com.google.eclipse.protobuf.protobuf.Literal;
 
 /**
  * @author alruiz@google.com (Alex Ruiz)
@@ -42,7 +44,9 @@
 
   private List<String> literalNames(Enum anEnum) {
     List<String> names = new ArrayList<String>();
-    for (Literal literal : anEnum.getLiterals()) names.add(literal.getName());
+    for (Literal literal : getAllContentsOfType(anEnum, Literal.class)) {
+      names.add(literal.getName());
+    }
     return names;
   }
   
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/contentassist/ProtobufProposalProvider.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/contentassist/ProtobufProposalProvider.java
index 68441f6..f4fce6d 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/contentassist/ProtobufProposalProvider.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/contentassist/ProtobufProposalProvider.java
@@ -1,9 +1,10 @@
 /*
  * 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
- *
+ * 
+ * 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.contentassist;
@@ -14,18 +15,8 @@
 import static com.google.eclipse.protobuf.ui.util.Strings.firstCharToLowerCase;
 import static java.lang.String.valueOf;
 import static java.util.Collections.emptyList;
+import static org.eclipse.xtext.EcoreUtil2.getAllContentsOfType;
 
-import java.util.*;
-
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.jface.text.contentassist.ICompletionProposal;
-import org.eclipse.swt.custom.StyledText;
-import org.eclipse.swt.graphics.Image;
-import org.eclipse.xtext.*;
-import org.eclipse.xtext.ui.PluginImageHelper;
-import org.eclipse.xtext.ui.editor.contentassist.*;
-
-import com.google.common.collect.ImmutableList;
 import com.google.eclipse.protobuf.grammar.CommonKeyword;
 import com.google.eclipse.protobuf.protobuf.*;
 import com.google.eclipse.protobuf.protobuf.Enum;
@@ -37,10 +28,22 @@
 import com.google.eclipse.protobuf.util.Properties;
 import com.google.inject.Inject;
 
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.jface.text.contentassist.ICompletionProposal;
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.xtext.*;
+import org.eclipse.xtext.ui.PluginImageHelper;
+import org.eclipse.xtext.ui.editor.contentassist.*;
+
+import java.util.*;
+
 /**
  * @author alruiz@google.com (Alex Ruiz)
- *
- * @see <a href="http://www.eclipse.org/Xtext/documentation/latest/xtext.html#contentAssist">Xtext Content Assist</a>
+ * 
+ * @see <a
+ *      href="http://www.eclipse.org/Xtext/documentation/latest/xtext.html#contentAssist">Xtext
+ *      Content Assist</a>
  */
 public class ProtobufProposalProvider extends AbstractProtobufProposalProvider {
 
@@ -56,8 +59,7 @@
   @Inject private Properties properties;
 
   @Override public void completeProtobuf_Syntax(EObject model, Assignment assignment, ContentAssistContext context,
-      ICompletionProposalAcceptor acceptor) {
-  }
+      ICompletionProposalAcceptor acceptor) {}
 
   @Override public void completeSyntax_Name(EObject model, Assignment assignment, ContentAssistContext context,
       ICompletionProposalAcceptor acceptor) {
@@ -88,16 +90,28 @@
       proposeCommonMessageOptions(context, acceptor);
       return true;
     }
+    if (model instanceof Enum) {
+      proposeCommonEnumOptions(context, acceptor);
+      return true;
+    }
     return false;
   }
 
   private void proposeCommonFileOptions(ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
-    for (Property option : descriptorProvider.get().fileOptions())
-      proposeOption(option, context, acceptor);
+    proposeOptions(descriptorProvider.get().fileOptions(), context, acceptor);
   }
 
   private void proposeCommonMessageOptions(ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
-    for (Property option : descriptorProvider.get().messageOptions())
+    proposeOptions(descriptorProvider.get().messageOptions(), context, acceptor);
+  }
+
+  private void proposeCommonEnumOptions(ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
+    proposeOptions(descriptorProvider.get().enumOptions(), context, acceptor);
+  }
+  
+  private void proposeOptions(Collection<Property> options, ContentAssistContext context,
+      ICompletionProposalAcceptor acceptor) {
+    for (Property option : options)
       proposeOption(option, context, acceptor);
   }
 
@@ -126,44 +140,7 @@
       ICompletionProposalAcceptor acceptor) {}
 
   @Override public void complete_STRING(EObject model, RuleCall ruleCall, ContentAssistContext context,
-      ICompletionProposalAcceptor acceptor) {
-    if (model instanceof Property && isProposalForDefaultValue(context)) {
-      Property p = (Property) model;
-      if (properties.isString(p)) {
-        proposeEmptyString(context, acceptor);
-        return;
-      }
-      if (properties.isBool(p)) {
-        proposeBooleanValues(context, acceptor);
-        return;
-      }
-    }
-    if (model instanceof Option || model instanceof FieldOption || model instanceof Syntax) return;
-    for (AbstractElement element : context.getFirstSetGrammarElements()) {
-      if (!(element instanceof Assignment)) continue;
-      Assignment assignment = (Assignment) element;
-      if (EQUAL.hasValue(assignment.getOperator()) && assignment.getFeature().equals("name")) return;
-    }
-    super.complete_STRING(model, ruleCall, context, acceptor);
-  }
-
-  private void proposeEmptyString(ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
-    createAndAccept(EMPTY_STRING, 1, context, acceptor);
-  }
-
-  private boolean isProposalForDefaultValue(ContentAssistContext context) {
-    return isProposalForAssignment(DEFAULT, context);
-  }
-
-  private boolean isProposalForAssignment(CommonKeyword feature, ContentAssistContext context) {
-    ImmutableList<AbstractElement> grammarElements = context.getFirstSetGrammarElements();
-    for (AbstractElement e : grammarElements) {
-      if (!(e instanceof Assignment)) continue;
-      Assignment a = (Assignment) e;
-      if (feature.hasValue(a.getFeature()) && EQUAL.hasValue(a.getOperator())) return true;
-    }
-    return false;
-  }
+      ICompletionProposalAcceptor acceptor) {}
 
   @Override public void completeKeyword(Keyword keyword, ContentAssistContext context,
       ICompletionProposalAcceptor acceptor) {
@@ -183,16 +160,26 @@
       EObject grammarElement = context.getLastCompleteNode().getGrammarElement();
       if (isKeyword(grammarElement, SYNTAX)) {
         proposeEqualProto2(context, acceptor);
-        return true;
       }
+      return true;
     }
     if (OPENING_BRACKET.hasValue(keyword)) {
       return proposeOpenBracket(context, acceptor);
     }
     if (TRUE.hasValue(keyword) || FALSE.hasValue(keyword)) {
-      if (!isBoolProposalValid(context)) return true;
+      if (isBoolProposalValid(context)) {
+        proposeBooleanValues(context, acceptor);
+      }
+      return true;
     }
-    // remove keyword proposals when current node is "]". At this position we only accept "default" or field options.
+    if (NAN.hasValue(keyword)) {
+      if (isNanProposalValid(context)) {
+        proposeAndAccept(keyword.toString(), context, acceptor);
+      }
+      return true;
+    }
+    // remove keyword proposals when current node is "]". At this position we
+    // only accept "default" or field options.
     return context.getCurrentNode().getText().equals(CLOSING_BRACKET.toString());
   }
 
@@ -201,7 +188,7 @@
   }
 
   private boolean isKeyword(EObject object, CommonKeyword keyword) {
-    return object instanceof Keyword && keyword.hasValue(((Keyword)object).getValue());
+    return object instanceof Keyword && keyword.hasValue(((Keyword) object).getValue());
   }
 
   private boolean isBoolProposalValid(ContentAssistContext context) {
@@ -218,6 +205,20 @@
     return false;
   }
 
+  private boolean isNanProposalValid(ContentAssistContext context) {
+    EObject model = context.getCurrentModel();
+    if (model instanceof Property) return properties.mayBeNan((Property) model);
+    if (model instanceof Option) {
+      Property fileOption = descriptorProvider.get().lookupOption(((Option) model).getName());
+      return fileOption != null && properties.mayBeNan(fileOption);
+    }
+    if (model instanceof FieldOption) {
+      Property fileOption = descriptorProvider.get().lookupOption(((FieldOption) model).getName());
+      return fileOption != null && properties.mayBeNan(fileOption);
+    }
+    return false;
+  }
+
   private boolean proposeOpenBracket(ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
     EObject model = context.getCurrentModel();
     if (!(model instanceof Property)) return false;
@@ -292,9 +293,19 @@
 
   @Override public void completeProperty_Default(EObject model, Assignment assignment, ContentAssistContext context,
       ICompletionProposalAcceptor acceptor) {
-    Enum enumType = finder.enumTypeOf((Property) model);
-    if (enumType == null) return;
-    proposeAndAccept(enumType, context, acceptor);
+    Property property = (Property) model;
+    if (properties.isString(property)) {
+      proposeEmptyString(context, acceptor);
+      return;
+    }
+    Enum enumType = finder.enumTypeOf(property);
+    if (enumType != null) {
+      proposeAndAccept(enumType, context, acceptor);
+    }
+  }
+
+  private void proposeEmptyString(ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
+    createAndAccept(EMPTY_STRING, 1, context, acceptor);
   }
 
   @Override public void completeProperty_Index(EObject model, Assignment assignment, ContentAssistContext context,
@@ -341,8 +352,8 @@
 
   @Override public void completeFieldOption_Name(EObject model, Assignment assignment, ContentAssistContext context,
       ICompletionProposalAcceptor acceptor) {
-    Field field = extractFieldFrom(context);
-    proposeCommonFieldOptions(field, context, acceptor);
+     Field field = extractFieldFrom(context);
+     proposeCommonFieldOptions(field, context, acceptor);
   }
 
   private Field extractFieldFrom(ContentAssistContext context) {
@@ -362,7 +373,8 @@
     List<FieldOption> options = field.getFieldOptions();
     if (options.isEmpty()) return emptyList();
     List<String> optionNames = new ArrayList<String>();
-    for (FieldOption option : options) optionNames.add(option.getName());
+    for (FieldOption option : options)
+      optionNames.add(option.getName());
     return optionNames;
   }
 
@@ -385,7 +397,7 @@
     if (isStringOption && proposal instanceof ConfigurableCompletionProposal) {
       // set cursor between the proposal's quotes
       ConfigurableCompletionProposal configurable = (ConfigurableCompletionProposal) proposal;
-      configurable.setCursorPosition(proposalText.length() - 2);
+      configurable.setCursorPosition(proposalText.length() - 1);
     }
     acceptor.accept(proposal);
   }
@@ -409,18 +421,19 @@
   }
 
   private void proposeBooleanValues(ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
-    CommonKeyword[] keywords = { FALSE, TRUE };
+    CommonKeyword[] keywords = {FALSE, TRUE};
     proposeAndAccept(keywords, context, acceptor);
   }
 
   private void proposeAndAccept(CommonKeyword[] keywords, ContentAssistContext context,
       ICompletionProposalAcceptor acceptor) {
-    for (CommonKeyword keyword : keywords) proposeAndAccept(keyword.toString(), context, acceptor);
+    for (CommonKeyword keyword : keywords)
+      proposeAndAccept(keyword.toString(), context, acceptor);
   }
 
   private void proposeAndAccept(Enum enumType, ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
     Image image = imageHelper.getImage(images.imageFor(Literal.class));
-    for (Literal literal : enumType.getLiterals())
+    for (Literal literal : getAllContentsOfType(enumType, Literal.class))
       proposeAndAccept(literal.getName(), image, context, acceptor);
   }
 
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/Protobuf.xtext b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/Protobuf.xtext
index 61a0f2c..83a0e72 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/Protobuf.xtext
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/Protobuf.xtext
@@ -15,7 +15,7 @@
 generate protobuf "http://www.google.com/eclipse/protobuf/Protobuf"
 
 Protobuf:
-  (syntax=Syntax)?	
+  (syntax=Syntax)?  
   (elements+=ProtobufElement)*;
 
 Syntax:
@@ -66,8 +66,7 @@
 Property:
   modifier=Modifier type=AbstractTypeReference name=Name '=' index=INT 
   (
-    ('[' 'default' '=' default=ValueRef ']') |
-    ('[' 'default' '=' default=ValueRef (',' fieldOptions+=FieldOption)* ']') |
+    ('[' 'default' '=' default=ValueRef ((',' fieldOptions+=FieldOption)*)? ']') |
     ('[' fieldOptions+=FieldOption (',' fieldOptions+=FieldOption)* ']')
   )? ';';
 
@@ -146,9 +145,12 @@
 
 Enum:
   'enum' name=Name '{'
-  literals+=Literal*
+  elements+=EnumElement*
   '}' ';'?;
 
+EnumElement:
+  Option | Literal;
+
 Literal:
   name=Name '=' index=INT ';';
 
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/grammar/CommonKeyword.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/grammar/CommonKeyword.java
index d00648c..4d6fb11 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/grammar/CommonKeyword.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/grammar/CommonKeyword.java
@@ -19,8 +19,9 @@
   // looking for. The code was too complicated and if the grammar changed for some reason, we had to change our
   // implementation anyway.
 
-  BOOL("bool"), TRUE("true"), FALSE("false"), BYTES("bytes"), OPENING_BRACKET("["), CLOSING_BRACKET("]"),
-    DEFAULT("default"), EQUAL("="), SEMICOLON(";"), STRING("string"), SYNTAX("syntax");
+  BOOL("bool"), TRUE("true"), FALSE("false"), BYTES("bytes"), OPENING_BRACKET("["), CLOSING_BRACKET("]"), 
+    DEFAULT("default"), EQUAL("="), SEMICOLON(";"), STRING("string"), SYNTAX("syntax"), NAN("nan"), FLOAT("float"), 
+    DOUBLE("double");
 
   private final String value;
 
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/OptionType.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/OptionType.java
index 0134c92..d7592c6 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/OptionType.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/OptionType.java
@@ -9,5 +9,5 @@
 package com.google.eclipse.protobuf.scoping;
 
 enum OptionType {
-  FILE, MESSAGE, FIELD, METHOD;
+  FILE, MESSAGE, FIELD, ENUM, METHOD;
 }
\ No newline at end of file
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptor.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptor.java
index 37f3efa..d1b15ef 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptor.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtoDescriptor.java
@@ -44,6 +44,7 @@
     OPTION_DEFINITION_BY_NAME.put("FileOptions", FILE);
     OPTION_DEFINITION_BY_NAME.put("MessageOptions", MESSAGE);
     OPTION_DEFINITION_BY_NAME.put("FieldOptions", FIELD);
+    OPTION_DEFINITION_BY_NAME.put("EnumOptions", ENUM);
     OPTION_DEFINITION_BY_NAME.put("MethodOptions", METHOD);
   }
   
@@ -138,7 +139,7 @@
    * @return the option whose name matches the given one or {@code null} if a matching option is not found.
    */
   public Property lookupOption(String name) {
-    return lookupOption(name, FILE, MESSAGE, METHOD);
+    return lookupOption(name, FILE, MESSAGE, ENUM, METHOD);
   }
   
   private Property lookupOption(String name, OptionType...types) {
@@ -184,6 +185,16 @@
     return optionsOfType(FIELD);
   }
 
+  /**
+   * Returns all the enum-level options available. These are the options defined in
+   * {@code google/protobuf/descriptor.proto} (more details can be found
+   * <a href=http://code.google.com/apis/protocolbuffers/docs/proto.html#options" target="_blank">here</a>.)
+   * @return all the enum-level options available.
+   */
+  public Collection<Property> enumOptions() {
+    return optionsOfType(ENUM);
+  }
+
   private Collection<Property> optionsOfType(OptionType type) {
     return unmodifiableCollection(optionsByType.get(type).values());
   }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider.java
index 7feb1fb..a31f3ad 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider.java
@@ -12,6 +12,7 @@
 import static java.util.Collections.emptyList;
 import static org.eclipse.emf.common.util.URI.createURI;
 import static org.eclipse.emf.ecore.util.EcoreUtil.getAllContents;
+import static org.eclipse.xtext.EcoreUtil2.getAllContentsOfType;
 import static org.eclipse.xtext.resource.EObjectDescription.create;
 
 import java.util.*;
@@ -170,11 +171,11 @@
   IScope scope_LiteralRef_literal(LiteralRef literalRef, EReference reference) {
     EObject container = literalRef.eContainer();
     if (container instanceof Property) {
-      Enum enumType = finder.enumTypeOf((Property) container);
-      if (enumType != null) return scopeForLiterals(enumType);
+      Enum anEnum = finder.enumTypeOf((Property) container);
+      if (anEnum != null) return scopeForLiterals(anEnum);
     }
-    Enum enumType = enumTypeOfOption(container);
-    if (enumType != null) return scopeForLiterals(enumType);
+    Enum anEnum = enumTypeOfOption(container);
+    if (anEnum != null) return scopeForLiterals(anEnum);
     return null;
   }
 
@@ -185,14 +186,14 @@
     return null;
   }
 
-  private static IScope scopeForLiterals(Enum enumType) {
-    Collection<IEObjectDescription> descriptions = describeLiterals(enumType);
+  private static IScope scopeForLiterals(Enum anEnum) {
+    Collection<IEObjectDescription> descriptions = describeLiterals(anEnum);
     return createScope(descriptions);
   }
 
-  private static Collection<IEObjectDescription> describeLiterals(Enum enumType) {
+  private static Collection<IEObjectDescription> describeLiterals(Enum anEnum) {
     List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
-    for (Literal literal : enumType.getLiterals())
+    for (Literal literal : getAllContentsOfType(anEnum, Literal.class))
       descriptions.add(create(literal.getName(), literal));
     return descriptions;
   }
@@ -200,4 +201,6 @@
   private static IScope createScope(Iterable<IEObjectDescription> descriptions) {
     return new SimpleScope(descriptions, DO_NOT_IGNORE_CASE);
   }
+  
+  
 }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/Properties.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/Properties.java
index 3ddcd47..254b0ba 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/Properties.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/Properties.java
@@ -44,6 +44,11 @@
   public boolean isBool(Property p) {
     return isScalarType(p, BOOL);
   }
+  
+  public boolean mayBeNan(Property p) {
+    String typeName = typeNameOf(p);
+    return FLOAT.hasValue(typeName) || DOUBLE.hasValue(typeName);
+  }
 
   /**
    * Indicates whether the given property is of type {@code string}.