In progress: [Issue 155] Editor does not support complex custom options.

Working on content assist.
diff --git a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionExtendMessageFieldSource_optionExtendMessageField_Test.java b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionExtendMessageFieldSource_optionExtendMessageField_Test.java
index 101bac3..1e90c24 100644
--- a/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionExtendMessageFieldSource_optionExtendMessageField_Test.java
+++ b/com.google.eclipse.protobuf.integration.test/src/com/google/eclipse/protobuf/scoping/ProtobufScopeProvider_scope_OptionExtendMessageFieldSource_optionExtendMessageField_Test.java
@@ -54,7 +54,7 @@
   // }
   //
   // extend Type {
-  //   optional boolean active = 10;
+  //   optional bool active = 10;
   // }
   //
   // extend google.protobuf.FileOptions {
@@ -80,7 +80,7 @@
   // }
   //
   // extend Type {
-  //   optional boolean active = 10;
+  //   optional bool active = 10;
   // }
   //
   // extend google.protobuf.FieldOptions {
@@ -88,7 +88,7 @@
   // }
   //
   // message Person {
-  //   optional boolean active = 1 [(type).(active) = true];
+  //   optional bool active = 1 [(type).(active) = true];
   // }
   @Test public void should_provide_message_fields_for_first_field_in_field_custom_option() {
     CustomFieldOption option = xtext.find("type", ")", CustomFieldOption.class);
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 8923b87..87cae0d 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
@@ -15,6 +15,7 @@
 import static java.lang.String.valueOf;
 import static java.util.Collections.emptyList;
 import static org.eclipse.xtext.EcoreUtil2.getAllContentsOfType;
+import static org.eclipse.xtext.nodemodel.util.NodeModelUtils.findActualSemanticObjectFor;
 import static org.eclipse.xtext.util.Strings.toFirstLower;
 
 import java.util.*;
@@ -24,7 +25,8 @@
 import org.eclipse.swt.custom.StyledText;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.xtext.*;
-import org.eclipse.xtext.naming.*;
+import org.eclipse.xtext.naming.QualifiedName;
+import org.eclipse.xtext.nodemodel.INode;
 import org.eclipse.xtext.resource.IEObjectDescription;
 import org.eclipse.xtext.scoping.IScope;
 import org.eclipse.xtext.ui.PluginImageHelper;
@@ -49,7 +51,6 @@
 public class ProtobufProposalProvider extends AbstractProtobufProposalProvider {
 
   @Inject private IEObjectDescriptionChooser descriptionChooser;
-  @Inject private ProtobufScopeProvider scopes;
   @Inject private ProtoDescriptorProvider descriptorProvider;
   @Inject private FieldOptions fieldOptions;
   @Inject private ModelFinder finder;
@@ -59,7 +60,7 @@
   @Inject private Literals literals;
   @Inject private Options options;
   @Inject private Properties properties;
-  
+
   @Override public void completeProtobuf_Syntax(EObject model, Assignment assignment, ContentAssistContext context,
       ICompletionProposalAcceptor acceptor) {}
 
@@ -73,7 +74,7 @@
     String proposal = SYNTAX + space() + EQUAL_PROTO2_IN_QUOTES;
     proposeAndAccept(proposal, imageHelper.getImage(images.imageFor(Syntax.class)), context, acceptor);
   }
-  
+
   @Override public void completeNativeOption_Source(EObject model, Assignment assignment,
       ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
     ProtoDescriptor descriptor = descriptorProvider.primaryDescriptor();
@@ -161,7 +162,7 @@
     Property p = propertyFrom(context);
     return p != null && properties.mayBeNan(p);
   }
-  
+
   private Property propertyFrom(ContentAssistContext context) {
     EObject model = context.getCurrentModel();
     if (model instanceof Property) return (Property) model;
@@ -372,7 +373,7 @@
     }
     return false;
   }
-  
+
   private void proposeBooleanValues(ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
     CommonKeyword[] keywords = { FALSE, TRUE };
     proposeAndAccept(keywords, context, acceptor);
@@ -403,15 +404,11 @@
     return word.equals(previousWord);
   }
 
-  @Override public void completeOptionSource_OptionField(EObject model, Assignment assignment,
-      ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
-  }
-  
   @Override public void completeCustomOption_Source(EObject model, Assignment assignment,
       ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
     if (!(model instanceof CustomOption)) return;
     CustomOption option = (CustomOption) model;
-    IScope scope = scopes.scope_OptionSource_optionField(option.getSource(), null);
+    IScope scope = getScopeProvider().scope_OptionSource_optionField(option.getSource(), null);
     proposeAndAcceptOptions(scope, context, acceptor);
   }
 
