In progress: [Issue 104] Update "Next Id" comment when generating a the
tag number of a field or literal
Almost done. Need to finish preference page to add/edit/remove patterns.
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/commands/CommentNodesFinder_matchingCommentNode_Test.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/commands/CommentNodesFinder_matchingCommentNode_Test.java
index bbf9db4..2ac8385 100644
--- a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/commands/CommentNodesFinder_matchingCommentNode_Test.java
+++ b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/commands/CommentNodesFinder_matchingCommentNode_Test.java
@@ -10,22 +10,23 @@
import static com.google.eclipse.protobuf.junit.util.Finder.findProperty;
import static org.hamcrest.core.IsEqual.equalTo;
-import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsNull.*;
import static org.junit.Assert.assertThat;
+import java.util.regex.Matcher;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.xtext.nodemodel.INode;
+import org.eclipse.xtext.util.Pair;
+import org.junit.*;
+
import com.google.eclipse.protobuf.junit.core.XtextRule;
import com.google.eclipse.protobuf.junit.util.MultiLineTextBuilder;
import com.google.eclipse.protobuf.protobuf.*;
-import org.eclipse.emf.ecore.EObject;
-import org.eclipse.xtext.nodemodel.INode;
-import org.junit.*;
-
-import java.util.regex.Pattern;
-
/**
- * Tests for <code>{@link CommentNodesFinder#matchingCommentNode(EObject, Pattern...)}</code>.
- *
+ * Tests for <code>{@link CommentNodesFinder#matchingCommentNode(EObject, String...)}</code>.
+ *
* @author alruiz@google.com (Alex Ruiz)
*/
public class CommentNodesFinder_matchingCommentNode_Test {
@@ -33,34 +34,48 @@
@Rule public XtextRule xtext = new XtextRule();
private CommentNodesFinder finder;
-
+
@Before public void setUp() {
finder = xtext.getInstanceOf(CommentNodesFinder.class);
}
-
+
@Test public void should_return_matching_single_line_comment_of_element() {
MultiLineTextBuilder proto = new MultiLineTextBuilder();
- proto.append("message Person { ")
- .append(" // Indicates whether the person is active or not.")
- .append(" optional bool active = 1; ")
- .append("} ");
+ proto.append("message Person { ")
+ .append(" // Next Id: 6 ")
+ .append(" optional bool active = 1;")
+ .append("} ");
Protobuf root = xtext.parse(proto);
Property active = findProperty("active", root);
- INode node = finder.matchingCommentNode(active, Pattern.compile(".*"));
- assertThat(node.getText().trim(), equalTo("// Indicates whether the person is active or not."));
+ Pair<INode, Matcher> match = finder.matchingCommentNode(active, "next id: [\\d]+");
+ INode node = match.getFirst();
+ assertThat(node.getText().trim(), equalTo("// Next Id: 6"));
}
@Test public void should_return_matching_multi_line_comment_of_element() {
MultiLineTextBuilder proto = new MultiLineTextBuilder();
- proto.append("message Person { ")
- .append(" /* ")
- .append(" * Indicates whether the person is active or not.")
- .append(" */ ")
- .append(" optional bool active = 1; ")
- .append("} ");
+ proto.append("message Person { ")
+ .append(" /* ")
+ .append(" * Next Id: 6 ")
+ .append(" */ ")
+ .append(" optional bool active = 1;")
+ .append("} ");
Protobuf root = xtext.parse(proto);
Property active = findProperty("active", root);
- INode node = finder.matchingCommentNode(active, Pattern.compile(".*"));
+ Pair<INode, Matcher> match = finder.matchingCommentNode(active, "NEXT ID: [\\d]+");
+ INode node = match.getFirst();
assertThat(node, notNullValue());
}
+
+ @Test public void should_return_null_if_no_matching_node_found() {
+ MultiLineTextBuilder proto = new MultiLineTextBuilder();
+ proto.append("message Person { ")
+ .append(" // Next Id: 6 ")
+ .append(" optional bool active = 1;")
+ .append("} ");
+ Protobuf root = xtext.parse(proto);
+ Property active = findProperty("active", root);
+ Pair<INode, Matcher> match = finder.matchingCommentNode(active, "Hello");
+ assertThat(match, nullValue());
+ }
}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/CommentNodesFinder.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/CommentNodesFinder.java
index 475c344..4773067 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/CommentNodesFinder.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/CommentNodesFinder.java
@@ -9,16 +9,20 @@
package com.google.eclipse.protobuf.ui.commands;
import static com.google.eclipse.protobuf.junit.util.SystemProperties.lineSeparator;
+import static java.util.regex.Pattern.CASE_INSENSITIVE;
import static org.eclipse.xtext.nodemodel.util.NodeModelUtils.getNode;
import static org.eclipse.xtext.util.Strings.isEmpty;
+import static org.eclipse.xtext.util.Tuples.pair;
-import com.google.eclipse.protobuf.util.ModelNodes;
-import com.google.inject.*;
+import java.util.*;
+import java.util.regex.*;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.nodemodel.*;
+import org.eclipse.xtext.util.*;
-import java.util.regex.*;
+import com.google.eclipse.protobuf.util.ModelNodes;
+import com.google.inject.*;
/**
* @author alruiz@google.com (Alex Ruiz)
@@ -26,9 +30,12 @@
@Singleton
class CommentNodesFinder {
- @Inject private ModelNodes nodes;
+ private static final String MATCH_ANYTHING = ".*";
- INode matchingCommentNode(EObject target, Pattern...patternsToMatch) {
+ @Inject private ModelNodes nodes;
+ @Inject private final IResourceScopeCache cache = IResourceScopeCache.NullImpl.INSTANCE;
+
+ Pair<INode, Matcher> matchingCommentNode(EObject target, String...patternsToMatch) {
ICompositeNode node = getNode(target);
for (INode currentNode : node.getAsTreeIterable()) {
if (currentNode instanceof ILeafNode && !((ILeafNode) currentNode).isHidden()) break;
@@ -37,13 +44,26 @@
if (isEmpty(rawComment)) continue;
String[] comment = rawComment.split(lineSeparator());
for (String line : comment) {
- for (Pattern pattern : patternsToMatch) {
+ for (Pattern pattern : compile(patternsToMatch, target)) {
Matcher matcher = pattern.matcher(line);
- if (matcher.matches()) return currentNode;
+ if (matcher.matches()) return pair(currentNode, matcher);
}
}
}
}
return null;
}
+
+ private List<Pattern> compile(String[] patterns, EObject target) {
+ List<Pattern> compiled = new ArrayList<Pattern>();
+ for (final String s : patterns) {
+ Pattern p = cache.get(s, target.eResource(), new Provider<Pattern>() {
+ public Pattern get() {
+ return Pattern.compile(MATCH_ANYTHING + s + MATCH_ANYTHING, CASE_INSENSITIVE);
+ }
+ });
+ compiled.add(p);
+ }
+ return compiled;
+ }
}
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 957a9c5..f57ed44 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
@@ -8,15 +8,12 @@
*/
package com.google.eclipse.protobuf.ui.commands;
-import static com.google.eclipse.protobuf.grammar.CommonKeyword.SEMICOLON;
-import static com.google.eclipse.protobuf.junit.util.SystemProperties.lineSeparator;
import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.*;
+import static org.eclipse.xtext.util.Strings.isEmpty;
-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 java.util.regex.Matcher;
+import org.apache.log4j.Logger;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.swt.custom.StyledText;
@@ -27,10 +24,14 @@
import org.eclipse.xtext.ui.editor.contentassist.ContentAssistContext;
import org.eclipse.xtext.ui.editor.contentassist.antlr.ParserBasedContentAssistContextFactory;
import org.eclipse.xtext.ui.editor.model.IXtextDocument;
+import org.eclipse.xtext.util.Pair;
import org.eclipse.xtext.util.concurrent.IUnitOfWork;
-import org.eclipse.xtext.validation.IConcreteSyntaxValidator.InvalidConcreteSyntaxException;
-import java.util.regex.Pattern;
+import com.google.eclipse.protobuf.grammar.CommonKeyword;
+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
@@ -41,13 +42,17 @@
*/
public class SmartSemicolonHandler extends SmartInsertHandler {
+ private static Logger logger = Logger.getLogger(SmartSemicolonHandler.class);
+
@Inject private CommentNodesFinder commentNodesFinder;
@Inject private Fields fields;
@Inject private Literals literals;
@Inject private ModelNodes nodes;
@Inject private ParserBasedContentAssistContextFactory contextFactory;
- private final String semicolon = SEMICOLON.toString();
+ private static final String SEMICOLON = CommonKeyword.SEMICOLON.toString();
+
+ private static final ContentToInsert INSERT_SEMICOLON_AT_CURRENT_LOCATION = new ContentToInsert(SEMICOLON, Location.CURRENT);
/** {@inheritDoc} */
@Override protected void insertContent(XtextEditor editor, StyledText styledText) {
@@ -64,22 +69,23 @@
styledText.insert(newContent.value);
styledText.setCaretOffset(offset + newContent.value.length());
}
-
+
private ContentToInsert newContent(final XtextEditor editor, final StyledText styledText, final String line) {
+ if (line.endsWith(SEMICOLON)) return INSERT_SEMICOLON_AT_CURRENT_LOCATION;
+ final IXtextDocument document = editor.getDocument();
+ ContentToInsert contentToInsert = ContentToInsert.NONE;
try {
- final IXtextDocument document = editor.getDocument();
- return document.modify(new IUnitOfWork<ContentToInsert, XtextResource>() {
+ contentToInsert = document.modify(new IUnitOfWork<ContentToInsert, XtextResource>() {
public ContentToInsert exec(XtextResource state) {
int offset = styledText.getCaretOffset();
ContentAssistContext[] context = contextFactory.create(editor.getInternalSourceViewer(), offset, state);
for (ContentAssistContext c : context) {
- if (isCommentOrString(c.getCurrentNode())) break;
+ if (isCommentOrString(c.getCurrentNode())) continue;
EObject model = c.getCurrentModel();
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);
@@ -101,13 +107,16 @@
return content;
}
}
- return new ContentToInsert(semicolon, Location.CURRENT);
+ return INSERT_SEMICOLON_AT_CURRENT_LOCATION;
}
});
- } catch (InvalidConcreteSyntaxException e) {}
- return ContentToInsert.NONE;
+ } catch (Throwable e) {
+ logger.error("Unable to generate tag number", e);
+ return INSERT_SEMICOLON_AT_CURRENT_LOCATION;
+ }
+ return contentToInsert;
}
-
+
private boolean isCommentOrString(INode currentNode) {
return nodes.wasCreatedByAnyComment(currentNode) || wasCreatedByString(currentNode);
}
@@ -125,43 +134,45 @@
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;
+ boolean hasIndex = indexNode != null && !isEmpty(indexNode.getText());
+ return hasIndex ? new ContentToInsert(SEMICOLON, Location.END) : ContentToInsert.NONE;
}
-
+
private void updateIndexInCommentOfParent(EObject o, int index, IXtextDocument document) {
-// EObject parent = o.eContainer();
-// if (parent == null) return;
-// INode node = commentNodesFinder.matchingCommentNode(parent, Pattern.compile("// Next Id: [0-9]"));
-// if (node == null) {
-// System.out.println("No matching node");
-// return;
-// }
-// try {
-// document.replace(node.getOffset(), node.getText().length(), "// Next Id: " + (index + 1) + lineSeparator());
-// } catch (BadLocationException e) {
-// e.printStackTrace();
-// }
+ EObject parent = o.eContainer();
+ if (parent == null) return;
+ String pattern = "Next[\\s]+Id:[\\s]+([\\d])+";
+ Pair<INode, Matcher> match = commentNodesFinder.matchingCommentNode(parent, pattern);
+ if (match == null) return;
+ String originalText = match.getSecond().group();
+ String replacement = originalText.replaceAll("[\\d]+", String.valueOf(index + 1));
+ INode node = match.getFirst();
+ try {
+ document.replace(node.getOffset() + node.getText().indexOf(originalText), originalText.length(), replacement);
+ } catch (BadLocationException e) {
+ logger.error("Unable to update comment tracking next tag number", e);
+ }
}
-
+
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 static enum Location {
NONE, CURRENT, END;
}