diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/completion/CaretLocation.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/completion/CaretLocation.java index e501f06230bc..fbd735d7fcf0 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/completion/CaretLocation.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/completion/CaretLocation.java @@ -28,6 +28,7 @@ public enum CaretLocation { ABOVE_PACKAGE("ABOVE_PACKAGE"), // above the "package" statement (if any). ABOVE_FIRST_CLASS("ABOVE_FIRST_CLASS"), // Outside any classs and above the first class or interface stmt. OUTSIDE_CLASSES("OUTSIDE_CLASSES"), // Outside any class but behind some class or interface stmt. + INSIDE_PACKAGE("INSIDE_PACKAGE"), // inside package statement INSIDE_CLASS("INSIDE_CLASS"), // inside a class definition but not in a method. INSIDE_METHOD("INSIDE_METHOD"), // in a method definition. INSIDE_CLOSURE("INSIDE_CLOSURE"), // inside a closure definition. diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/completion/util/CompletionContext.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/completion/util/CompletionContext.java index b42aff0bbdf9..ff6731639f42 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/completion/util/CompletionContext.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/api/completion/util/CompletionContext.java @@ -28,7 +28,6 @@ import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.ast.Parameter; -import org.codehaus.groovy.ast.expr.ClassExpression; import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.ConstructorCallExpression; @@ -285,6 +284,8 @@ private CaretLocation getCaretLocationFromRequest() { if (t.id() == GroovyTokenId.LITERAL_package) { return CaretLocation.ABOVE_PACKAGE; + } else if (t.id() == GroovyTokenId.LITERAL_class || t.id() == GroovyTokenId.LITERAL_def) { + break; } } @@ -293,6 +294,8 @@ private CaretLocation getCaretLocationFromRequest() { boolean classDefBeforePosition = false; boolean openBraceBeforePosition = false; + // is there package statement? + boolean afterPackagePosition = false; ts.move(position); @@ -303,9 +306,39 @@ private CaretLocation getCaretLocationFromRequest() { } else if (t.id() == GroovyTokenId.LITERAL_class || t.id() == GroovyTokenId.LITERAL_interface || t.id() == GroovyTokenId.LITERAL_trait) { classDefBeforePosition = true; break; + } else if (t.id() == GroovyTokenId.LITERAL_package) { + afterPackagePosition = true; + break; } } + if (afterPackagePosition) { + // are we in the package statement? + ts.move(position); // back on the caret position + boolean isPackageStatement = true; + boolean blockComment = false; + int countOfWhitespaces = 0; + while (ts.isValid() && ts.movePrevious() && ts.offset() >= 0 && isPackageStatement) { + Token t = ts.token(); + if (t.id() == GroovyTokenId.LITERAL_package) { + break; + } else if (t.id() == GroovyTokenId.WHITESPACE) { + countOfWhitespaces++; + } else if (t.id() == GroovyTokenId.BLOCK_COMMENT) { + // cases: + // package /* comment */ org.apache.something + // package /* comment */org.apache.something + // package/* comment */org.apache.something + blockComment = true; + } else { + isPackageStatement = (t.id() == GroovyTokenId.DOT || t.id() == GroovyTokenId.IDENTIFIER); + } + } + if (isPackageStatement && (countOfWhitespaces == 1 || (blockComment && countOfWhitespaces < 3))) { + // we are just behind the package keyword + return CaretLocation.INSIDE_PACKAGE; + } + } boolean classDefAfterPosition = false; @@ -319,7 +352,7 @@ private CaretLocation getCaretLocationFromRequest() { } } - if (path != null) { + if (path != null) { ASTNode node = path.root(); if (node instanceof ModuleNode) { ModuleNode module = (ModuleNode) node; diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/KeywordCompletion.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/KeywordCompletion.java index 76f753bc82b5..87f8de12a751 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/KeywordCompletion.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/KeywordCompletion.java @@ -82,7 +82,19 @@ public boolean complete(Map proposals, CompletionCon // filter-out keywords in a step-by-step approach filterPackageStatement(havePackage); filterPrefix(prefix); - filterLocation(request.location); + if (keywords.contains(GroovyKeyword.KEYWORD_package) && isFirstStatement(request)) { + // This is a hack for offering package keyword in the script as the first statement. + // The current implementation use INSIDE_LOCATION for the top context in script, which is OK, + // but package is only above class keyword and will not be displayed here. + // This covers case, when you have empty file and you want to write package as the first. + filterLocation(request.location); + if (!keywords.contains(GroovyKeyword.KEYWORD_package)) { + // the package is only above class keyword, but on the first position we should offer it + keywords.add(GroovyKeyword.KEYWORD_package); + } + } else { + filterLocation(request.location); + } filterClassInterfaceOrdering(request.context); filterMethodDefinitions(request.context); filterKeywordsNextToEachOther(request.context); @@ -98,6 +110,27 @@ public boolean complete(Map proposals, CompletionCon return true; } + boolean isFirstStatement(final CompletionContext request) { + TokenSequence ts = LexUtilities.getGroovyTokenSequence(request.doc, 1); + + if (ts != null) { + ts.move(request.lexOffset); + if (ts.movePrevious()) { + while (ts.isValid() && ts.movePrevious() && ts.offset() >= 0) { + Token t = ts.token(); + if (!(t.id() == GroovyTokenId.NLS || t.id() == GroovyTokenId.WHITESPACE + || t.id() == GroovyTokenId.SH_COMMENT || t.id() == GroovyTokenId.SL_COMMENT + || t.id() == GroovyTokenId.BLOCK_COMMENT || t.id() == GroovyTokenId.LINE_COMMENT)) { + return false; + } + } + } + return true; + } + + return false; + } + boolean checkForPackageStatement(final CompletionContext request) { TokenSequence ts = LexUtilities.getGroovyTokenSequence(request.doc, 1); diff --git a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/GroovyCompletionImpl.java b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/GroovyCompletionImpl.java index 43d300283c53..297a11493ce1 100644 --- a/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/GroovyCompletionImpl.java +++ b/groovy/groovy.editor/src/org/netbeans/modules/groovy/editor/completion/provider/GroovyCompletionImpl.java @@ -203,6 +203,12 @@ public CompletionImplResult makeProposals(CodeCompletionContext completionContex } private void makeClassProposals(ProposalsCollector proposalsCollector, CompletionContext context) { + + if (context.location == CaretLocation.INSIDE_PACKAGE) { + proposalsCollector.completePackages(context); + return; + } + if (!(context.location == CaretLocation.OUTSIDE_CLASSES || context.location == CaretLocation.INSIDE_STRING)) { proposalsCollector.completePackages(context); proposalsCollector.completeTypes(context); diff --git a/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword01/PackageKeyword01.groovy b/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword01/PackageKeyword01.groovy new file mode 100644 index 000000000000..b557c916720a --- /dev/null +++ b/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword01/PackageKeyword01.groovy @@ -0,0 +1 @@ +pac diff --git a/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword01/PackageKeyword01.groovy.testPackageKeyword01.completion b/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword01/PackageKeyword01.groovy.testPackageKeyword01.completion new file mode 100644 index 000000000000..5cc4bc82878f --- /dev/null +++ b/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword01/PackageKeyword01.groovy.testPackageKeyword01.completion @@ -0,0 +1,7 @@ +Code completion result for source line: +pac| +(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true) +------------------------------------ +CLASS Package null +CLASS PackageKeyword01 null +KEYWORD package null diff --git a/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword02/PackageKeyword02.groovy b/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword02/PackageKeyword02.groovy new file mode 100644 index 000000000000..8365332e6d4f --- /dev/null +++ b/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword02/PackageKeyword02.groovy @@ -0,0 +1,5 @@ +pac + +class AA { + +} diff --git a/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword02/PackageKeyword02.groovy.testPackageKeyword02.completion b/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword02/PackageKeyword02.groovy.testPackageKeyword02.completion new file mode 100644 index 000000000000..f73a27285751 --- /dev/null +++ b/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword02/PackageKeyword02.groovy.testPackageKeyword02.completion @@ -0,0 +1,7 @@ +Code completion result for source line: +pac| +(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true) +------------------------------------ +CLASS Package null +CLASS PackageKeyword02 null +KEYWORD package null diff --git a/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword03/PackageKeyword03.groovy b/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword03/PackageKeyword03.groovy new file mode 100644 index 000000000000..ffc26ad9beb0 --- /dev/null +++ b/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword03/PackageKeyword03.groovy @@ -0,0 +1,5 @@ +/** +comment +*/ + + pac diff --git a/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword03/PackageKeyword03.groovy.testPackageKeyword03.completion b/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword03/PackageKeyword03.groovy.testPackageKeyword03.completion new file mode 100644 index 000000000000..e6e8de1f11a7 --- /dev/null +++ b/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packageKeyword03/PackageKeyword03.groovy.testPackageKeyword03.completion @@ -0,0 +1,7 @@ +Code completion result for source line: +pac| +(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true) +------------------------------------ +CLASS Package null +CLASS PackageKeyword03 null +KEYWORD package null diff --git a/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packagesCC01/PackagesCC01.groovy b/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packagesCC01/PackagesCC01.groovy new file mode 100644 index 000000000000..be1d226ec0ef --- /dev/null +++ b/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packagesCC01/PackagesCC01.groovy @@ -0,0 +1 @@ +package /* a comment */ or diff --git a/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packagesCC01/PackagesCC01.groovy.testPackagesCC01.completion b/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packagesCC01/PackagesCC01.groovy.testPackagesCC01.completion new file mode 100644 index 000000000000..f8c5f774f4ce --- /dev/null +++ b/groovy/groovy.editor/test/unit/data/testfiles/completion/package/packagesCC01/PackagesCC01.groovy.testPackagesCC01.completion @@ -0,0 +1,5 @@ +Code completion result for source line: +package /* a comment */ or| +(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true) +------------------------------------ +PACKAGE org null diff --git a/groovy/groovy.editor/test/unit/src/org/netbeans/modules/groovy/editor/api/completion/PackageKeywordCCTest.java b/groovy/groovy.editor/test/unit/src/org/netbeans/modules/groovy/editor/api/completion/PackageKeywordCCTest.java new file mode 100644 index 000000000000..5d888b228055 --- /dev/null +++ b/groovy/groovy.editor/test/unit/src/org/netbeans/modules/groovy/editor/api/completion/PackageKeywordCCTest.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.groovy.editor.api.completion; + +import org.junit.Before; + +/** + * + * @author Petr Pisl + */ +public class PackageKeywordCCTest extends GroovyCCTestBase { + + public PackageKeywordCCTest(String testName) { + super(testName); + } + + @Override + protected String getTestType() { + return "package"; + } + + public void testPackageKeyword01() throws Exception { + checkCompletion(BASE + "PackageKeyword01.groovy", "pac^", true); + } + + public void testPackageKeyword02() throws Exception { + checkCompletion(BASE + "PackageKeyword02.groovy", "pac^", true); + } + + public void testPackageKeyword03() throws Exception { + checkCompletion(BASE + "PackageKeyword03.groovy", "pac^", true); + } + + public void testPackagesCC01() throws Exception { + checkCompletion(BASE + "PackagesCC01.groovy", "package /* a comment */ or^", true); + } + +}