@@ -419,21 +416,67 @@
       ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
     if (!(model instanceof CustomFieldOption)) return;
     CustomFieldOption option = (CustomFieldOption) model;
-    IScope scope = scopes.scope_OptionSource_optionField(option.getSource(), null);
+    IScope scope = getScopeProvider().scope_OptionSource_optionField(option.getSource(), null);
     proposeAndAcceptOptions(scope, context, acceptor);
   }
-  
+
   private void proposeAndAcceptOptions(IScope scope, ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
     Image image = imageForOption();
     for (IEObjectDescription d : descriptionChooser.shortestQualifiedNamesIn(scope)) {
       proposeAndAccept(d, image, context, acceptor);
     }
   }
-  
+
   private Image imageForOption() {
     return imageHelper.getImage(images.imageFor(Option.class));
   }
 
+  @Override public void completeCustomOption_OptionFields(EObject model, Assignment assignment,
+      ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
+  }
+
+  @Override public void completeCustomFieldOption_OptionFields(EObject model, Assignment assignment,
+      ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
+  }
+
+  @Override public void completeOptionSource_OptionField(EObject model, Assignment assignment,
+      ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
+  }
+
+  @Override public void completeOptionMessageFieldSource_OptionMessageField(EObject model, Assignment assignment,
+      ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
+  }
+
+  @Override public void completeOptionExtendMessageFieldSource_OptionExtendMessageField(EObject model,
+      Assignment assignment, ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
+  }
+
+  @Override public void complete_OptionMessageFieldSource(EObject model, RuleCall ruleCall,
+      ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
+    INode node = context.getCurrentNode();
+    EObject e = findActualSemanticObjectFor(node);
+    if (!(e instanceof OptionMessageFieldSource)) return;
+    OptionMessageFieldSource s = (OptionMessageFieldSource) e;
+    IScope scope = getScopeProvider().scope_OptionMessageFieldSource_optionMessageField(s, null);
+    for (IEObjectDescription d : descriptionChooser.shortestQualifiedNamesIn(scope)) {
+      Image image = imageHelper.getImage(images.imageFor(d.getEObjectOrProxy()));
+      proposeAndAccept(d, image, context, acceptor);
+    }
+  }
+
+  @Override public void complete_OptionExtendMessageFieldSource(EObject model, RuleCall ruleCall,
+      ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
+    INode node = context.getCurrentNode();
+    EObject e = findActualSemanticObjectFor(node);
+    if (!(e instanceof OptionExtendMessageFieldSource)) return;
+    OptionExtendMessageFieldSource s = (OptionExtendMessageFieldSource) e;
+    IScope scope = getScopeProvider().scope_OptionExtendMessageFieldSource_optionExtendMessageField(s, null);
+    for (IEObjectDescription d : descriptionChooser.shortestQualifiedNamesIn(scope)) {
+      Image image = imageHelper.getImage(images.imageFor(d.getEObjectOrProxy()));
+      proposeAndAccept(d, image, context, acceptor);
+    }
+  }
+
   private void proposeAndAccept(IEObjectDescription d, Image image, ContentAssistContext context,
       ICompletionProposalAcceptor acceptor) {
     QualifiedName name = d.getName();
@@ -441,41 +484,6 @@
     ICompletionProposal proposal = createCompletionProposal(name.toString(), display, image, context);
     acceptor.accept(proposal);
   }
-  
-  @Override public void completeCustomOption_OptionFields(EObject model, Assignment assignment,
-      ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
-    super.completeCustomOption_OptionFields(model, assignment, context, acceptor);
-  }
-
-  @Override public void completeCustomFieldOption_OptionFields(EObject model, Assignment assignment,
-      ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
-    super.completeCustomFieldOption_OptionFields(model, assignment, context, acceptor);
-  }
-
-  
-//  @Override public void completeCustomOption_PropertyField(EObject model, Assignment assignment,
-//      ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
-//    if (!(model instanceof CustomOption)) return;
-//    Property property = options.propertyFrom((CustomOption) model);
-//    proposeAndAcceptOptionFields(property, context, acceptor);
-//  }
-//  
-//  @Override public void completeCustomFieldOption_PropertyField(EObject model, Assignment assignment,
-//      ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
-//    if (!(model instanceof CustomFieldOption)) return;
-//    Property property = fieldOptions.propertyFrom((CustomFieldOption) model);
-//    proposeAndAcceptOptionFields(property, context, acceptor);
-//  }
-  
-  private void proposeAndAcceptOptionFields(Property property, ContentAssistContext context,
-      ICompletionProposalAcceptor acceptor) {
-    Message message = finder.messageTypeOf(property);
-    if (message == null) return;
-    Image image = imageHelper.getImage("property.gif");
-    for (Property p : finder.propertiesOf(message)) {
-      proposeAndAccept(p.getName(), image, context, acceptor);
-    }
-  }
 
   @Override public void completeCustomOption_Value(EObject model, Assignment assignment, ContentAssistContext context,
       ICompletionProposalAcceptor acceptor) {
@@ -487,7 +495,7 @@
       proposeAndAcceptOptionFieldValue((Property) f, context, acceptor);
     }
   }
