Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}

Expand All @@ -293,6 +294,8 @@ private CaretLocation getCaretLocationFromRequest() {

boolean classDefBeforePosition = false;
boolean openBraceBeforePosition = false;
// is there package statement?
boolean afterPackagePosition = false;

ts.move(position);

Expand All @@ -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<GroovyTokenId> 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;

Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,19 @@ public boolean complete(Map<Object, CompletionProposal> 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);
Expand All @@ -98,6 +110,27 @@ public boolean complete(Map<Object, CompletionProposal> proposals, CompletionCon
return true;
}

boolean isFirstStatement(final CompletionContext request) {
TokenSequence<GroovyTokenId> ts = LexUtilities.getGroovyTokenSequence(request.doc, 1);

if (ts != null) {
ts.move(request.lexOffset);
if (ts.movePrevious()) {
while (ts.isValid() && ts.movePrevious() && ts.offset() >= 0) {
Comment thread
sdedic marked this conversation as resolved.
Token<GroovyTokenId> t = ts.token();
if (!(t.id() == GroovyTokenId.NLS || t.id() == GroovyTokenId.WHITESPACE

@sdedic sdedic Nov 15, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be moved off to some utilities a a Predicate<Token<GroovyTokenId>> as I fear that whitespace-traversing code is on many places and does not take all the WS/comment tokens into account (see for example the package name back traversal code in this PR).

Edit: correction - there's some support already: see findPreviousNonWsNonComment, so maybe SH_COMMENT and SL_COMMENT the whitespace set should be added to the WS set there ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good idea. Do you want to do it in this PR?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps yes, but a possible retrofit of other code (that should use the helper instead of custom WS token checks) should be perhaps done separately. Your call.

|| 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<GroovyTokenId> ts = LexUtilities.getGroovyTokenSequence(request.doc, 1);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pac
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pac

class AA {

}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
comment
*/

pac
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package /* a comment */ or
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Code completion result for source line:
package /* a comment */ or|
(QueryType=COMPLETION, prefixSearch=true, caseSensitive=true)
------------------------------------
PACKAGE org null
Original file line number Diff line number Diff line change
@@ -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);
}

}