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