-  
+
   @Override public void completeCustomFieldOption_Value(EObject model, Assignment assignment,
       ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
     // TODO content assist returns "{"
@@ -509,10 +517,14 @@
       proposeAndAccept(enumType, context, acceptor);
     }
   }
-  
+
   private void proposeAndAccept(String proposalText, Image image, ContentAssistContext context,
       ICompletionProposalAcceptor acceptor) {
     ICompletionProposal proposal = createCompletionProposal(proposalText, proposalText, image, context);
     acceptor.accept(proposal);
   }
+
+  @Override public ProtobufScopeProvider getScopeProvider() {
+    return (ProtobufScopeProvider) super.getScopeProvider();
+  }
 }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/AstWalker.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/AstWalker.java
index b6a5b02..b2e9973 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/AstWalker.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/AstWalker.java
@@ -8,21 +8,21 @@
  */
 package com.google.eclipse.protobuf.scoping;
 
-import static java.util.Collections.emptyList;
+import static java.util.Collections.*;
 import static org.eclipse.emf.ecore.util.EcoreUtil.getAllContents;
 
-import com.google.eclipse.protobuf.model.util.*;
-import com.google.eclipse.protobuf.parser.NonProto2;
-import com.google.eclipse.protobuf.protobuf.*;
-import com.google.eclipse.protobuf.protobuf.Package;
-import com.google.inject.Inject;
+import java.util.*;
 
 import org.eclipse.emf.common.util.TreeIterator;
 import org.eclipse.emf.ecore.EObject;
 import org.eclipse.emf.ecore.resource.*;
 import org.eclipse.xtext.resource.IEObjectDescription;
 
-import java.util.*;
+import com.google.eclipse.protobuf.model.util.*;
+import com.google.eclipse.protobuf.parser.NonProto2;
+import com.google.eclipse.protobuf.protobuf.*;
+import com.google.eclipse.protobuf.protobuf.Package;
+import com.google.inject.Inject;
 
 /**
  * @author alruiz@google.com (Alex Ruiz)
@@ -45,7 +45,7 @@
     descriptions.addAll(imported(root, scopeFinder, criteria));
     return descriptions;
   }
-  
+
   Collection<IEObjectDescription> traverseAst(Protobuf start, ScopeFinder scopeFinder, Object criteria) {
     Set<IEObjectDescription> descriptions = new HashSet<IEObjectDescription>();
     descriptions.addAll(local(start, scopeFinder, criteria));
@@ -58,7 +58,7 @@
   }
 
   private Collection<IEObjectDescription> local(EObject start, ScopeFinder scopeFinder, Object criteria, int level) {
-    List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
+    Set<IEObjectDescription> descriptions = new HashSet<IEObjectDescription>();
     for (EObject element : start.eContents()) {
       descriptions.addAll(scopeFinder.descriptions(element, criteria, level));
       if (element instanceof Message) {
@@ -71,13 +71,15 @@
   private Collection<IEObjectDescription> imported(Protobuf start, ScopeFinder scopeFinder, Object criteria) {
     List<Import> allImports = modelFinder.importsIn(start);
     if (allImports.isEmpty()) return emptyList();
-    ResourceSet resourceSet = start.eResource().getResourceSet();
+    Resource resource = start.eResource();
+    if (resource == null) return emptySet();
+    ResourceSet resourceSet = resource.getResourceSet();
     return imported(allImports, modelFinder.packageOf(start), resourceSet, scopeFinder, criteria);
   }
 
   private Collection<IEObjectDescription> imported(List<Import> allImports, Package aPackage,
       ResourceSet resourceSet, ScopeFinder scopeFinder, Object criteria) {
-    List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
+    Set<IEObjectDescription> descriptions = new HashSet<IEObjectDescription>();
     for (Import anImport : allImports) {
       if (imports.isImportingDescriptor(anImport)) {
         descriptions.addAll(scopeFinder.fromProtoDescriptor(anImport, criteria));
@@ -111,7 +113,7 @@
   }
 
   private Collection<IEObjectDescription> local(Resource resource, ScopeFinder scopeFinder, Object criteria) {
-    List<IEObjectDescription> descriptions = new ArrayList<IEObjectDescription>();
+    Set<IEObjectDescription> descriptions = new HashSet<IEObjectDescription>();
     TreeIterator<Object> contents = getAllContents(resource, true);
     while (contents.hasNext()) {
       Object next = contents.next();