Fixed: [Issue 174] Content assistance too eager to provide field id.
Fixed: [Issue 177] Typing semicolon too fast does not generate tag
number.
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/util/Literals_calculateIndexOf_Test.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/util/Literals_calculateNewIndexOf_Test.java
similarity index 86%
rename from com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/util/Literals_calculateIndexOf_Test.java
rename to com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/util/Literals_calculateNewIndexOf_Test.java
index 8fcf9ad..2ca6260 100644
--- a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/util/Literals_calculateIndexOf_Test.java
+++ b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/util/Literals_calculateNewIndexOf_Test.java
@@ -19,11 +19,11 @@
import com.google.eclipse.protobuf.protobuf.Literal;
/**
- * Tests for <code>{@link Literals#calculateIndexOf(Literal)}</code>.
+ * Tests for <code>{@link Literals#calculateNewIndexOf(Literal)}</code>.
*
* @author alruiz@google.com (Alex Ruiz)
*/
-public class Literals_calculateIndexOf_Test {
+public class Literals_calculateNewIndexOf_Test {
@Rule public XtextRule xtext = overrideRuntimeModuleWith(unitTestModule());
private Literals literals;
@@ -39,7 +39,7 @@
// }
@Test public void should_return_zero_for_first_and_only_literal() {
Literal mobile = xtext.find("MOBILE", Literal.class);
- long index = literals.calculateIndexOf(mobile);
+ long index = literals.calculateNewIndexOf(mobile);
assertThat(index, equalTo(0L));
}
@@ -52,7 +52,7 @@
// }
@Test public void should_return_max_index_value_plus_one_for_new_literal() {
Literal work = xtext.find("WORK", Literal.class);
- long index = literals.calculateIndexOf(work);
+ long index = literals.calculateNewIndexOf(work);
assertThat(index, equalTo(6L));
}
}
diff --git a/com.google.eclipse.protobuf.ui/plugin.xml b/com.google.eclipse.protobuf.ui/plugin.xml
index 8498bdc..54a8999 100644
--- a/com.google.eclipse.protobuf.ui/plugin.xml
+++ b/com.google.eclipse.protobuf.ui/plugin.xml
@@ -30,7 +30,7 @@
</activeWhen>
</handler>
<handler
- class="com.google.eclipse.protobuf.ui.ProtobufExecutableExtensionFactory:com.google.eclipse.protobuf.ui.commands.SmartSemicolonHandler"
+ class="com.google.eclipse.protobuf.ui.ProtobufExecutableExtensionFactory:com.google.eclipse.protobuf.ui.commands.semicolon.SmartSemicolonHandler"
commandId="com.google.eclipse.protobuf.ui.smartSemicolon">
<activeWhen>
<reference definitionId="com.google.eclipse.protobuf.Protobuf.Editor.opened">
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/semicolon/CommentNodesFinder.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/semicolon/CommentNodesFinder.java
new file mode 100644
index 0000000..059a2c6
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/semicolon/CommentNodesFinder.java
@@ -0,0 +1,70 @@
+/*
+ * 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.ui.commands.semicolon;
+
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.eclipse.protobuf.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 java.util.List;
+import java.util.regex.*;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.xtext.nodemodel.*;
+import org.eclipse.xtext.util.*;
+
+import com.google.eclipse.protobuf.model.util.INodes;
+import com.google.inject.*;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+@Singleton class CommentNodesFinder {
+ private static final String MATCH_ANYTHING = ".*";
+
+ @Inject private INodes 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 (!nodes.isHiddenLeafNode(currentNode) || !nodes.isComment(currentNode)) {
+ continue;
+ }
+ String rawComment = ((ILeafNode) currentNode).getText();
+ if (isEmpty(rawComment)) {
+ continue;
+ }
+ String[] comment = rawComment.split(lineSeparator());
+ for (String line : comment) {
+ for (Pattern pattern : compile(patternsToMatch, target)) {
+ Matcher matcher = pattern.matcher(line);
+ if (matcher.matches()) { return pair(currentNode, matcher); }
+ }
+ }
+ }
+ return null;
+ }
+
+ private List<Pattern> compile(String[] patterns, EObject target) {
+ List<Pattern> compiled = newArrayList();
+ for (final String s : patterns) {
+ Pattern p = cache.get(s, target.eResource(), new Provider<Pattern>() {
+ @Override 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/semicolon/SmartSemicolonHandler.java
similarity index 60%
rename from com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/SmartSemicolonHandler.java
rename to com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/semicolon/SmartSemicolonHandler.java
index 455003e..20a69a4 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/semicolon/SmartSemicolonHandler.java
@@ -6,16 +6,17 @@
*
* http://www.eclipse.org/legal/epl-v10.html
*/
-package com.google.eclipse.protobuf.ui.commands;
+package com.google.eclipse.protobuf.ui.commands.semicolon;
import static com.google.eclipse.protobuf.protobuf.ProtobufPackage.Literals.*;
import static java.util.regex.Pattern.compile;
import static org.eclipse.xtext.util.Strings.isEmpty;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.*;
import org.apache.log4j.Logger;
-import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.*;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.xtext.nodemodel.INode;
@@ -31,6 +32,7 @@
import com.google.eclipse.protobuf.grammar.CommonKeyword;
import com.google.eclipse.protobuf.model.util.*;
import com.google.eclipse.protobuf.protobuf.*;
+import com.google.eclipse.protobuf.ui.commands.SmartInsertHandler;
import com.google.eclipse.protobuf.ui.preferences.editor.numerictag.core.NumericTagPreferences;
import com.google.eclipse.protobuf.ui.util.Literals;
import com.google.inject.Inject;
@@ -45,6 +47,10 @@
public class SmartSemicolonHandler extends SmartInsertHandler {
private static final Pattern NUMBERS_PATTERN = compile("[\\d]+");
+ private static final IUnitOfWork.Void<XtextResource> NULL_UNIT_OF_WORK = new IUnitOfWork.Void<XtextResource>() {
+ @Override public void process(XtextResource resource) {}
+ };
+
private static Logger logger = Logger.getLogger(SmartSemicolonHandler.class);
@Inject private CommentNodesFinder commentNodesFinder;
@@ -56,36 +62,29 @@
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) {
- int offset = styledText.getCaretOffset();
- int lineAtOffset = styledText.getLineAtOffset(offset);
- int offsetAtLine = styledText.getOffsetAtLine(lineAtOffset);
- String line = styledText.getLine(lineAtOffset);
- ContentToInsert newContent = newContent(editor, styledText, line);
- if (newContent.equals(ContentToInsert.TAG_NUMBER_INSERTED)) {
- refreshHighlighting(editor);
+ StyledTextAccess styledTextAccess = new StyledTextAccess(styledText);
+ String line = styledTextAccess.lineAtCaretOffset();
+ if (line.endsWith(SEMICOLON)) {
+ styledTextAccess.insert(SEMICOLON);
return;
}
- if (newContent.location.equals(Location.END)) {
- offset = offsetAtLine + line.length();
- styledText.setCaretOffset(offset);
- }
- styledText.insert(newContent.value);
- styledText.setCaretOffset(offset + newContent.value.length());
+ waitForReconcilerToFinishWork(editor);
+ insertContent(editor, styledTextAccess);
+ refreshHighlighting(editor);
}
- private ContentToInsert newContent(final XtextEditor editor, final StyledText styledText, final String line) {
- if (line.endsWith(SEMICOLON)) {
- return INSERT_SEMICOLON_AT_CURRENT_LOCATION;
- }
+ private void waitForReconcilerToFinishWork(XtextEditor editor) {
+ editor.getDocument().readOnly(NULL_UNIT_OF_WORK);
+ }
+
+ private void insertContent(final XtextEditor editor, final StyledTextAccess styledTextAccess) {
+ final AtomicBoolean shouldInsertSemicolon = new AtomicBoolean(true);
final IXtextDocument document = editor.getDocument();
try {
- return document.modify(new IUnitOfWork<ContentToInsert, XtextResource>() {
- @Override public ContentToInsert exec(XtextResource resource) {
- int offset = styledText.getCaretOffset();
+ document.modify(new IUnitOfWork.Void<XtextResource>() {
+ @Override public void process(XtextResource resource) {
+ int offset = styledTextAccess.caretOffset();
ContentAssistContext[] context = contextFactory.create(editor.getInternalSourceViewer(), offset, resource);
for (ContentAssistContext c : context) {
if (nodes.isCommentOrString(c.getCurrentNode())) {
@@ -98,63 +97,46 @@
}
if (model instanceof Literal) {
Literal literal = (Literal) model;
- ContentToInsert content = newContent(literal);
- if (content.equals(ContentToInsert.TAG_NUMBER_INSERTED)) {
- long index = literals.calculateIndexOf(literal);
+ if (shouldCalculateIndex(literal, LITERAL__INDEX)) {
+ long index = literals.calculateNewIndexOf(literal);
literal.setIndex(index);
updateIndexInCommentOfParent(literal, index, document);
+ shouldInsertSemicolon.set(false);
}
- return content;
}
if (model instanceof MessageField) {
MessageField field = (MessageField) model;
- ContentToInsert content = newContent(field);
- if (content.equals(ContentToInsert.TAG_NUMBER_INSERTED)) {
+ if (shouldCalculateIndex(field, MESSAGE_FIELD__INDEX)) {
long index = indexedElements.calculateNewIndexFor(field);
field.setIndex(index);
updateIndexInCommentOfParent(field, index, document);
+ shouldInsertSemicolon.set(false);
}
- return content;
}
}
- return INSERT_SEMICOLON_AT_CURRENT_LOCATION;
}
});
- } catch (Throwable e) {
- logger.error("Unable to generate tag number", e);
- return INSERT_SEMICOLON_AT_CURRENT_LOCATION;
+ } catch (Throwable t) {
+ logger.error("Unable to generate tag number", t);
}
+ if (shouldInsertSemicolon.get()) {
+ styledTextAccess.insert(SEMICOLON);
+ }
+ }
+
+ private boolean shouldCalculateIndex(EObject target, EAttribute indexAttribute) {
+ INode node = nodes.firstNodeForFeature(target, indexAttribute);
+ return node == null || isEmpty(node.getText());
}
private EObject modelFrom(ContentAssistContext c) {
EObject current = c.getCurrentModel();
- if (isIndexed(current)) {
- return current;
- }
- return c.getPreviousModel();
+ boolean isIndexed = current instanceof MessageField || current instanceof Literal;
+ return (isIndexed) ? current : c.getPreviousModel();
}
- private boolean isIndexed(EObject e) {
- return e instanceof MessageField || e instanceof Literal;
- }
-
- private ContentToInsert newContent(Literal literal) {
- INode indexNode = nodes.firstNodeForFeature(literal, LITERAL__INDEX);
- return newContent(indexNode);
- }
-
- private ContentToInsert newContent(MessageField field) {
- INode indexNode = nodes.firstNodeForFeature(field, MESSAGE_FIELD__INDEX);
- return newContent(indexNode);
- }
-
- private ContentToInsert newContent(INode indexNode) {
- boolean hasIndex = indexNode != null && !isEmpty(indexNode.getText());
- return hasIndex ? new ContentToInsert(SEMICOLON, Location.END) : ContentToInsert.TAG_NUMBER_INSERTED;
- }
-
- private void updateIndexInCommentOfParent(EObject o, long index, IXtextDocument document) {
- EObject parent = o.eContainer();
+ private void updateIndexInCommentOfParent(EObject target, long index, IXtextDocument document) {
+ EObject parent = target.eContainer();
if (parent == null) {
return;
}
@@ -184,28 +166,4 @@
}
});
}
-
- private static class ContentToInsert {
- final String value;
- final Location location;
-
- static final ContentToInsert TAG_NUMBER_INSERTED = new ContentToInsert();
-
- ContentToInsert() {
- this("", Location.NONE);
- }
-
- ContentToInsert(String value, Location location) {
- this.value = value;
- this.location = location;
- }
-
- @Override public String toString() {
- return String.format("ContentToInsert [value=%s, location=%s]", value, location);
- }
- }
-
- private static enum Location {
- NONE, CURRENT, END;
- }
}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/semicolon/StyledTextAccess.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/semicolon/StyledTextAccess.java
new file mode 100644
index 0000000..2f6bfda
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/commands/semicolon/StyledTextAccess.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2012 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.ui.commands.semicolon;
+
+import org.eclipse.swt.custom.StyledText;
+
+/**
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+class StyledTextAccess {
+ private final StyledText styledText;
+
+ StyledTextAccess(StyledText styledText) {
+ this.styledText = styledText;
+ }
+
+ String lineAtCaretOffset() {
+ int offset = caretOffset();
+ int lineAtOffset = styledText.getLineAtOffset(offset);
+ return styledText.getLine(lineAtOffset);
+ }
+
+ void setCaretOffsetToEndOfLine() {
+ int offset = caretOffset();
+ int lineAtOffset = styledText.getLineAtOffset(offset);
+ String line = styledText.getLine(lineAtOffset);
+ int offsetAtLine = styledText.getOffsetAtLine(lineAtOffset);
+ offset = offsetAtLine + line.length();
+ styledText.setCaretOffset(offset);
+ }
+
+ void insert(String text) {
+ styledText.insert(text);
+ styledText.setCaretOffset(caretOffset() + text.length());
+ }
+
+ int caretOffset() {
+ return styledText.getCaretOffset();
+ }
+}
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 7e6dadb..6da38e0 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
@@ -282,7 +282,7 @@
@Override public void completeLiteral_Index(EObject model, Assignment assignment, ContentAssistContext context,
ICompletionProposalAcceptor acceptor) {
- long index = literals.calculateIndexOf((Literal) model);
+ long index = literals.calculateNewIndexOf((Literal) model);
proposeIndex(index, context, acceptor);
}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Literals.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Literals.java
index 0dcf2ea..d962ed8 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Literals.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Literals.java
@@ -43,7 +43,7 @@
* the given literal.
* @return the calculated index value.
*/
- public long calculateIndexOf(Literal l) {
+ public long calculateNewIndexOf(Literal l) {
long index = -1;
List<Literal> allLiterals = getAllContentsOfType(l.eContainer(), Literal.class);
for (Literal c : allLiterals) {