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}.