diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/HashCodeEqualsHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/HashCodeEqualsHandler.java index 61e00bbd63..d8581798b9 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/HashCodeEqualsHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/HashCodeEqualsHandler.java @@ -22,15 +22,19 @@ import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.IVariableBinding; +import org.eclipse.jdt.core.dom.NodeFinder; +import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.manipulation.CoreASTProvider; import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationSettings; import org.eclipse.jdt.internal.corext.codemanipulation.GenerateHashCodeEqualsOperation; import org.eclipse.jdt.internal.corext.dom.ASTNodes; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; +import org.eclipse.jdt.ls.core.internal.corrections.DiagnosticsHelper; import org.eclipse.jdt.ls.core.internal.handlers.JdtDomModels.LspVariableBinding; import org.eclipse.jdt.ls.core.internal.preferences.Preferences; import org.eclipse.jdt.ls.core.internal.text.correction.SourceAssistProcessor; @@ -113,8 +117,10 @@ public static TextEdit generateHashCodeEqualsTextEdit(IType type, LspVariableBin CodeGenerationSettings codeGenSettings = new CodeGenerationSettings(); codeGenSettings.createComments = generateComments; codeGenSettings.overrideAnnotation = true; + ASTNode node = NodeFinder.perform(astRoot, DiagnosticsHelper.getStartOffset(type.getCompilationUnit(), cursor), DiagnosticsHelper.getLength(type.getCompilationUnit(), cursor)); + ASTNode declarationNode = SourceAssistProcessor.getDeclarationNode(node); // If cursor position is not specified, then insert to the last by default. - IJavaElement insertPosition = CodeGenerationUtils.findInsertElement(type, cursor); + IJavaElement insertPosition = (declarationNode instanceof TypeDeclaration) ? CodeGenerationUtils.findInsertElement(type, null) : CodeGenerationUtils.findInsertElement(type, cursor); GenerateHashCodeEqualsOperation operation = new GenerateHashCodeEqualsOperation(typeBinding, variableBindings, astRoot, insertPosition, codeGenSettings, useInstanceof, useJava7Objects, regenerate, false, false); operation.setUseBlocksForThen(useBlocks); operation.run(null); diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/text/correction/SourceAssistProcessor.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/text/correction/SourceAssistProcessor.java index eb51c291e0..913efa1753 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/text/correction/SourceAssistProcessor.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/text/correction/SourceAssistProcessor.java @@ -34,8 +34,11 @@ import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; +import org.eclipse.jdt.core.dom.BodyDeclaration; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.Statement; +import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.manipulation.CoreASTProvider; import org.eclipse.jdt.core.manipulation.OrganizeImportsOperation; import org.eclipse.jdt.internal.corext.dom.ASTNodes; @@ -138,8 +141,22 @@ public List> getSourceActionCommands(CodeActionParam // Generate hashCode() and equals() if (supportsHashCodeEquals(context, type, monitor)) { - Optional> hashCodeEquals = getHashCodeEqualsAction(params); - addSourceActionCommand($, params.getContext(), hashCodeEquals); + // Generate QuickAssist + Optional> quickAssistHashCodeEquals = Optional.empty(); + ASTNode node = context.getCoveredNode(); + if (node == null) { + node = context.getCoveringNode(); + } + ASTNode declarationNode = getDeclarationNode(node); + if (declarationNode instanceof TypeDeclaration) { + quickAssistHashCodeEquals = getHashCodeEqualsAction(params, JavaCodeActionKind.QUICK_ASSIST); + addSourceActionCommand($, params.getContext(), quickAssistHashCodeEquals); + } + + // Generate Source Action + Optional> sourceActionHashCodeEquals = getHashCodeEqualsAction(params, JavaCodeActionKind.SOURCE_GENERATE_HASHCODE_EQUALS); + addSourceActionCommand($, params.getContext(), sourceActionHashCodeEquals); + } // Generate toString() @@ -304,16 +321,16 @@ private boolean supportsHashCodeEquals(IInvocationContext context, IType type, I } } - private Optional> getHashCodeEqualsAction(CodeActionParams params) { + private Optional> getHashCodeEqualsAction(CodeActionParams params, String kind) { if (!preferenceManager.getClientPreferences().isHashCodeEqualsPromptSupported()) { return Optional.empty(); } Command command = new Command(ActionMessages.GenerateHashCodeEqualsAction_label, COMMAND_ID_ACTION_HASHCODEEQUALSPROMPT, Collections.singletonList(params)); if (preferenceManager.getClientPreferences().isSupportedCodeActionKind(JavaCodeActionKind.SOURCE_GENERATE_HASHCODE_EQUALS)) { CodeAction codeAction = new CodeAction(ActionMessages.GenerateHashCodeEqualsAction_label); - codeAction.setKind(JavaCodeActionKind.SOURCE_GENERATE_HASHCODE_EQUALS); + codeAction.setKind(kind); codeAction.setCommand(command); - codeAction.setDiagnostics(Collections.EMPTY_LIST); + codeAction.setDiagnostics(Collections.emptyList()); return Optional.of(Either.forRight(codeAction)); } else { return Optional.of(Either.forLeft(command)); @@ -428,7 +445,7 @@ private Optional> addFinalModifierWherePossibleActio JavaLanguageServerPlugin.logException("Problem converting proposal to code actions", e); return Optional.empty(); } - + if (!ChangeUtil.hasChanges(edit)) { return Optional.empty(); } @@ -459,7 +476,7 @@ private Optional> getCodeActionFromProposal(CodeActi if (!ChangeUtil.hasChanges(edit)) { return Optional.empty(); } - + Command command = new Command(name, CodeActionHandler.COMMAND_ID_APPLY_EDIT, Collections.singletonList(edit)); if (preferenceManager.getClientPreferences().isSupportedCodeActionKind(kind)) { CodeAction codeAction = new CodeAction(name); @@ -555,4 +572,17 @@ public static InnovationContext getInnovationContext(CodeActionParams params, IP public static ICompilationUnit getCompilationUnit(CodeActionParams params) { return JDTUtils.resolveCompilationUnit(params.getTextDocument().getUri()); } + + public static ASTNode getDeclarationNode(ASTNode node) { + if (node == null) { + return null; + } + if (node instanceof BodyDeclaration) { + return null; + } + while (node != null && !(node instanceof BodyDeclaration) && !(node instanceof Statement)) { + node = node.getParent(); + } + return node; + } } diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionHandlerTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionHandlerTest.java index b4af2a3df5..bdb699f403 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionHandlerTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/CodeActionHandlerTest.java @@ -609,4 +609,21 @@ public static Either findAction(List> any = codeActions.stream().filter((action) -> Objects.equals(kind, action.getLeft() == null ? action.getRight().getKind() : action.getLeft().getCommand())).findFirst(); return any.isPresent() ? any.get() : null; } + + public static List> findActions(List> codeActions, String kind) { + return codeActions.stream().filter((action) -> Objects.equals(kind, action.getLeft() == null ? action.getRight().getKind() : action.getLeft().getCommand())).collect(Collectors.toList()); + } + + public static boolean commandExists(List> codeActions, String command) { + if (codeActions.isEmpty()) { + return false; + } + for (Either codeAction : codeActions) { + Command actionCommand = getCommand(codeAction); + if (actionCommand != null && actionCommand.getCommand().equals(command)) { + return true; + } + } + return false; + } } diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/HashCodeEqualsActionTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/HashCodeEqualsActionTest.java index 61a77a3d83..ca864b944e 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/HashCodeEqualsActionTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/HashCodeEqualsActionTest.java @@ -26,6 +26,7 @@ import org.eclipse.jdt.ls.core.internal.JavaClientConnection; import org.eclipse.jdt.ls.core.internal.JavaCodeActionKind; import org.eclipse.jdt.ls.core.internal.LanguageServerWorkingCopyOwner; +import org.eclipse.jdt.ls.core.internal.text.correction.SourceAssistProcessor; import org.eclipse.lsp4j.CodeAction; import org.eclipse.lsp4j.CodeActionParams; import org.eclipse.lsp4j.Command; @@ -120,4 +121,29 @@ public void testHashCodeEqualsDisabled_enum() throws JavaModelException { Assert.assertNotNull(codeActions); Assert.assertFalse("The operation is not applicable to enums", CodeActionHandlerTest.containsKind(codeActions, JavaCodeActionKind.SOURCE_GENERATE_HASHCODE_EQUALS)); } + + @Test + public void testHashCodeEqualsQuickAssist() throws JavaModelException { + //@formatter:off + ICompilationUnit unit = fPackageP.createCompilationUnit("A.java", "package p;\r\n" + + "\r\n" + + "public class A {\r\n" + + " public final String name = \"test\";\r\n" + + "}" + , true, null); + //@formatter:on + CodeActionParams params = CodeActionUtil.constructCodeActionParams(unit, "A"); + List> codeActions = server.codeAction(params).join(); + Assert.assertNotNull(codeActions); + List> quickAssistActions = CodeActionHandlerTest.findActions(codeActions, JavaCodeActionKind.QUICK_ASSIST); + Assert.assertFalse(quickAssistActions.isEmpty()); + Assert.assertTrue(CodeActionHandlerTest.commandExists(quickAssistActions, SourceAssistProcessor.COMMAND_ID_ACTION_HASHCODEEQUALSPROMPT)); + // Test if the quick assist exists only for type declaration + params = CodeActionUtil.constructCodeActionParams(unit, "String name"); + codeActions = server.codeAction(params).join(); + Assert.assertNotNull(codeActions); + quickAssistActions = CodeActionHandlerTest.findActions(codeActions, JavaCodeActionKind.QUICK_ASSIST); + Assert.assertFalse(quickAssistActions.isEmpty()); + Assert.assertFalse(CodeActionHandlerTest.commandExists(quickAssistActions, SourceAssistProcessor.COMMAND_ID_ACTION_HASHCODEEQUALSPROMPT)); + } }