Fixed: [ Issue 86 ] Bugs with automatic field number generation when
pressing semicolon.
Added recognition of comments and strings.
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/SmartSemicolonHandler.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/SmartSemicolonHandler.java
index abc31c1..d6d6190 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/SmartSemicolonHandler.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/SmartSemicolonHandler.java
@@ -11,8 +11,14 @@
import static com.google.eclipse.protobuf.grammar.CommonKeyword.SEMICOLON;
import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.*;
+import com.google.eclipse.protobuf.protobuf.*;
+import com.google.eclipse.protobuf.ui.util.*;
+import com.google.eclipse.protobuf.util.*;
+import com.google.inject.Inject;
+
import org.eclipse.emf.ecore.EObject;
import org.eclipse.swt.custom.StyledText;
+import org.eclipse.xtext.*;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.ui.editor.XtextEditor;
@@ -21,11 +27,6 @@
import org.eclipse.xtext.util.concurrent.IUnitOfWork;
import org.eclipse.xtext.validation.IConcreteSyntaxValidator.InvalidConcreteSyntaxException;
-import com.google.eclipse.protobuf.protobuf.*;
-import com.google.eclipse.protobuf.ui.util.*;
-import com.google.eclipse.protobuf.util.ModelNodes;
-import com.google.inject.Inject;
-
/**
* Inserts a semicolon at the end of a line, regardless of the current position of the caret in the editor. If the
* line of code being edited is a property or enum literal and if it does not have an index yet, this handler will
@@ -44,64 +45,97 @@
/** {@inheritDoc} */
@Override protected void insertContent(XtextEditor editor, StyledText styledText) {
- int originalCaretOffset = styledText.getCaretOffset();
- int lineAtOffset = styledText.getLineAtOffset(originalCaretOffset);
+ int offset = styledText.getCaretOffset();
+ int lineAtOffset = styledText.getLineAtOffset(offset);
int offsetAtLine = styledText.getOffsetAtLine(lineAtOffset);
String line = styledText.getLine(lineAtOffset);
- if (line.endsWith(semicolon)) {
- behaveLikeRegularEditing(styledText, originalCaretOffset);
- return;
+ ContentToInsert newContent = newContent(editor, styledText, line);
+ if (newContent.equals(ContentToInsert.NONE)) return;
+ if (newContent.location.equals(Location.END)) {
+ offset = offsetAtLine + line.length();
+ styledText.setCaretOffset(offset);
}
- String content = contentToInsert(editor, originalCaretOffset);
- if (content == null) return;
- int endOfLineOffset = offsetAtLine + line.length();
- styledText.setCaretOffset(endOfLineOffset);
- insert(styledText, content, endOfLineOffset);
+ styledText.insert(newContent.value);
+ styledText.setCaretOffset(offset + newContent.value.length());
}
-
- private void behaveLikeRegularEditing(StyledText styledText, int caretOffset) {
- insert(styledText, semicolon, caretOffset);
- }
-
- private void insert(StyledText styledText, String content, int caretOffset) {
- styledText.insert(content);
- styledText.setCaretOffset(caretOffset + content.length());
- }
-
- private String contentToInsert(final XtextEditor editor, final int offset) {
+
+ private ContentToInsert newContent(final XtextEditor editor, final StyledText styledText, final String line) {
try {
- return editor.getDocument().modify(new IUnitOfWork<String, XtextResource>() {
- public String exec(XtextResource state) {
+ return editor.getDocument().modify(new IUnitOfWork<ContentToInsert, XtextResource>() {
+ public ContentToInsert exec(XtextResource state) {
+ int offset = styledText.getCaretOffset();
ContentAssistContext[] context = contextFactory.create(editor.getInternalSourceViewer(), offset, state);
- if (context == null || context.length == 0) return semicolon;
for (ContentAssistContext c : context) {
+ INode currentNode = c.getCurrentNode();
+ if (nodes.wasCreatedByAnyComment(currentNode) || wasCreatedByString(currentNode)) break;
EObject model = c.getCurrentModel();
- if (model instanceof Literal)
- return contentToInsert((Literal) model);
- if (model instanceof Property)
- return contentToInsert((Property) model);
+ if (model instanceof FieldOption) {
+ FieldOption option = (FieldOption) model;
+ model = option.eContainer();
+ }
+ if (line.endsWith(semicolon)) break;
+ if (model instanceof Literal) {
+ Literal literal = (Literal) model;
+ ContentToInsert content = newContent(literal);
+ if (content.equals(ContentToInsert.NONE)) {
+ int index = literals.calculateIndexOf(literal);
+ literal.setIndex(index);
+ }
+ return content;
+ }
+ if (model instanceof Property) {
+ Property property = (Property) model;
+ ContentToInsert content = newContent(property);
+ if (content.equals(ContentToInsert.NONE)) {
+ int index = fields.calculateTagNumberOf(property);
+ property.setIndex(index);
+ }
+ return content;
+ }
}
- return semicolon;
+ return new ContentToInsert(semicolon, Location.CURRENT);
}
});
- } catch (InvalidConcreteSyntaxException e) {
- return null;
+ } catch (InvalidConcreteSyntaxException e) {}
+ return ContentToInsert.NONE;
+ }
+
+ private boolean wasCreatedByString(INode node) {
+ EObject grammarElement = node.getGrammarElement();
+ if (!(grammarElement instanceof RuleCall)) return false;
+ AbstractRule rule = ((RuleCall) grammarElement).getRule();
+ if (!(rule instanceof TerminalRule)) return false;
+ TerminalRule terminalRule = (TerminalRule) rule;
+ return "STRING".equals(terminalRule.getName());
+ }
+
+ private ContentToInsert newContent(Literal literal) {
+ INode indexNode = nodes.firstNodeForFeature(literal, LITERAL__INDEX);
+ return newContent(indexNode);
+ }
+
+ private ContentToInsert newContent(Property property) {
+ INode indexNode = nodes.firstNodeForFeature(property, FIELD__INDEX);
+ return newContent(indexNode);
+ }
+
+ private ContentToInsert newContent(INode indexNode) {
+ return (indexNode != null) ? new ContentToInsert(semicolon, Location.END) : ContentToInsert.NONE;
+ }
+
+ private static class ContentToInsert {
+ final String value;
+ final Location location;
+
+ static final ContentToInsert NONE = new ContentToInsert("", Location.NONE);
+
+ ContentToInsert(String value, Location location) {
+ this.value = value;
+ this.location = location;
}
}
-
- private String contentToInsert(Literal literal) {
- INode indexNode = nodes.firstNodeForFeature(literal, LITERAL__INDEX);
- if (indexNode != null) return semicolon;
- int index = literals.calculateIndexOf(literal);
- literal.setIndex(index);
- return null;
- }
-
- private String contentToInsert(Property property) {
- INode indexNode = nodes.firstNodeForFeature(property, FIELD__INDEX);
- if (indexNode != null) return semicolon;
- int index = fields.calculateTagNumberOf(property);
- property.setIndex(index);
- return null;
+
+ private static enum Location {
+ NONE, CURRENT, END;
}
}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/documentation/ProtobufDocumentationProvider.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/documentation/ProtobufDocumentationProvider.java
index 97fbe20..15fecd8 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/documentation/ProtobufDocumentationProvider.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/documentation/ProtobufDocumentationProvider.java
@@ -33,7 +33,6 @@
delegates.add(p2);
}
-
/** {@inheritDoc} */
public String getDocumentation(EObject o) {
for (IEObjectDocumentationProvider p: delegates) {
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/documentation/SingleLineCommentDocumentationProvider.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/documentation/SingleLineCommentDocumentationProvider.java
index 58598c3..1f787f6 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/documentation/SingleLineCommentDocumentationProvider.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/documentation/SingleLineCommentDocumentationProvider.java
@@ -11,16 +11,15 @@
import static com.google.eclipse.protobuf.util.CommonWords.space;
import static org.eclipse.xtext.nodemodel.util.NodeModelUtils.getNode;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.xtext.TerminalRule;
-import org.eclipse.xtext.documentation.IEObjectDocumentationProvider;
-import org.eclipse.xtext.nodemodel.*;
-
import com.google.eclipse.protobuf.protobuf.*;
import com.google.eclipse.protobuf.scoping.*;
-import com.google.eclipse.protobuf.util.CommonWords;
+import com.google.eclipse.protobuf.util.ModelNodes;
import com.google.inject.Inject;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.xtext.documentation.IEObjectDocumentationProvider;
+import org.eclipse.xtext.nodemodel.*;
+
/**
* Provides single line comments of a protobuf element as its documentation when hovered.
*
@@ -33,8 +32,7 @@
private static final String UNIX_NEW_LINE = "\\n";
@Inject private ProtoDescriptorProvider descriptorProvider;
-
- private static final String RULE_NAME = "SL_COMMENT";
+ @Inject private ModelNodes nodes;
public String getDocumentation(EObject o) {
String comment = findComment(o);
@@ -48,7 +46,7 @@
StringBuilder commentBuilder = new StringBuilder();
for (INode currentNode : node.getAsTreeIterable()) {
if (currentNode instanceof ILeafNode && !((ILeafNode) currentNode).isHidden()) break;
- if (currentNode instanceof ILeafNode && isSingleLineCommentTerminalRule(currentNode.getGrammarElement())) {
+ if (currentNode instanceof ILeafNode && nodes.wasCreatedBySingleLineComment(currentNode)) {
String comment = ((ILeafNode) currentNode).getText();
commentBuilder.append(cleanUp(comment));
}
@@ -70,12 +68,6 @@
return o;
}
- private boolean isSingleLineCommentTerminalRule(EObject o) {
- if (!(o instanceof TerminalRule)) return false;
- TerminalRule rule = (TerminalRule) o;
- return RULE_NAME.equalsIgnoreCase(rule.getName());
- }
-
private String cleanUp(String comment) {
return comment.replaceFirst(COMMENT_START, "")
.replaceAll(WINDOWS_NEW_LINE, space())
diff --git a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/ModelNodes.java b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/ModelNodes.java
index a720813..bdd8f79 100644
--- a/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/ModelNodes.java
+++ b/com.google.eclipse.protobuf/src/com/google/eclipse/protobuf/util/ModelNodes.java
@@ -13,6 +13,7 @@
import com.google.inject.Singleton;
import org.eclipse.emf.ecore.*;
+import org.eclipse.xtext.TerminalRule;
import org.eclipse.xtext.nodemodel.INode;
import java.util.List;
@@ -25,9 +26,47 @@
@Singleton
public class ModelNodes {
- public INode firstNodeForFeature(EObject semanticObject, EStructuralFeature structuralFeature) {
- List<INode> nodes = findNodesForFeature(semanticObject, structuralFeature);
+ private static final String SINGLE_LINE_COMMENT_RULE_NAME = "SL_COMMENT";
+
+ /**
+ * Returns the first node that was used to assign values to the given feature for the given object.
+ * @param o the given object.
+ * @param feature the given feature.
+ * @return the first node that was used to assign values to the given feature for the given object, or {@code null} if
+ * a node cannot be found.
+ */
+ public INode firstNodeForFeature(EObject o, EStructuralFeature feature) {
+ List<INode> nodes = findNodesForFeature(o, feature);
if (nodes.isEmpty()) return null;
return nodes.get(0);
}
+
+ /**
+ * Indicates whether the given node was created by a single- or multi-line comment.
+ * @param node the node to check.
+ * @return {@code true} if the given node was created by a single- or multi-line comment; {@code false} otherwise.
+ */
+ public boolean wasCreatedByAnyComment(INode node) {
+ return wasCreatedByComment(node, SINGLE_LINE_COMMENT_RULE_NAME, "ML_COMMENT");
+ }
+
+ /**
+ * Indicates whether the given node was created by a single-line comment.
+ * @param node the node to check.
+ * @return {@code true} if the given node was created by a single-line comment; {@code false} otherwise.
+ */
+ public boolean wasCreatedBySingleLineComment(INode node) {
+ return wasCreatedByComment(node, SINGLE_LINE_COMMENT_RULE_NAME);
+ }
+
+ private boolean wasCreatedByComment(INode node, String...commentRuleNames) {
+ EObject o = node.getGrammarElement();
+ if (!(o instanceof TerminalRule)) return false;
+ TerminalRule rule = (TerminalRule) o;
+ String actualName = rule.getName();
+ for (String name : commentRuleNames) {
+ if (name.equalsIgnoreCase(actualName)) return true;
+ }
+ return false;
+ }
}