[NETBEANS-3588] Code Templates not working in Java Editor in for loops (#2444)
* [NETBEANS-3588] Code Templates not working in Java Editor in for loops
* [NETBEANS-3588] Code Templates not working in Java Editor in for loops
diff --git a/java/java.editor/src/org/netbeans/modules/editor/java/JavaCodeTemplateFilter.java b/java/java.editor/src/org/netbeans/modules/editor/java/JavaCodeTemplateFilter.java
index 3d8b6ef..bbfbe4f 100644
--- a/java/java.editor/src/org/netbeans/modules/editor/java/JavaCodeTemplateFilter.java
+++ b/java/java.editor/src/org/netbeans/modules/editor/java/JavaCodeTemplateFilter.java
@@ -20,27 +20,28 @@
package org.netbeans.modules.editor.java;
import com.sun.source.tree.Tree;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.logging.Logger;
import javax.swing.text.JTextComponent;
-
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.util.SourcePositions;
+import com.sun.source.util.TreePath;
import org.netbeans.api.java.lexer.JavaTokenId;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.JavaSource.Phase;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.api.java.source.TreeUtilities;
+import org.netbeans.api.lexer.Token;
+import org.netbeans.api.lexer.TokenHierarchy;
+import org.netbeans.api.lexer.TokenId;
import org.netbeans.api.lexer.TokenSequence;
-import org.netbeans.api.progress.ProgressUtils;
+import org.netbeans.api.progress.BaseProgressUtils;
import org.netbeans.lib.editor.codetemplates.api.CodeTemplate;
import org.netbeans.lib.editor.codetemplates.spi.CodeTemplateFilter;
import org.netbeans.modules.parsing.api.ParserManager;
@@ -58,7 +59,6 @@
*/
public class JavaCodeTemplateFilter implements CodeTemplateFilter {
- private static final Logger LOG = Logger.getLogger(JavaCodeTemplateFilter.class.getName());
private static final String EXPRESSION = "EXPRESSION"; //NOI18N
private static final String CLASS_HEADER = "CLASS_HEADER"; //NOI18N
@@ -72,88 +72,121 @@
final Source source = Source.create(component.getDocument());
if (source != null) {
final AtomicBoolean cancel = new AtomicBoolean();
- ProgressUtils.runOffEventDispatchThread(new Runnable() {
- @Override
- public void run() {
- try {
- ParserManager.parse(Collections.singleton(source), new UserTask() {
- @Override
- public void run(ResultIterator resultIterator) throws Exception {
- if (cancel.get()) {
- return;
+ BaseProgressUtils.runOffEventDispatchThread(() -> {
+ try {
+ ParserManager.parse(Collections.singleton(source), new UserTask() {
+ @Override
+ public void run(ResultIterator resultIterator) throws Exception {
+ if (cancel.get()) {
+ return;
+ }
+ Parser.Result result = resultIterator.getParserResult(startOffset);
+ CompilationController controller = result != null ? CompilationController.get(result) : null;
+ if (controller != null && Phase.PARSED.compareTo(controller.toPhase(Phase.PARSED)) <= 0) {
+ TreeUtilities tu = controller.getTreeUtilities();
+ int eo = endOffset;
+ int so = startOffset;
+ if (so >= 0) {
+ so = result.getSnapshot().getEmbeddedOffset(startOffset);
}
- Parser.Result result = resultIterator.getParserResult(startOffset);
- CompilationController controller = result != null ? CompilationController.get(result) : null;
- if (controller != null && Phase.PARSED.compareTo(controller.toPhase(Phase.PARSED)) <= 0) {
- TreeUtilities tu = controller.getTreeUtilities();
- int eo = endOffset;
- int so = startOffset;
- if (so >= 0) {
- so = result.getSnapshot().getEmbeddedOffset(startOffset);
- }
- if (endOffset >= 0) {
- eo = result.getSnapshot().getEmbeddedOffset(endOffset);
- TokenSequence<JavaTokenId> ts = SourceUtils.getJavaTokenSequence(controller.getTokenHierarchy(), so);
- int delta = ts.move(so);
+ if (endOffset >= 0) {
+ eo = result.getSnapshot().getEmbeddedOffset(endOffset);
+ TokenSequence<JavaTokenId> ts = SourceUtils.getJavaTokenSequence(controller.getTokenHierarchy(), so);
+ int delta = ts.move(so);
+ if (delta == 0 || ts.moveNext() && ts.token().id() == JavaTokenId.WHITESPACE) {
+ delta = ts.move(eo);
if (delta == 0 || ts.moveNext() && ts.token().id() == JavaTokenId.WHITESPACE) {
- delta = ts.move(eo);
- if (delta == 0 || ts.moveNext() && ts.token().id() == JavaTokenId.WHITESPACE) {
- String selectedText = controller.getText().substring(so, eo).trim();
- SourcePositions[] sp = new SourcePositions[1];
- ExpressionTree expr = selectedText.length() > 0 ? tu.parseExpression(selectedText, sp) : null;
- if (expr != null && expr.getKind() != Tree.Kind.IDENTIFIER && !Utilities.containErrors(expr) && sp[0].getEndPosition(null, expr) >= selectedText.length()) {
- stringCtx = EXPRESSION;
- }
+ String selectedText = controller.getText().substring(so, eo).trim();
+ SourcePositions[] sp = new SourcePositions[1];
+ ExpressionTree expr = selectedText.length() > 0 ? tu.parseExpression(selectedText, sp) : null;
+ if (expr != null && expr.getKind() != Tree.Kind.IDENTIFIER && !Utilities.containErrors(expr) && sp[0].getEndPosition(null, expr) >= selectedText.length()) {
+ stringCtx = EXPRESSION;
}
}
}
- Tree tree = tu.pathFor(so).getLeaf();
- if (eo >= 0 && so != eo) {
- if (tu.pathFor(eo).getLeaf() != tree) {
- return;
- }
+ }
+ Tree tree = tu.pathFor(so).getLeaf();
+ if (eo >= 0 && so != eo) {
+ if (tu.pathFor(eo).getLeaf() != tree) {
+ return;
}
- treeKindCtx = tree.getKind();
- switch (treeKindCtx) {
- case CASE:
- if (so < controller.getTrees().getSourcePositions().getEndPosition(controller.getCompilationUnit(), ((CaseTree)tree).getExpression())) {
- treeKindCtx = null;
- }
- break;
- case CLASS:
- SourcePositions sp = controller.getTrees().getSourcePositions();
- int startPos = (int)sp.getEndPosition(controller.getCompilationUnit(), ((ClassTree)tree).getModifiers());
- if (startPos <= 0) {
- startPos = (int)sp.getStartPosition(controller.getCompilationUnit(), tree);
- }
- String headerText = controller.getText().substring(startPos, so);
- int idx = headerText.indexOf('{'); //NOI18N
- if (idx < 0) {
- treeKindCtx = null;
- stringCtx = CLASS_HEADER;
- }
- break;
- case FOR_LOOP:
- case ENHANCED_FOR_LOOP:
- case WHILE_LOOP:
- sp = controller.getTrees().getSourcePositions();
+ }
+ treeKindCtx = tree.getKind();
+ switch (treeKindCtx) {
+ case CASE:
+ if (so < controller.getTrees().getSourcePositions().getEndPosition(controller.getCompilationUnit(), ((CaseTree)tree).getExpression())) {
+ treeKindCtx = null;
+ }
+ break;
+ case CLASS:
+ SourcePositions sp = controller.getTrees().getSourcePositions();
+ int startPos = (int)sp.getEndPosition(controller.getCompilationUnit(), ((ClassTree)tree).getModifiers());
+ if (startPos <= 0) {
startPos = (int)sp.getStartPosition(controller.getCompilationUnit(), tree);
- String text = controller.getText().substring(startPos, so);
- if (!text.trim().endsWith(")")) {
+ }
+ String headerText = controller.getText().substring(startPos, so);
+ int idx = headerText.indexOf('{'); //NOI18N
+ if (idx < 0) {
+ treeKindCtx = null;
+ stringCtx = CLASS_HEADER;
+ }
+ break;
+ case FOR_LOOP:
+ case ENHANCED_FOR_LOOP:
+ if (!isRightParenthesisOfLoopPresent(controller, so)) {
+ treeKindCtx = null;
+ }
+ break;
+ case PARENTHESIZED:
+ if (isPartOfWhileLoop(controller, so)) {
+ if (!isRightParenthesisOfLoopPresent(controller, so)) {
treeKindCtx = null;
}
- }
+ }
+ break;
}
}
- });
- } catch (ParseException ex) {
- Exceptions.printStackTrace(ex);
- }
+ }
+ });
+ } catch (ParseException ex) {
+ Exceptions.printStackTrace(ex);
}
}, NbBundle.getMessage(JavaCodeTemplateProcessor.class, "JCT-init"), cancel, false); //NOI18N
}
}
}
+
+ private boolean isRightParenthesisOfLoopPresent(CompilationController controller, int abbrevStartOffset) {
+ TokenHierarchy<?> tokenHierarchy = controller.getTokenHierarchy();
+ TokenSequence<?> tokenSequence = tokenHierarchy.tokenSequence();
+ tokenSequence.move(abbrevStartOffset);
+ if (tokenSequence.moveNext()) {
+ TokenId tokenId = skipNextWhitespaces(tokenSequence);
+ return tokenId == null ? false : (tokenId == JavaTokenId.RPAREN);
+ }
+ return false;
+ }
+
+ private TokenId skipNextWhitespaces(TokenSequence<?> tokenSequence) {
+ TokenId tokenId = null;
+ while (tokenSequence.moveNext()) {
+ Token<?> token = tokenSequence.token();
+ if (token != null) {
+ tokenId = token.id();
+ }
+ if (tokenId != JavaTokenId.WHITESPACE) {
+ break;
+ }
+ }
+ return tokenId;
+ }
+
+ private boolean isPartOfWhileLoop(CompilationController controller, int abbrevStartOffset) {
+ TreeUtilities treeUtilities = controller.getTreeUtilities();
+ TreePath currentPath = treeUtilities.pathFor(abbrevStartOffset);
+ TreePath parentPath = treeUtilities.getPathElementOfKind(Tree.Kind.WHILE_LOOP, currentPath);
+ return parentPath != null;
+ }
@Override
public synchronized boolean accept(CodeTemplate template) {
diff --git a/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/JavaCodeTemplateProcessorTest.java b/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/JavaCodeTemplateProcessorTest.java
index 08b5d41..e4843ca 100644
--- a/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/JavaCodeTemplateProcessorTest.java
+++ b/java/java.editor/test/unit/src/org/netbeans/modules/editor/java/JavaCodeTemplateProcessorTest.java
@@ -157,7 +157,52 @@
"}");
assertFileObjectTextMatchesRegex("(?s)\\s*?public class Test.*?");
}
-
+
+ public void testCodeTemplatesShouldWorkInsideParenthesesOfForEachLoop() throws Exception {
+ doTestTemplateInsert("${name newVarName}",
+ "public class Test {\n" +
+ " private void t(String... args) {\n" +
+ " for (String |) {\n" +
+ " }\n" +
+ " }\n" +
+ "}",
+ "public class Test {\n" +
+ " private void t(String... args) {\n" +
+ " for (String name|) {\n" +
+ " }\n" +
+ " }\n" +
+ "}");
+ doTestTemplateInsert("${names iterable}",
+ "public class Test {\n" +
+ " private void t(String... args) {\n" +
+ " for (String name: |) {\n" +
+ " }\n" +
+ " }\n" +
+ "}",
+ "public class Test {\n" +
+ " private void t(String... args) {\n" +
+ " for (String name: args|) {\n" +
+ " }\n" +
+ " }\n" +
+ "}");
+ }
+
+ public void testCodeTemplatesShouldWorkInsideParenthesesOfWhileLoop() throws Exception {
+ doTestTemplateInsert("${list instanceof=\"java.util.List\"}.isEmpty()",
+ "public class Test {\n" +
+ " private void t(String... args) {\n" +
+ " while (|) {\n" +
+ " }\n" +
+ " }\n" +
+ "}",
+ "public class Test {\n" +
+ " private void t(String... args) {\n" +
+ " while (list|.isEmpty()) {\n" +
+ " }\n" +
+ " }\n" +
+ "}");
+ }
+
private void assertFileObjectTextMatchesRegex(String regex) throws IOException {
String text = testFile.asText();
assertTrue("The file text must match the regular expression", text.matches(regex));