Fixed: [ Issue 53 ] Add content-assist for field options https://code.google.com/p/protobuf-dt/issues/detail?id=53
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 0111d0c..f0ca40c 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
@@ -13,6 +13,10 @@ import static com.google.eclipse.protobuf.ui.grammar.CommonKeyword.*; import static com.google.eclipse.protobuf.ui.grammar.CompoundElement.*; import static java.lang.String.valueOf; +import static java.util.Collections.emptyList; + +import java.util.ArrayList; +import java.util.List; import org.eclipse.emf.ecore.EObject; import org.eclipse.jface.text.contentassist.ICompletionProposal; @@ -25,8 +29,9 @@ import com.google.common.collect.ImmutableList; import com.google.eclipse.protobuf.protobuf.*; import com.google.eclipse.protobuf.protobuf.Enum; -import com.google.eclipse.protobuf.scoping.*; -import com.google.eclipse.protobuf.ui.grammar.*; +import com.google.eclipse.protobuf.scoping.Descriptor; +import com.google.eclipse.protobuf.scoping.DescriptorProvider; +import com.google.eclipse.protobuf.ui.grammar.CommonKeyword; import com.google.eclipse.protobuf.ui.grammar.CompoundElement; import com.google.eclipse.protobuf.ui.labeling.Images; import com.google.eclipse.protobuf.ui.util.*; @@ -112,21 +117,6 @@ } } - private void proposeBooleanValues(ContentAssistContext context, ICompletionProposalAcceptor acceptor) { - CommonKeyword[] keywords = { FALSE, TRUE }; - proposeAndAccept(keywords, context, acceptor); - } - - private void proposeAndAccept(CommonKeyword[] keywords, ContentAssistContext context, - ICompletionProposalAcceptor acceptor) { - for (CommonKeyword keyword : keywords) proposeAndAccept(keyword, context, acceptor); - } - - private void proposeAndAccept(CommonKeyword keyword, ContentAssistContext context, - ICompletionProposalAcceptor acceptor) { - proposeAndAccept(keyword.toString(), context, acceptor); - } - @Override public void complete_ID(EObject model, RuleCall ruleCall, ContentAssistContext context, ICompletionProposalAcceptor acceptor) {} @@ -138,7 +128,7 @@ proposeEmptyString(context, acceptor); return; } - if (model instanceof Option || model instanceof Syntax) 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; @@ -192,7 +182,8 @@ if (TRUE.hasValue(keyword) || FALSE.hasValue(keyword)) { if (!isBoolProposalValid(context)) return true; } - return false; + // 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()); } private void proposeEqualProto2(ContentAssistContext context, ICompletionProposalAcceptor acceptor) { @@ -203,15 +194,6 @@ return object instanceof Keyword && keyword.hasValue(((Keyword)object).getValue()); } - private boolean isLastWordFromCaretPositionEqualTo(String word, ContentAssistContext context) { - StyledText styledText = context.getViewer().getTextWidget(); - int valueLength = word.length(); - int start = styledText.getCaretOffset() - valueLength; - if (start < 0) return false; - String previousWord = styledText.getTextRange(start, valueLength); - return word.equals(previousWord); - } - private boolean isBoolProposalValid(ContentAssistContext context) { EObject model = context.getCurrentModel(); if (model instanceof Property) return properties.isBool((Property) model); @@ -301,19 +283,6 @@ proposeAndAccept(enumType, context, acceptor); } - private void proposeAndAccept(Enum enumType, ContentAssistContext context, ICompletionProposalAcceptor acceptor) { - Image image = imageHelper.getImage(imageRegistry.imageFor(Literal.class)); - for (Literal literal : enumType.getLiterals()) - proposeAndAccept(literal.getName(), image, context, acceptor); - } - - private void proposeAndAccept(String proposalText, Image image, ContentAssistContext context, - ICompletionProposalAcceptor acceptor) { - String s = addSpaceAtBeginning(proposalText, context); - ICompletionProposal proposal = createCompletionProposal(s, proposalText, image, context); - acceptor.accept(proposal); - } - @Override public void completeProperty_Index(EObject model, Assignment assignment, ContentAssistContext context, ICompletionProposalAcceptor acceptor) { int index = properties.calculateTagNumberOf((Property) model); @@ -344,22 +313,96 @@ } private void proposeAndAccept(String proposalText, ContentAssistContext context, ICompletionProposalAcceptor acceptor) { - String s = addSpaceAtBeginning(proposalText, context); - acceptor.accept(createCompletionProposal(s, context)); - } - - private String addSpaceAtBeginning(String proposal, ContentAssistContext context) { - if (!isLastWordFromCaretPositionEqualTo(SPACE, context)) return SPACE + proposal; - return proposal; + acceptor.accept(createCompletionProposal(proposalText, context)); } @Override protected ICompletionProposal createCompletionProposal(String proposalText, ContentAssistContext context) { - String s = addSpaceAtBeginning(proposalText, context); - return createCompletionProposal(s, null, defaultImage(), getPriorityHelper().getDefaultPriority(), + return createCompletionProposal(proposalText, null, defaultImage(), getPriorityHelper().getDefaultPriority(), context.getPrefix(), context); } private Image defaultImage() { return imageHelper.getImage(imageRegistry.defaultImage()); } + + @Override public void complete_FieldOption(EObject model, RuleCall ruleCall, ContentAssistContext context, + ICompletionProposalAcceptor acceptor) { + System.out.println("complete_FieldOption"); + } + + @Override public void completeFieldOption_Name(EObject model, Assignment assignment, ContentAssistContext context, + ICompletionProposalAcceptor acceptor) { + Property property = extractPropertyFrom(context); + proposeCommonFieldOptions(property, context, acceptor); + } + + private void proposeCommonFieldOptions(Property property, ContentAssistContext context, + ICompletionProposalAcceptor acceptor) { + boolean isPrimitive = properties.isPrimitive(property); + List<String> options = existingFieldOptionNames(property); + for (Property fieldOption : descriptorProvider.get().fieldOptions()) { + String optionName = fieldOption.getName(); + if (options.contains(optionName) || ("packed".equals(optionName) && !isPrimitive)) continue; + String proposalText = optionName + SPACE + EQUAL + SPACE; + boolean isBooleanOption = properties.isBool(fieldOption); + if (isBooleanOption) proposalText = proposalText + TRUE; + ICompletionProposal proposal = createCompletionProposal(proposalText, context); + acceptor.accept(proposal); + } + } + + private List<String> existingFieldOptionNames(Property property) { + List<FieldOption> options = property.getFieldOptions(); + if (options.isEmpty()) return emptyList(); + List<String> optionNames = new ArrayList<String>(); + for (FieldOption option : options) optionNames.add(option.getName()); + return optionNames; + } + + @Override public void completeFieldOption_Value(EObject model, Assignment assignment, ContentAssistContext context, + ICompletionProposalAcceptor acceptor) { + FieldOption option = (FieldOption) model; + Descriptor descriptor = descriptorProvider.get(); + Property fieldOption = descriptor.lookupFieldOption(option.getName()); + if (fieldOption == null) return; + if (properties.isBool(fieldOption)) { + proposeBooleanValues(context, acceptor); + return; + } + if (descriptor.isCTypeOption(option)) { + proposeAndAccept(descriptor.cType(), context, acceptor); + return; + } + } + + private void proposeBooleanValues(ContentAssistContext context, ICompletionProposalAcceptor acceptor) { + 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); + } + + private void proposeAndAccept(Enum enumType, ContentAssistContext context, ICompletionProposalAcceptor acceptor) { + Image image = imageHelper.getImage(imageRegistry.imageFor(Literal.class)); + for (Literal literal : enumType.getLiterals()) + proposeAndAccept(literal.getName(), image, context, acceptor); + } + + private void proposeAndAccept(String proposalText, Image image, ContentAssistContext context, + ICompletionProposalAcceptor acceptor) { + ICompletionProposal proposal = createCompletionProposal(proposalText, proposalText, image, context); + acceptor.accept(proposal); + } + + private boolean isLastWordFromCaretPositionEqualTo(String word, ContentAssistContext context) { + StyledText styledText = context.getViewer().getTextWidget(); + int valueLength = word.length(); + int start = styledText.getCaretOffset() - valueLength; + if (start < 0) return false; + String previousWord = styledText.getTextRange(start, valueLength); + return word.equals(previousWord); + } }
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/outline/ProtobufOutlineTreeProvider.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/outline/ProtobufOutlineTreeProvider.java index 2c63379..a7c6603 100644 --- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/outline/ProtobufOutlineTreeProvider.java +++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/outline/ProtobufOutlineTreeProvider.java
@@ -33,6 +33,7 @@ static { IGNORED_ELEMENT_TYPES.add(BooleanRef.class); + IGNORED_ELEMENT_TYPES.add(FieldOption.class); IGNORED_ELEMENT_TYPES.add(MessageReference.class); }
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/Descriptor.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/Descriptor.java index e48ed6a..a113563 100644 --- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/Descriptor.java +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/Descriptor.java
@@ -8,6 +8,7 @@ */ package com.google.eclipse.protobuf.scoping; +import static com.google.eclipse.protobuf.scoping.OptionType.*; import static java.util.Collections.unmodifiableCollection; import static org.eclipse.emf.common.util.URI.createURI; import static org.eclipse.xtext.EcoreUtil2.*; @@ -16,7 +17,8 @@ import java.io.*; import java.util.*; -import org.eclipse.xtext.parser.*; +import org.eclipse.xtext.parser.IParseResult; +import org.eclipse.xtext.parser.IParser; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.util.StringInputStream; @@ -33,14 +35,17 @@ private Protobuf root; - private final Map<String, Property> fileOptions = new LinkedHashMap<String, Property>(); + private final Map<OptionType, Map<String, Property>> options = new HashMap<OptionType, Map<String, Property>>(); + private Enum optimizedMode; + private Enum cType; /** * Creates a new </code>{@link Descriptor}</code>. * @param parser the grammar parser. */ @Inject public Descriptor(IParser parser) { + addOptionTypes(); try { XtextResource resource = new XtextResource(createURI("descriptor.proto")); IParseResult result = parser.parse(new InputStreamReader(globalScopeContents(), "UTF-8")); @@ -53,6 +58,11 @@ } } + private void addOptionTypes() { + for (OptionType type : OptionType.values()) + options.put(type, new LinkedHashMap<String, Property>()); + } + private static InputStream globalScopeContents() { return new StringInputStream(descriptorContents()); } @@ -90,35 +100,64 @@ } private void initContents() { - Message m = fileOptionsMessage(); - for (MessageElement e : m.getElements()) { + for (Message m : getAllContentsOfType(root, Message.class)) { + if (isFileOptionsMessage(m)) initFileOptions(m); + else if (isFieldOptionsMessage(m)) initFieldOptions(m); + } + } + + private boolean isFileOptionsMessage(Message m) { + return "FileOptions".equals(m.getName()); + } + + private boolean isFieldOptionsMessage(Message m) { + return "FieldOptions".equals(m.getName()); + } + + private void initFileOptions(Message fileOptionsMessage) { + for (MessageElement e : fileOptionsMessage.getElements()) { if (e instanceof Property) { addFileOption((Property) e); continue; } - if (isOptimizeModeEnum(e)) { + if (isEnumWithName(e, "OptimizeMode")) { optimizedMode = (Enum) e; continue; } } } + + private void addFileOption(Property p) { + addOption(FILE, p); + } - private boolean isOptimizeModeEnum(MessageElement e) { + private void initFieldOptions(Message fieldOptionsMessage) { + for (MessageElement e : fieldOptionsMessage.getElements()) { + if (e instanceof Property) { + addFieldOption((Property) e); + continue; + } + if (isEnumWithName(e, "CType")) { + cType = (Enum) e; + continue; + } + } + } + + private void addFieldOption(Property p) { + addOption(FIELD, p); + } + + private void addOption(OptionType type, Property p) { + options.get(type).put(p.getName(), p); + } + + private boolean isEnumWithName(MessageElement e, String name) { if (!(e instanceof Enum)) return false; Enum anEnum = (Enum) e; - return "OptimizeMode".equals(anEnum.getName()); + return name.equals(anEnum.getName()); } - - private Message fileOptionsMessage() { - for (Message m : getAllContentsOfType(root, Message.class)) - if ("FileOptions".equals(m.getName())) return m; - throw new IllegalStateException("Unable to find message 'FileOptions'"); - } - - private void addFileOption(Property p) { - fileOptions.put(p.getName(), p); - } - + /** * Returns all the file-level options available. These are the options defined in * {@code google/protobuf/descriptor.proto} (more details can be found @@ -126,7 +165,7 @@ * @return all the file-level options available. */ public Collection<Property> fileOptions() { - return unmodifiableCollection(fileOptions.values()); + return optionsOfType(FILE); } /** @@ -139,11 +178,11 @@ } /** - * Indicates whether the given option is the "OptimizeMode" one (defined in {@code google/protobuf/descriptor.proto}. + * Indicates whether the given option is the "optimize_for" one (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>.) * @param option the given option. - * @return {@code true} if the given option is the "OptimizeMode" one, {@code false} otherwise. + * @return {@code true} if the given option is the "optimize_for" one, {@code false} otherwise. */ public boolean isOptimizeForOption(Option option) { if (option == null) return false; @@ -158,6 +197,56 @@ * @return the option whose name matches the given one or {@code null} if a matching option is not found. */ public Property lookupFileOption(String name) { - return fileOptions.get(name); + return lookupOption(FILE, name); + } + + /** + * Returns all the field-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 field-level options available. + */ + public Collection<Property> fieldOptions() { + return optionsOfType(FIELD); + } + + private Collection<Property> optionsOfType(OptionType type) { + return unmodifiableCollection(options.get(type).values()); + } + + /** + * Looks up a field-level option per name. Field-level options are 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>.) + * @param name the name of the option to look for. + * @return the option whose name matches the given one or {@code null} if a matching option is not found. + */ + public Property lookupFieldOption(String name) { + return lookupOption(FIELD, name); + } + + private Property lookupOption(OptionType type, String name) { + return options.get(type).get(name); + } + + /** + * Indicates whether the given option is the "ctype" one (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>.) + * @param option the given option. + * @return {@code true} if the given option is the "ctype" one, {@code false} otherwise. + */ + public boolean isCTypeOption(FieldOption option) { + if (option == null) return false; + return "ctype".equals(option.getName()); + } + + /** + * Returns the {@code enum} "CType" (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 the {@code enum} "CType." + */ + public Enum cType() { + return cType; } }
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 new file mode 100644 index 0000000..ab8ab3a --- /dev/null +++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/scoping/OptionType.java
@@ -0,0 +1,13 @@ +/* + * 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 + * + * http://www.eclipse.org/legal/epl-v10.html + */ +package com.google.eclipse.protobuf.scoping; + +enum OptionType { + FILE, FIELD; +} \ No newline at end of file
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 21bf3b6..f4a09d7 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
@@ -14,10 +14,14 @@ import java.util.*; -import org.eclipse.emf.common.util.*; -import org.eclipse.emf.ecore.*; -import org.eclipse.emf.ecore.resource.*; -import org.eclipse.xtext.naming.*; +import org.eclipse.emf.common.util.TreeIterator; +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.xtext.naming.IQualifiedNameProvider; +import org.eclipse.xtext.naming.QualifiedName; import org.eclipse.xtext.resource.IEObjectDescription; import org.eclipse.xtext.scoping.IScope; import org.eclipse.xtext.scoping.impl.*; @@ -127,6 +131,10 @@ Enum optimizedMode = descriptor.optimizedMode(); return scopeForLiterals(optimizedMode); } + if (container instanceof FieldOption && descriptor.isCTypeOption((FieldOption) container)) { + Enum cType = descriptor.cType(); + return scopeForLiterals(cType); + } return null; }