You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by db...@apache.org on 2021/11/15 10:31:46 UTC

[netbeans] branch master updated: LSP: Organize imports action added. (#3317)

This is an automated email from the ASF dual-hosted git repository.

dbalek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git


The following commit(s) were added to refs/heads/master by this push:
     new 4a4528e  LSP: Organize imports action added. (#3317)
4a4528e is described below

commit 4a4528e5089acd6a74157964ef1067b2f508fcae
Author: Dusan Balek <du...@oracle.com>
AuthorDate: Mon Nov 15 11:31:20 2021 +0100

    LSP: Organize imports action added. (#3317)
---
 .../ChangeMethodParametersRefactoring.java         |   2 +-
 .../lsp/server/protocol/CodeActionsProvider.java   |  35 ++-
 .../lsp/server/protocol/ConstructorGenerator.java  | 103 ++++++---
 .../server/protocol/DelegateMethodGenerator.java   | 202 +++++++++--------
 .../server/protocol/EqualsHashCodeGenerator.java   |  87 ++++----
 .../ExtractSuperclassOrInterfaceRefactoring.java   |   4 +-
 .../lsp/server/protocol/GetterSetterGenerator.java | 135 ++++++-----
 .../protocol/ImplementOverrideMethodGenerator.java | 118 +++++-----
 .../java/lsp/server/protocol/LoggerGenerator.java  |  50 +++--
 .../java/lsp/server/protocol/MoveRefactoring.java  |   4 +-
 .../server/protocol/OrganizeImportsCodeAction.java |  87 ++++++++
 .../lsp/server/protocol/PullUpRefactoring.java     |   2 +-
 .../lsp/server/protocol/PushDownRefactoring.java   |   2 +-
 .../modules/java/lsp/server/protocol/Server.java   |  40 +++-
 .../java/lsp/server/protocol/SurroundWithHint.java |  14 +-
 .../server/protocol/TextDocumentServiceImpl.java   |  45 +++-
 .../lsp/server/protocol/ToStringGenerator.java     | 106 ++++-----
 .../lsp/server/protocol/WorkspaceServiceImpl.java  |  21 +-
 .../java/lsp/server/protocol/ServerTest.java       | 246 +++++++++++++--------
 java/java.lsp.server/vscode/package.json           |  28 +++
 java/java.lsp.server/vscode/src/extension.ts       |   2 +-
 21 files changed, 857 insertions(+), 476 deletions(-)

diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ChangeMethodParametersRefactoring.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ChangeMethodParametersRefactoring.java
index 849b4b6..157fdce 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ChangeMethodParametersRefactoring.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ChangeMethodParametersRefactoring.java
@@ -108,7 +108,7 @@ public final class ChangeMethodParametersRefactoring extends CodeRefactoring {
         }
         QuickPickItem elementItem = new QuickPickItem(createLabel(info, element, true));
         elementItem.setUserData(new ElementData(element));
-        return Collections.singletonList(createCodeAction(Bundle.DN_ChangeMethodParams(), CHANGE_METHOD_PARAMS_REFACTORING_KIND, CHANGE_METHOD_PARAMS_REFACTORING_COMMAND, Utils.toUri(elementSource), elementItem));
+        return Collections.singletonList(createCodeAction(Bundle.DN_ChangeMethodParams(), CHANGE_METHOD_PARAMS_REFACTORING_KIND, null, CHANGE_METHOD_PARAMS_REFACTORING_COMMAND, Utils.toUri(elementSource), elementItem));
     }
 
     @Override
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/CodeActionsProvider.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/CodeActionsProvider.java
index db57677..5ea0ec7 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/CodeActionsProvider.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/CodeActionsProvider.java
@@ -20,7 +20,10 @@ package org.netbeans.modules.java.lsp.server.protocol;
 
 import com.sun.source.tree.LineMap;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
@@ -47,26 +50,44 @@ import org.netbeans.modules.parsing.api.ResultIterator;
 public abstract class CodeActionsProvider {
 
     public static final String CODE_GENERATOR_KIND = "source.generate";
+    public static final String CODE_ACTIONS_PROVIDER_CLASS = "providerClass";
+    public static final String DATA = "data";
     protected static final String ERROR = "<error>"; //NOI18N
 
     public abstract List<CodeAction> getCodeActions(ResultIterator resultIterator, CodeActionParams params) throws Exception;
 
-    public abstract Set<String> getCommands();
+    public CompletableFuture<CodeAction> resolve(NbCodeLanguageClient client, CodeAction codeAction, Object data) {
+        return CompletableFuture.completedFuture(codeAction);
+    }
 
-    public abstract CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments);
+    public Set<String> getCommands() {
+        return Collections.emptySet();
+    }
 
-    protected static int getOffset(CompilationInfo info, Position pos) {
-        LineMap lm = info.getCompilationUnit().getLineMap();
-        return (int) lm.getPosition(pos.getLine() + 1, pos.getCharacter() + 1);
+    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
+        return CompletableFuture.completedFuture(false);
     }
 
-    protected static CodeAction createCodeAction(String name, String kind, String command, Object... args) {
+    protected CodeAction createCodeAction(String name, String kind, Object data, String command, Object... commandArgs) {
         CodeAction action = new CodeAction(name);
         action.setKind(kind);
-        action.setCommand(new Command(name, command, Arrays.asList(args)));
+        if (command != null) {
+            action.setCommand(new Command(name, command, Arrays.asList(commandArgs)));
+        }
+        if (data != null) {
+            Map<String, Object> map = new HashMap<>();
+            map.put(CODE_ACTIONS_PROVIDER_CLASS, getClass().getName());
+            map.put(DATA, data);
+            action.setData(map);
+        }
         return action;
     }
 
+    protected static int getOffset(CompilationInfo info, Position pos) {
+        LineMap lm = info.getCompilationUnit().getLineMap();
+        return (int) lm.getPosition(pos.getLine() + 1, pos.getCharacter() + 1);
+    }
+
     protected static String createLabel(CompilationInfo info, Element e) {
         return createLabel(info, e, false);
     }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ConstructorGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ConstructorGenerator.java
index 7086bdd..1528927 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ConstructorGenerator.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ConstructorGenerator.java
@@ -19,6 +19,8 @@
 package org.netbeans.modules.java.lsp.server.protocol;
 
 import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSyntaxException;
 import com.sun.source.tree.ClassTree;
 import com.sun.source.tree.Tree;
 import com.sun.source.util.TreePath;
@@ -26,8 +28,10 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
@@ -43,7 +47,6 @@ import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
 import javax.lang.model.util.ElementFilter;
 import javax.lang.model.util.Elements;
-import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
 import org.eclipse.lsp4j.CodeAction;
 import org.eclipse.lsp4j.CodeActionKind;
 import org.eclipse.lsp4j.CodeActionParams;
@@ -68,9 +71,11 @@ import org.openide.util.lookup.ServiceProvider;
 @ServiceProvider(service = CodeActionsProvider.class, position = 10)
 public final class ConstructorGenerator extends CodeActionsProvider {
 
-    public static final String GENERATE_CONSTRUCTOR =  "java.generate.constructor";
+    private static final String URI =  "uri";
+    private static final String OFFSET =  "offset";
+    private static final String CONSTRUCTORS =  "constructors";
+    private static final String FIELDS =  "fields";
 
-    private final Set<String> commands = Collections.singleton(GENERATE_CONSTRUCTOR);
     private final Gson gson = new Gson();
 
     public ConstructorGenerator() {
@@ -169,62 +174,101 @@ public final class ConstructorGenerator extends CodeActionsProvider {
             return Collections.emptyList();
         }
         String uri = Utils.toUri(info.getFileObject());
-        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateConstructor(), isSource ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, GENERATE_CONSTRUCTOR, uri, startOffset, constructors, fields));
-    }
-
-    @Override
-    public Set<String> getCommands() {
-        return commands;
+        Map<String, Object> data = new HashMap<>();
+        data.put(URI, uri);
+        data.put(OFFSET, startOffset);
+        data.put(CONSTRUCTORS, constructors);
+        data.put(FIELDS, fields);
+        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateConstructor(), isSource ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, data, null));
     }
 
     @Override
     @NbBundle.Messages({
         "DN_SelectSuperConstructor=Select super constructor",
     })
-    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
-        if (arguments.size() > 3) {
-            String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
-            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
-            List<QuickPickItem> constructors = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem[].class));
-            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(3)), QuickPickItem[].class));
+    public CompletableFuture<CodeAction> resolve(NbCodeLanguageClient client, CodeAction codeAction, Object data) {
+        CompletableFuture<CodeAction> future = new CompletableFuture<>();
+        try {
+            String uri = ((JsonObject) data).getAsJsonPrimitive(URI).getAsString();
+            int offset = ((JsonObject) data).getAsJsonPrimitive(OFFSET).getAsInt();
+            List<QuickPickItem> constructors = Arrays.asList(gson.fromJson(gson.toJson(((JsonObject) data).get(CONSTRUCTORS)), QuickPickItem[].class));
+            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(((JsonObject) data).get(FIELDS), QuickPickItem[].class));
             if (constructors.size() < 2 && fields.isEmpty()) {
-                generate(client, uri, offset, constructors, fields);
+                WorkspaceEdit edit = generate(client, uri, offset, constructors, fields);
+                if (edit != null) {
+                    codeAction.setEdit(edit);
+                }
+                future.complete(codeAction);
             } else {
                 if (constructors.size() > 1) {
                     client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectSuperConstructor(), true, constructors)).thenAccept(selected -> {
-                        if (selected != null) {
-                            selectFields(client, uri, offset, selected, fields);
+                        try {
+                            if (selected != null) {
+                                selectFields(client, uri, offset, selected, fields).handle((edit, ex) -> {
+                                    if (ex != null) {
+                                        future.completeExceptionally(ex);
+                                    } else {
+                                        if (edit != null) {
+                                            codeAction.setEdit(edit);
+                                        }
+                                        future.complete(codeAction);
+                                    }
+                                    return null;
+                                });
+                            } else {
+                                future.complete(codeAction);
+                            }
+                        } catch (IOException | IllegalArgumentException ex) {
+                            future.completeExceptionally(ex);
                         }
                     });
                 } else {
-                    selectFields(client, uri, offset, constructors, fields);
+                    selectFields(client, uri, offset, constructors, fields).handle((edit, ex) -> {
+                        if (ex != null) {
+                            future.completeExceptionally(ex);
+                        } else {
+                            if (edit != null) {
+                                codeAction.setEdit(edit);
+                            }
+                            future.complete(codeAction);
+                        }
+                        return null;
+                    });
                 }
             }
-        } else {
-            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        } catch (JsonSyntaxException | IOException | IllegalArgumentException ex) {
+            future.completeExceptionally(ex);
         }
-        return CompletableFuture.completedFuture(true);
+        return future;
     }
 
     @NbBundle.Messages({
         "DN_SelectConstructorFields=Select fields to be initialized by constructor",
     })
-    private void selectFields(NbCodeLanguageClient client, String uri, int offset, List<QuickPickItem> constructors, List<QuickPickItem> fields) {
+    private CompletableFuture<WorkspaceEdit> selectFields(NbCodeLanguageClient client, String uri, int offset, List<QuickPickItem> constructors, List<QuickPickItem> fields) throws IOException, IllegalArgumentException {
+        CompletableFuture<WorkspaceEdit> future = new CompletableFuture<>();
         if (!fields.isEmpty()) {
             client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectConstructorFields(), true, fields)).thenAccept(selected -> {
-                if (selected != null) {
-                    generate(client, uri, offset, constructors, selected);
+                try {
+                    if (selected != null) {
+                        future.complete(generate(client, uri, offset, constructors, selected));
+                    } else {
+                        future.complete(null);
+                    }
+                } catch (IOException | IllegalArgumentException ex) {
+                    future.completeExceptionally(ex);
                 }
             });
         } else {
-            generate(client, uri, offset, constructors, fields);
+            future.complete(generate(client, uri, offset, constructors, fields));
         }
+        return future;
     }
 
     @NbBundle.Messages({
         "DN_ConstructorAlreadyExists=Given constructor already exists",
     })
-    private void generate(NbCodeLanguageClient client, String uri, int offset, List<QuickPickItem> constructors, List<QuickPickItem> fields) {
+    private WorkspaceEdit generate(NbCodeLanguageClient client, String uri, int offset, List<QuickPickItem> constructors, List<QuickPickItem> fields) throws IOException, IllegalArgumentException {
         try {
             FileObject file = Utils.fromUri(uri);
             JavaSource js = JavaSource.forFileObject(file);
@@ -247,11 +291,10 @@ public final class ConstructorGenerator extends CodeActionsProvider {
                     GeneratorUtils.generateConstructors(wc, tp, selectedFields, selectedConstructors, -1);
                 }
             });
-            client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
+            return edits.isEmpty() ? null : new WorkspaceEdit(Collections.singletonMap(uri, edits));
         } catch (GeneratorUtils.DuplicateMemberException dme) {
             client.showMessage(new MessageParams(MessageType.Info, Bundle.DN_ConstructorAlreadyExists()));
-        } catch (IOException | IllegalArgumentException ex) {
-            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
         }
+        return null;
     }
 }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DelegateMethodGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DelegateMethodGenerator.java
index 6f605b1..9673e31 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DelegateMethodGenerator.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/DelegateMethodGenerator.java
@@ -19,6 +19,8 @@
 package org.netbeans.modules.java.lsp.server.protocol;
 
 import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSyntaxException;
 import com.sun.source.tree.Scope;
 import com.sun.source.util.TreePath;
 import com.sun.source.util.Trees;
@@ -26,8 +28,9 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
-import java.util.Set;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 import javax.lang.model.element.Element;
@@ -41,12 +44,9 @@ import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
 import javax.lang.model.util.ElementFilter;
 import javax.lang.model.util.Elements;
-import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
 import org.eclipse.lsp4j.CodeAction;
 import org.eclipse.lsp4j.CodeActionKind;
 import org.eclipse.lsp4j.CodeActionParams;
-import org.eclipse.lsp4j.MessageParams;
-import org.eclipse.lsp4j.MessageType;
 import org.eclipse.lsp4j.TextEdit;
 import org.eclipse.lsp4j.WorkspaceEdit;
 import org.netbeans.api.java.source.CompilationController;
@@ -66,14 +66,13 @@ import org.openide.util.lookup.ServiceProvider;
 @ServiceProvider(service = CodeActionsProvider.class, position = 60)
 public final class DelegateMethodGenerator extends CodeActionsProvider {
 
-    public static final String GENERATE_DELEGATE_METHOD =  "java.generate.delegateMethod";
+    private static final String URI =  "uri";
+    private static final String OFFSET =  "offset";
+    private static final String TYPE =  "type";
+    private static final String FIELDS =  "fields";
 
-    private final Set<String> commands = Collections.singleton(GENERATE_DELEGATE_METHOD);
     private final Gson gson = new Gson();
 
-    public DelegateMethodGenerator() {
-    }
-
     @Override
     @NbBundle.Messages({
         "DN_GenerateDelegateMethod=Generate Delegate Method...",
@@ -122,108 +121,137 @@ public final class DelegateMethodGenerator extends CodeActionsProvider {
         String uri = Utils.toUri(info.getFileObject());
         QuickPickItem typeItem = new QuickPickItem(createLabel(info, typeElement));
         typeItem.setUserData(new ElementData(typeElement));
-        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateDelegateMethod(), CODE_GENERATOR_KIND, GENERATE_DELEGATE_METHOD, uri, offset, typeItem, fields));
-    }
-
-    @Override
-    public Set<String> getCommands() {
-        return commands;
+        Map<String, Object> data = new HashMap<>();
+        data.put(URI, uri);
+        data.put(OFFSET, offset);
+        data.put(TYPE, typeItem);
+        data.put(FIELDS, fields);
+        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateDelegateMethod(), CODE_GENERATOR_KIND, data, null));
     }
 
     @Override
     @NbBundle.Messages({
         "DN_SelectDelegateMethodField=Select target field to generate delegates for",
     })
-    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
-        if (arguments.size() > 3) {
-            String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
-            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
-            QuickPickItem type = gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem.class);
-            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(3)), QuickPickItem[].class));
+    public CompletableFuture<CodeAction> resolve(NbCodeLanguageClient client, CodeAction codeAction, Object data) {
+        CompletableFuture<CodeAction> future = new CompletableFuture<>();
+        try {
+            String uri = ((JsonObject) data).getAsJsonPrimitive(URI).getAsString();
+            int offset = ((JsonObject) data).getAsJsonPrimitive(OFFSET).getAsInt();
+            QuickPickItem type = gson.fromJson(gson.toJson(((JsonObject) data).get(TYPE)), QuickPickItem.class);
+            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(((JsonObject) data).get(FIELDS), QuickPickItem[].class));
             if (fields.size() == 1) {
-                selectMethods(client, uri, offset, type, fields.get(0));
+                selectMethods(client, uri, offset, type, fields.get(0)).handle((edit, ex) -> {
+                    if (ex != null) {
+                        future.completeExceptionally(ex);
+                    } else {
+                        if (edit != null) {
+                            codeAction.setEdit(edit);
+                        }
+                        future.complete(codeAction);
+                    }
+                    return null;
+                });
             } else {
                 client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectDelegateMethodField(), false, fields)).thenAccept(selected -> {
-                    if (selected != null && !selected.isEmpty()) {
-                        selectMethods(client, uri, offset, type, selected.get(0));
+                    try {
+                        if (selected != null && !selected.isEmpty()) {
+                            selectMethods(client, uri, offset, type, selected.get(0)).handle((edit, ex) -> {
+                                if (ex != null) {
+                                    future.completeExceptionally(ex);
+                                } else {
+                                    if (edit != null) {
+                                        codeAction.setEdit(edit);
+                                    }
+                                    future.complete(codeAction);
+                                }
+                                return null;
+                            });
+                        } else {
+                            future.complete(codeAction);
+                        }
+                    } catch (IOException | IllegalArgumentException ex) {
+                        future.completeExceptionally(ex);
                     }
                 });
             }
-        } else {
-            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        } catch (JsonSyntaxException | IOException | IllegalArgumentException ex) {
+            future.completeExceptionally(ex);
         }
-        return CompletableFuture.completedFuture(true);
+        return future;
     }
 
     @NbBundle.Messages({
         "DN_SelectDelegateMethods=Select methods to generate delegates for",
     })
-    private void selectMethods(NbCodeLanguageClient client, String uri, int offset, QuickPickItem type, QuickPickItem selectedField) {
-        try {
-            FileObject file = Utils.fromUri(uri);
-            JavaSource js = JavaSource.forFileObject(file);
-            if (js == null) {
-                throw new IOException("Cannot get JavaSource for: " + uri);
-            }
-            js.runUserActionTask(info -> {
-                info.toPhase(JavaSource.Phase.RESOLVED);
-                TypeElement origin = (TypeElement) gson.fromJson(gson.toJson(type.getUserData()), ElementData.class).resolve(info);
-                VariableElement field = (VariableElement) gson.fromJson(gson.toJson(selectedField.getUserData()), ElementData.class).resolve(info);
-                if (origin != null && field != null) {
-                    final ElementUtilities eu = info.getElementUtilities();
-                    final Trees trees = info.getTrees();
-                    final Scope scope = info.getTreeUtilities().scopeFor(offset);
-                    ElementUtilities.ElementAcceptor acceptor = new ElementUtilities.ElementAcceptor() {
-                        @Override
-                        public boolean accept(Element e, TypeMirror type) {
-                            if (e.getKind() == ElementKind.METHOD && trees.isAccessible(scope, e, (DeclaredType)type)) {
-                                Element impl = eu.getImplementationOf((ExecutableElement)e, origin);
-                                return impl == null || (!impl.getModifiers().contains(Modifier.FINAL) && impl.getEnclosingElement() != origin);
-                            }
-                            return false;
+    private CompletableFuture<WorkspaceEdit> selectMethods(NbCodeLanguageClient client, String uri, int offset, QuickPickItem type, QuickPickItem selectedField) throws IOException, IllegalArgumentException {
+        CompletableFuture<WorkspaceEdit> future = new CompletableFuture<>();
+        FileObject file = Utils.fromUri(uri);
+        JavaSource js = JavaSource.forFileObject(file);
+        if (js == null) {
+            throw new IOException("Cannot get JavaSource for: " + uri);
+        }
+        js.runUserActionTask(info -> {
+            info.toPhase(JavaSource.Phase.RESOLVED);
+            TypeElement origin = (TypeElement) gson.fromJson(gson.toJson(type.getUserData()), ElementData.class).resolve(info);
+            VariableElement field = (VariableElement) gson.fromJson(gson.toJson(selectedField.getUserData()), ElementData.class).resolve(info);
+            if (origin != null && field != null) {
+                final ElementUtilities eu = info.getElementUtilities();
+                final Trees trees = info.getTrees();
+                final Scope scope = info.getTreeUtilities().scopeFor(offset);
+                ElementUtilities.ElementAcceptor acceptor = new ElementUtilities.ElementAcceptor() {
+                    @Override
+                    public boolean accept(Element e, TypeMirror type) {
+                        if (e.getKind() == ElementKind.METHOD && trees.isAccessible(scope, e, (DeclaredType)type)) {
+                            Element impl = eu.getImplementationOf((ExecutableElement)e, origin);
+                            return impl == null || (!impl.getModifiers().contains(Modifier.FINAL) && impl.getEnclosingElement() != origin);
                         }
-                    };
-                    List<QuickPickItem> methods = new ArrayList<>();
-                    for (ExecutableElement method : ElementFilter.methodsIn(eu.getMembers(field.asType(), acceptor))) {
-                        QuickPickItem item = new QuickPickItem(String.format("%s.%s", field.getSimpleName().toString(), createLabel(info, method)));
-                        item.setUserData(new ElementData(method));
-                        methods.add(item);
+                        return false;
                     }
-                    client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectDelegateMethods(), true, methods)).thenAccept(selected -> {
+                };
+                List<QuickPickItem> methods = new ArrayList<>();
+                for (ExecutableElement method : ElementFilter.methodsIn(eu.getMembers(field.asType(), acceptor))) {
+                    QuickPickItem item = new QuickPickItem(String.format("%s.%s", field.getSimpleName().toString(), createLabel(info, method)));
+                    item.setUserData(new ElementData(method));
+                    methods.add(item);
+                }
+                client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectDelegateMethods(), true, methods)).thenAccept(selected -> {
+                    try {
                         if (selected != null && !selected.isEmpty()) {
-                            generate(client, uri, offset, selectedField, selected);
+                            future.complete(generate(uri, offset, selectedField, selected));
+                        } else {
+                            future.complete(null);
                         }
-                    });
-                }
-            }, true);
-        } catch (IOException | IllegalArgumentException ex) {
-            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
-        }
+                    } catch (IOException | IllegalArgumentException ex) {
+                        future.completeExceptionally(ex);
+                    }
+                });
+            } else {
+                future.complete(null);
+            }
+        }, true);
+        return future;
     }
 
-    private void generate(NbCodeLanguageClient client, String uri, int offset, QuickPickItem selectedField, List<QuickPickItem> selectedMethods) {
-        try {
-            FileObject file = Utils.fromUri(uri);
-            JavaSource js = JavaSource.forFileObject(file);
-            if (js == null) {
-                throw new IOException("Cannot get JavaSource for: " + uri);
-            }
-            List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
-                wc.toPhase(JavaSource.Phase.RESOLVED);
-                TreePath tp = wc.getTreeUtilities().pathFor(offset);
-                tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
-                if (tp != null) {
-                    VariableElement field = (VariableElement) gson.fromJson(gson.toJson(selectedField.getUserData()), ElementData.class).resolve(wc);
-                    List<ExecutableElement> methods = selectedMethods.stream().map(item -> {
-                        ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
-                        return (ExecutableElement)data.resolve(wc);
-                    }).collect(Collectors.toList());
-                    org.netbeans.modules.java.editor.codegen.DelegateMethodGenerator.generateDelegatingMethods(wc, tp, field, methods, -1);
-                }
-            });
-            client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
-        } catch (IOException | IllegalArgumentException ex) {
-            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+    private WorkspaceEdit generate(String uri, int offset, QuickPickItem selectedField, List<QuickPickItem> selectedMethods) throws IOException, IllegalArgumentException {
+        FileObject file = Utils.fromUri(uri);
+        JavaSource js = JavaSource.forFileObject(file);
+        if (js == null) {
+            throw new IOException("Cannot get JavaSource for: " + uri);
         }
+        List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+            wc.toPhase(JavaSource.Phase.RESOLVED);
+            TreePath tp = wc.getTreeUtilities().pathFor(offset);
+            tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+            if (tp != null) {
+                VariableElement field = (VariableElement) gson.fromJson(gson.toJson(selectedField.getUserData()), ElementData.class).resolve(wc);
+                List<ExecutableElement> methods = selectedMethods.stream().map(item -> {
+                    ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                    return (ExecutableElement)data.resolve(wc);
+                }).collect(Collectors.toList());
+                org.netbeans.modules.java.editor.codegen.DelegateMethodGenerator.generateDelegatingMethods(wc, tp, field, methods, -1);
+            }
+        });
+        return edits.isEmpty() ? null : new WorkspaceEdit(Collections.singletonMap(uri, edits));
     }
 }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/EqualsHashCodeGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/EqualsHashCodeGenerator.java
index 67a981b..2554aa2 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/EqualsHashCodeGenerator.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/EqualsHashCodeGenerator.java
@@ -19,15 +19,17 @@
 package org.netbeans.modules.java.lsp.server.protocol;
 
 import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSyntaxException;
 import com.sun.source.tree.Tree;
 import com.sun.source.util.TreePath;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashSet;
+import java.util.HashMap;
 import java.util.List;
-import java.util.Set;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 import javax.lang.model.element.ElementKind;
@@ -36,12 +38,9 @@ import javax.lang.model.element.Modifier;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.VariableElement;
 import javax.lang.model.util.ElementFilter;
-import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
 import org.eclipse.lsp4j.CodeAction;
 import org.eclipse.lsp4j.CodeActionKind;
 import org.eclipse.lsp4j.CodeActionParams;
-import org.eclipse.lsp4j.MessageParams;
-import org.eclipse.lsp4j.MessageType;
 import org.eclipse.lsp4j.TextEdit;
 import org.eclipse.lsp4j.WorkspaceEdit;
 import org.netbeans.api.java.source.CompilationController;
@@ -59,16 +58,15 @@ import org.openide.util.lookup.ServiceProvider;
 @ServiceProvider(service = CodeActionsProvider.class, position = 40)
 public final class EqualsHashCodeGenerator extends CodeActionsProvider {
 
-    public static final String GENERATE_EQUALS =  "java.generate.equals";
-    public static final String GENERATE_HASH_CODE =  "java.generate.hashCode";
-    public static final String GENERATE_EQUALS_HASH_CODE =  "java.generate.equals.hashCode";
+    private static final String KIND =  "kind";
+    private static final String URI =  "uri";
+    private static final String OFFSET =  "offset";
+    private static final String FIELDS =  "fields";
+    private static final int EQUALS_ONLY = 1;
+    private static final int HASH_CODE_ONLY = 2;
 
-    private final Set<String> commands = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(GENERATE_EQUALS_HASH_CODE, GENERATE_EQUALS, GENERATE_HASH_CODE)));
     private final Gson gson = new Gson();
 
-    public EqualsHashCodeGenerator() {
-    }
-
     @Override
     @NbBundle.Messages({
         "DN_GenerateEquals=Generate equals()...",
@@ -113,16 +111,11 @@ public final class EqualsHashCodeGenerator extends CodeActionsProvider {
         String uri = Utils.toUri(info.getFileObject());
         if (equalsHashCode[0] == null) {
             if (equalsHashCode[1] == null) {
-                return Collections.singletonList(createCodeAction(Bundle.DN_GenerateEqualsHashCode(), CODE_GENERATOR_KIND, GENERATE_EQUALS_HASH_CODE, uri, offset, fields));
+                return Collections.singletonList(createCodeAction(Bundle.DN_GenerateEqualsHashCode(), CODE_GENERATOR_KIND, data(0, uri, offset, fields), null));
             }
-            return Collections.singletonList(createCodeAction(Bundle.DN_GenerateEquals(), CODE_GENERATOR_KIND, GENERATE_EQUALS, uri, offset, fields));
+            return Collections.singletonList(createCodeAction(Bundle.DN_GenerateEquals(), CODE_GENERATOR_KIND, data(EQUALS_ONLY, uri, offset, fields), null));
         }
-        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateHashCode(), CODE_GENERATOR_KIND, GENERATE_HASH_CODE, uri, offset, fields));
-    }
-
-    @Override
-    public Set<String> getCommands() {
-        return commands;
+        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateHashCode(), CODE_GENERATOR_KIND, data(HASH_CODE_ONLY, uri, offset, fields), null));
     }
 
     @Override
@@ -131,22 +124,24 @@ public final class EqualsHashCodeGenerator extends CodeActionsProvider {
         "DN_SelectHashCode=Select fields to be included in hashCode()",
         "DN_SelectEqualsHashCode=Select fields to be included in equals() and hashCode()",
     })
-    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
-        if (arguments.size() > 2) {
-            String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
-            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
-            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem[].class));
+    public CompletableFuture<CodeAction> resolve(NbCodeLanguageClient client, CodeAction codeAction, Object data) {
+        CompletableFuture<CodeAction> future = new CompletableFuture<>();
+        try {
+            int kind = ((JsonObject) data).getAsJsonPrimitive(KIND).getAsInt();
+            String uri = ((JsonObject) data).getAsJsonPrimitive(URI).getAsString();
+            int offset = ((JsonObject) data).getAsJsonPrimitive(OFFSET).getAsInt();
+            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(((JsonObject) data).get(FIELDS), QuickPickItem[].class));
             String text;
-            boolean generateEquals = !GENERATE_HASH_CODE.equals(command);
-            boolean generateHashCode = !GENERATE_EQUALS.equals(command);
-            switch (command) {
-                case GENERATE_EQUALS: text = Bundle.DN_SelectEquals(); break;
-                case GENERATE_HASH_CODE: text = Bundle.DN_SelectHashCode(); break;
+            boolean generateEquals = HASH_CODE_ONLY != kind;
+            boolean generateHashCode = EQUALS_ONLY != kind;
+            switch (kind) {
+                case EQUALS_ONLY: text = Bundle.DN_SelectEquals(); break;
+                case HASH_CODE_ONLY: text = Bundle.DN_SelectHashCode(); break;
                 default: text = Bundle.DN_SelectEqualsHashCode(); break;
             }
             client.showQuickPick(new ShowQuickPickParams(text, true, fields)).thenAccept(selected -> {
-                if (selected != null) {
-                    try {
+                try {
+                    if (selected != null) {
                         FileObject file = Utils.fromUri(uri);
                         JavaSource js = JavaSource.forFileObject(file);
                         if (js == null) {
@@ -158,21 +153,33 @@ public final class EqualsHashCodeGenerator extends CodeActionsProvider {
                             tp = wc.getTreeUtilities().getPathElementOfKind(Tree.Kind.CLASS, tp);
                             if (tp != null) {
                                 List<VariableElement> selectedFields = selected.stream().map(item -> {
-                                    ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
-                                    return (VariableElement)data.resolve(wc);
+                                    ElementData userData = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                                    return (VariableElement) userData.resolve(wc);
                                 }).collect(Collectors.toList());
                                 org.netbeans.modules.java.editor.codegen.EqualsHashCodeGenerator.generateEqualsAndHashCode(wc, tp, generateEquals ? selectedFields : null, generateHashCode ? selectedFields : null, -1);
                             }
                         });
-                        client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
-                    } catch (IOException | IllegalArgumentException ex) {
-                        client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+                        if (!edits.isEmpty()) {
+                            codeAction.setEdit(new WorkspaceEdit(Collections.singletonMap(uri, edits)));
+                        }
                     }
+                    future.complete(codeAction);
+                } catch (IOException | IllegalArgumentException ex) {
+                    future.completeExceptionally(ex);
                 }
             });
-        } else {
-            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        } catch(JsonSyntaxException ex) {
+            future.completeExceptionally(ex);
         }
-        return CompletableFuture.completedFuture(true);
+        return future;
+    }
+
+    private static Map<String, Object> data(int kind, String uri, int offset, List<QuickPickItem> fields) {
+        Map<String, Object> data = new HashMap<>();
+        data.put(KIND, kind);
+        data.put(URI, uri);
+        data.put(OFFSET, offset);
+        data.put(FIELDS, fields);
+        return data;
     }
 }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ExtractSuperclassOrInterfaceRefactoring.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ExtractSuperclassOrInterfaceRefactoring.java
index 87d4807..e1e09ec 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ExtractSuperclassOrInterfaceRefactoring.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ExtractSuperclassOrInterfaceRefactoring.java
@@ -146,10 +146,10 @@ public final class ExtractSuperclassOrInterfaceRefactoring extends CodeRefactori
             QuickPickItem elementItem = new QuickPickItem(createLabel(info, type));
             elementItem.setUserData(new ElementData(type));
             if (!type.getKind().isInterface()) {
-                result.add(createCodeAction(Bundle.DN_ExtractSuperclass(), CodeActionKind.RefactorExtract, EXTRACT_SUPERCLASS_REFACTORING_COMMAND, uri, elementItem, allMembers));
+                result.add(createCodeAction(Bundle.DN_ExtractSuperclass(), CodeActionKind.RefactorExtract, null, EXTRACT_SUPERCLASS_REFACTORING_COMMAND, uri, elementItem, allMembers));
             }
             if (!members.isEmpty()) {
-                result.add(createCodeAction(Bundle.DN_ExtractInterface(), CodeActionKind.RefactorExtract, EXTRACT_INTERFACE_REFACTORING_COMMAND, uri, elementItem, members));
+                result.add(createCodeAction(Bundle.DN_ExtractInterface(), CodeActionKind.RefactorExtract, null, EXTRACT_INTERFACE_REFACTORING_COMMAND, uri, elementItem, members));
             }
         }
         return result;
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/GetterSetterGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/GetterSetterGenerator.java
index 9841873..c4e3928 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/GetterSetterGenerator.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/GetterSetterGenerator.java
@@ -19,6 +19,8 @@
 package org.netbeans.modules.java.lsp.server.protocol;
 
 import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSyntaxException;
 import com.sun.source.tree.ClassTree;
 import com.sun.source.tree.Tree;
 import com.sun.source.util.TreePath;
@@ -26,9 +28,11 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
@@ -36,12 +40,9 @@ import javax.lang.model.element.Modifier;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.VariableElement;
 import javax.lang.model.util.ElementFilter;
-import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
 import org.eclipse.lsp4j.CodeAction;
 import org.eclipse.lsp4j.CodeActionKind;
 import org.eclipse.lsp4j.CodeActionParams;
-import org.eclipse.lsp4j.MessageParams;
-import org.eclipse.lsp4j.MessageType;
 import org.eclipse.lsp4j.Range;
 import org.eclipse.lsp4j.TextEdit;
 import org.eclipse.lsp4j.WorkspaceEdit;
@@ -66,16 +67,14 @@ import org.openide.util.lookup.ServiceProvider;
 @ServiceProvider(service = CodeActionsProvider.class, position = 30)
 public final class GetterSetterGenerator extends CodeActionsProvider {
 
-    public static final String GENERATE_GETTERS =  "java.generate.getters";
-    public static final String GENERATE_SETTERS =  "java.generate.setters";
-    public static final String GENERATE_GETTERS_SETTERS =  "java.generate.getters.setters";
+    private static final String KIND =  "kind";
+    private static final String URI =  "uri";
+    private static final String OFFSET =  "offset";
+    private static final String ALL =  "all";
+    private static final String FIELDS =  "fields";
 
-    private final Set<String> commands = Collections.unmodifiableSet(new HashSet(Arrays.asList(GENERATE_GETTERS, GENERATE_SETTERS, GENERATE_GETTERS_SETTERS)));
     private final Gson gson = new Gson();
 
-    public GetterSetterGenerator() {
-    }
-
     @Override
     @NbBundle.Messages({
         "DN_GenerateGetters=Generate Getters...",
@@ -101,94 +100,98 @@ public final class GetterSetterGenerator extends CodeActionsProvider {
         List<CodeAction> result = new ArrayList<>();
         if (missingGetters) {
             String name = pair.first().size() == 1 ? Bundle.DN_GenerateGetterFor(pair.first().iterator().next().getSimpleName().toString()) : Bundle.DN_GenerateGetters();
-            result.add(createCodeAction(name, all ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, GENERATE_GETTERS, uri, offset, all, pair.first().stream().map(variableElement -> {
+            result.add(createCodeAction(name, all ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, data(GeneratorUtils.GETTERS_ONLY, uri, offset, all, pair.first().stream().map(variableElement -> {
                 QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
                 item.setUserData(new ElementData(variableElement));
                 return item;
-            }).collect(Collectors.toList())));
+            }).collect(Collectors.toList())), null));
         }
         if (missingSetters) {
             String name = pair.second().size() == 1 ? Bundle.DN_GenerateSetterFor(pair.second().iterator().next().getSimpleName().toString()) : Bundle.DN_GenerateSetters();
-            result.add(createCodeAction(name, all ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, GENERATE_SETTERS, uri, offset, all, pair.second().stream().map(variableElement -> {
+            result.add(createCodeAction(name, all ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, data(GeneratorUtils.SETTERS_ONLY, uri, offset, all, pair.second().stream().map(variableElement -> {
                 QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
                 item.setUserData(new ElementData(variableElement));
                 return item;
-            }).collect(Collectors.toList())));
+            }).collect(Collectors.toList())), null));
         }
         if (missingGetters && missingSetters) {
             pair.first().retainAll(pair.second());
             String name = pair.first().size() == 1 ? Bundle.DN_GenerateGetterSetterFor(pair.first().iterator().next().getSimpleName().toString()) : Bundle.DN_GenerateGettersSetters();
-            result.add(createCodeAction(name, all ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, GENERATE_GETTERS_SETTERS, uri, offset, all, pair.first().stream().map(variableElement -> {
+            result.add(createCodeAction(name, all ? CODE_GENERATOR_KIND : CodeActionKind.QuickFix, data(0, uri, offset, all, pair.first().stream().map(variableElement -> {
                 QuickPickItem item = new QuickPickItem(createLabel(info, variableElement));
                 item.setUserData(new ElementData(variableElement));
                 return item;
-            }).collect(Collectors.toList())));
+            }).collect(Collectors.toList())), null));
         }
         return result;
     }
 
     @Override
-    public Set<String> getCommands() {
-        return commands;
-    }
-
-    @Override
     @NbBundle.Messages({
         "DN_SelectGetters=Select fields to generate getters for",
         "DN_SelectSetters=Select fields to generate setters for",
         "DN_SelectGettersSetters=Select fields to generate getters and setters for",
     })
-    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
-        if (arguments.size() > 3) {
-            String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
-            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
-            boolean all = gson.fromJson(gson.toJson(arguments.get(2)), boolean.class);
-            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(3)), QuickPickItem[].class));
-            int kind;
+    public CompletableFuture<CodeAction> resolve(NbCodeLanguageClient client, CodeAction codeAction, Object data) {
+        CompletableFuture<CodeAction> future = new CompletableFuture<>();
+        try {
+            int kind = ((JsonObject) data).getAsJsonPrimitive(KIND).getAsInt();
+            String uri = ((JsonObject) data).getAsJsonPrimitive(URI).getAsString();
+            int offset = ((JsonObject) data).getAsJsonPrimitive(OFFSET).getAsInt();
+            boolean all = ((JsonObject) data).getAsJsonPrimitive(ALL).getAsBoolean();
+            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(((JsonObject) data).get(FIELDS), QuickPickItem[].class));
             String text;
-            switch (command) {
-                case GENERATE_GETTERS: kind = GeneratorUtils.GETTERS_ONLY; text = Bundle.DN_SelectGetters(); break;
-                case GENERATE_SETTERS: kind = GeneratorUtils.SETTERS_ONLY; text = Bundle.DN_SelectSetters(); break;
-                default: kind = 0; text = Bundle.DN_SelectGettersSetters(); break;
+            switch (kind) {
+                case GeneratorUtils.GETTERS_ONLY: text = Bundle.DN_SelectGetters(); break;
+                case GeneratorUtils.SETTERS_ONLY: text = Bundle.DN_SelectSetters(); break;
+                default: text = Bundle.DN_SelectGettersSetters(); break;
             }
             if (all && fields.size() > 1) {
                 client.showQuickPick(new ShowQuickPickParams(text, true, fields)).thenAccept(selected -> {
-                    if (selected != null && !selected.isEmpty()) {
-                        generate(client, kind, uri, offset, selected);
+                    try {
+                        if (selected != null && !selected.isEmpty()) {
+                            WorkspaceEdit edit = generate(kind, uri, offset, selected);
+                            if (edit != null) {
+                                codeAction.setEdit(edit);
+                            }
+                        }
+                        future.complete(codeAction);
+                    } catch (IOException | IllegalArgumentException ex) {
+                        future.completeExceptionally(ex);
                     }
                 });
-            } else if (fields.size() == 1) {
-                generate(client, kind, uri, offset, fields);
+            } else {
+                WorkspaceEdit edit = generate(kind, uri, offset, fields);
+                if (edit != null) {
+                    codeAction.setEdit(edit);
+                }
+                future.complete(codeAction);
             }
-        } else {
-            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        } catch(JsonSyntaxException | IOException | IllegalArgumentException ex) {
+            future.completeExceptionally(ex);
         }
-        return CompletableFuture.completedFuture(true);
+        return future;
     }
 
-    private void generate(NbCodeLanguageClient client, int kind, String uri, int offset, List<QuickPickItem> fields) throws IllegalArgumentException {
-        try {
-            FileObject file = Utils.fromUri(uri);
-            JavaSource js = JavaSource.forFileObject(file);
-            if (js == null) {
-                throw new IOException("Cannot get JavaSource for: " + uri);
-            }
-            List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
-                wc.toPhase(JavaSource.Phase.RESOLVED);
-                TreePath tp = wc.getTreeUtilities().pathFor(offset);
-                tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
-                if (tp != null) {
-                    List<VariableElement> variableElements = fields.stream().map(item -> {
-                        ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
-                        return (VariableElement) data.resolve(wc);
-                    }).collect(Collectors.toList());
-                    GeneratorUtils.generateGettersAndSetters(wc, tp, variableElements, kind, -1);
-                }
-            });
-            client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
-        } catch (IOException ex) {
-            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+    private WorkspaceEdit generate(int kind, String uri, int offset, List<QuickPickItem> fields) throws IOException, IllegalArgumentException {
+        FileObject file = Utils.fromUri(uri);
+        JavaSource js = JavaSource.forFileObject(file);
+        if (js == null) {
+            throw new IOException("Cannot get JavaSource for: " + uri);
         }
+        List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+            wc.toPhase(JavaSource.Phase.RESOLVED);
+            TreePath tp = wc.getTreeUtilities().pathFor(offset);
+            tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+            if (tp != null) {
+                List<VariableElement> variableElements = fields.stream().map(item -> {
+                    ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                    return (VariableElement) data.resolve(wc);
+                }).collect(Collectors.toList());
+                GeneratorUtils.generateGettersAndSetters(wc, tp, variableElements, kind, -1);
+            }
+        });
+        return edits.isEmpty() ? null : new WorkspaceEdit(Collections.singletonMap(uri, edits));
     }
 
     private static Pair<Set<VariableElement>, Set<VariableElement>> findMissingGettersSetters(CompilationInfo info, Range range, boolean all) {
@@ -254,4 +257,14 @@ public final class GetterSetterGenerator extends CodeActionsProvider {
 
         return Pair.of(missingGetters, missingSetters);
     }
+
+    private static Map<String, Object> data(int kind, String uri, int offset, boolean all, List<QuickPickItem> fields) {
+        Map<String, Object> data = new HashMap<>();
+        data.put(KIND, kind);
+        data.put(URI, uri);
+        data.put(OFFSET, offset);
+        data.put(ALL, all);
+        data.put(FIELDS, fields);
+        return data;
+    }
 }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ImplementOverrideMethodGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ImplementOverrideMethodGenerator.java
index 59d0c83..8a398f4 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ImplementOverrideMethodGenerator.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ImplementOverrideMethodGenerator.java
@@ -19,14 +19,16 @@
 package org.netbeans.modules.java.lsp.server.protocol;
 
 import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSyntaxException;
 import com.sun.source.util.TreePath;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashSet;
+import java.util.HashMap;
 import java.util.List;
-import java.util.Set;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 import javax.lang.model.SourceVersion;
@@ -35,12 +37,9 @@ import javax.lang.model.element.ElementKind;
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.Modifier;
 import javax.lang.model.element.TypeElement;
-import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
 import org.eclipse.lsp4j.CodeAction;
 import org.eclipse.lsp4j.CodeActionKind;
 import org.eclipse.lsp4j.CodeActionParams;
-import org.eclipse.lsp4j.MessageParams;
-import org.eclipse.lsp4j.MessageType;
 import org.eclipse.lsp4j.TextEdit;
 import org.eclipse.lsp4j.WorkspaceEdit;
 import org.netbeans.api.java.source.CompilationController;
@@ -61,15 +60,13 @@ import org.openide.util.lookup.ServiceProvider;
 @ServiceProvider(service = CodeActionsProvider.class, position = 70)
 public final class ImplementOverrideMethodGenerator extends CodeActionsProvider {
 
-    public static final String GENERATE_IMPLEMENT_METHOD =  "java.generate.implementMethod";
-    public static final String GENERATE_OVERRIDE_METHOD =  "java.generate.overrideMethod";
+    private static final String URI =  "uri";
+    private static final String OFFSET =  "offset";
+    private static final String IS_IMPLEMET =  "isImplement";
+    private static final String METHODS =  "methods";
 
-    private final Set<String> commands = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(GENERATE_IMPLEMENT_METHOD, GENERATE_OVERRIDE_METHOD)));
     private final Gson gson = new Gson();
 
-    public ImplementOverrideMethodGenerator() {
-    }
-
     @Override
     @NbBundle.Messages({
         "DN_GenerateImplementMethod=Generate Implement Method...",
@@ -108,7 +105,7 @@ public final class ImplementOverrideMethodGenerator extends CodeActionsProvider
                 implementMethods.add(new QuickPickItem(createLabel(info, method), enclosingTypeName, null, mustImplement, new ElementData(method)));
             }
             if (!implementMethods.isEmpty()) {
-                result.add(createCodeAction(Bundle.DN_GenerateImplementMethod(), CODE_GENERATOR_KIND, GENERATE_IMPLEMENT_METHOD, uri, offset, implementMethods));
+                result.add(createCodeAction(Bundle.DN_GenerateImplementMethod(), CODE_GENERATOR_KIND, data(uri, offset, true, implementMethods), null));
             }
         }
         if (typeElement.getKind().isClass() || typeElement.getKind().isInterface()) {
@@ -124,66 +121,75 @@ public final class ImplementOverrideMethodGenerator extends CodeActionsProvider
                 overrideMethods.add(item);
             }
             if (!overrideMethods.isEmpty()) {
-                result.add(createCodeAction(Bundle.DN_GenerateOverrideMethod(), CODE_GENERATOR_KIND, GENERATE_OVERRIDE_METHOD, uri, offset, overrideMethods));
+                result.add(createCodeAction(Bundle.DN_GenerateOverrideMethod(), CODE_GENERATOR_KIND, data (uri, offset, false, overrideMethods), null));
             }
         }
         return result;
     }
 
     @Override
-    public Set<String> getCommands() {
-        return commands;
-    }
-
-    @Override
     @NbBundle.Messages({
         "DN_SelectImplementMethod=Select methods to implement",
         "DN_SelectOverrideMethod=Select methods to override",
     })
-    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
-        if (arguments.size() > 2) {
-            String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
-            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
-            List<QuickPickItem> methods = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem[].class));
-            String text = command == GENERATE_IMPLEMENT_METHOD ? Bundle.DN_SelectImplementMethod() : Bundle.DN_SelectOverrideMethod();
-            boolean isImplement = command == GENERATE_IMPLEMENT_METHOD;
+    public CompletableFuture<CodeAction> resolve(NbCodeLanguageClient client, CodeAction codeAction, Object data) {
+        CompletableFuture<CodeAction> future = new CompletableFuture<>();
+        try {
+            String uri = ((JsonObject) data).getAsJsonPrimitive(URI).getAsString();
+            int offset = ((JsonObject) data).getAsJsonPrimitive(OFFSET).getAsInt();
+            boolean isImplement = ((JsonObject) data).getAsJsonPrimitive(IS_IMPLEMET).getAsBoolean();
+            List<QuickPickItem> methods = Arrays.asList(gson.fromJson(((JsonObject) data).get(METHODS), QuickPickItem[].class));
+            String text = isImplement ? Bundle.DN_SelectImplementMethod() : Bundle.DN_SelectOverrideMethod();
             client.showQuickPick(new ShowQuickPickParams(text, true, methods)).thenAccept(selected -> {
-                if (selected != null && !selected.isEmpty()) {
-                    generate(client, uri, offset, isImplement, selected);
+                try {
+                    if (selected != null && !selected.isEmpty()) {
+                        WorkspaceEdit edit = generate(uri, offset, isImplement, selected);
+                        if (edit != null) {
+                            codeAction.setEdit(edit);
+                        }
+                    }
+                    future.complete(codeAction);
+                } catch (IOException | IllegalArgumentException ex) {
+                    future.completeExceptionally(ex);
                 }
             });
-        } else {
-            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        } catch(JsonSyntaxException ex) {
+            future.completeExceptionally(ex);
         }
-        return CompletableFuture.completedFuture(true);
+        return future;
     }
 
-    private void generate(NbCodeLanguageClient client, String uri, int offset, boolean isImplement, List<QuickPickItem> methods) {
-        try {
-            FileObject file = Utils.fromUri(uri);
-            JavaSource js = JavaSource.forFileObject(file);
-            if (js == null) {
-                throw new IOException("Cannot get JavaSource for: " + uri);
-            }
-            List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
-                wc.toPhase(JavaSource.Phase.RESOLVED);
-                TreePath tp = wc.getTreeUtilities().pathFor(offset);
-                tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
-                if (tp != null) {
-                    List<ExecutableElement> selectedMethods = methods.stream().map(item -> {
-                        ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
-                        return (ExecutableElement)data.resolve(wc);
-                    }).collect(Collectors.toList());
-                    if (isImplement) {
-                        GeneratorUtils.generateAbstractMethodImplementations(wc, tp, selectedMethods, -1);
-                    } else {
-                        GeneratorUtils.generateMethodOverrides(wc, tp, selectedMethods, -1);
-                    }
-                }
-            });
-            client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
-        } catch (IOException | IllegalArgumentException ex) {
-            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+    private WorkspaceEdit generate(String uri, int offset, boolean isImplement, List<QuickPickItem> methods) throws IOException, IllegalArgumentException {
+        FileObject file = Utils.fromUri(uri);
+        JavaSource js = JavaSource.forFileObject(file);
+        if (js == null) {
+            throw new IOException("Cannot get JavaSource for: " + uri);
         }
+        List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+            wc.toPhase(JavaSource.Phase.RESOLVED);
+            TreePath tp = wc.getTreeUtilities().pathFor(offset);
+            tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+            if (tp != null) {
+                List<ExecutableElement> selectedMethods = methods.stream().map(item -> {
+                    ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                    return (ExecutableElement)data.resolve(wc);
+                }).collect(Collectors.toList());
+                if (isImplement) {
+                    GeneratorUtils.generateAbstractMethodImplementations(wc, tp, selectedMethods, -1);
+                } else {
+                    GeneratorUtils.generateMethodOverrides(wc, tp, selectedMethods, -1);
+                }
+            }
+        });
+        return edits == null ? null : new WorkspaceEdit(Collections.singletonMap(uri, edits));
+    }
+
+    private static Map<String, Object> data(String uri, int offset, boolean isImplement, List<QuickPickItem> methods) {
+        Map<String, Object> data = new HashMap<>();
+        data.put(URI, uri);
+        data.put(OFFSET, offset);
+        data.put(IS_IMPLEMET, isImplement);
+        data.put(METHODS, methods);
+        return data;
     }
 }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LoggerGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LoggerGenerator.java
index 824838d..14220ac 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LoggerGenerator.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LoggerGenerator.java
@@ -19,14 +19,17 @@
 package org.netbeans.modules.java.lsp.server.protocol;
 
 import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSyntaxException;
 import com.sun.source.tree.ClassTree;
 import com.sun.source.tree.VariableTree;
 import com.sun.source.util.TreePath;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.EnumSet;
+import java.util.HashMap;
 import java.util.List;
-import java.util.Set;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.logging.Logger;
 import javax.lang.model.element.Modifier;
@@ -36,12 +39,9 @@ import javax.lang.model.type.DeclaredType;
 import javax.lang.model.type.TypeKind;
 import javax.lang.model.type.TypeMirror;
 import javax.lang.model.util.ElementFilter;
-import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
 import org.eclipse.lsp4j.CodeAction;
 import org.eclipse.lsp4j.CodeActionKind;
 import org.eclipse.lsp4j.CodeActionParams;
-import org.eclipse.lsp4j.MessageParams;
-import org.eclipse.lsp4j.MessageType;
 import org.eclipse.lsp4j.TextEdit;
 import org.eclipse.lsp4j.WorkspaceEdit;
 import org.netbeans.api.java.source.CompilationController;
@@ -62,9 +62,9 @@ import org.openide.util.lookup.ServiceProvider;
 @ServiceProvider(service = CodeActionsProvider.class, position = 20)
 public final class LoggerGenerator extends CodeActionsProvider {
 
-    public static final String GENERATE_LOGGER =  "java.generate.logger";
+    private static final String URI =  "uri";
+    private static final String OFFSET =  "offset";
 
-    private final Set<String> commands = Collections.singleton(GENERATE_LOGGER);
     private final Gson gson = new Gson();
 
     public LoggerGenerator() {
@@ -102,25 +102,24 @@ public final class LoggerGenerator extends CodeActionsProvider {
             }
         }
         String uri = Utils.toUri(info.getFileObject());
-        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateLogger(), CODE_GENERATOR_KIND, GENERATE_LOGGER, uri, offset));
-    }
-
-    @Override
-    public Set<String> getCommands() {
-        return commands;
+        Map<String, Object> data = new HashMap<>();
+        data.put(URI, uri);
+        data.put(OFFSET, offset);
+        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateLogger(), CODE_GENERATOR_KIND, data, null));
     }
 
     @Override
     @NbBundle.Messages({
         "DN_SelectLoggerName=Logger field name",
     })
-    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
-        if (arguments.size() > 1) {
-            String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
-            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
+    public CompletableFuture<CodeAction> resolve(NbCodeLanguageClient client, CodeAction codeAction, Object data) {
+        CompletableFuture<CodeAction> future = new CompletableFuture<>();
+        try {
+            String uri = ((JsonObject) data).getAsJsonPrimitive(URI).getAsString();
+            int offset = ((JsonObject) data).getAsJsonPrimitive(OFFSET).getAsInt();
             client.showInputBox(new ShowInputBoxParams(Bundle.DN_SelectLoggerName(), "LOG")).thenAccept(value -> {
-                if (value != null && BaseUtilities.isJavaIdentifier(value)) {
-                    try {
+                try {
+                    if (value != null && BaseUtilities.isJavaIdentifier(value)) {
                         FileObject file = Utils.fromUri(uri);
                         JavaSource js = JavaSource.forFileObject(file);
                         if (js == null) {
@@ -136,15 +135,18 @@ public final class LoggerGenerator extends CodeActionsProvider {
                                 wc.rewrite(cls, GeneratorUtilities.get(wc).insertClassMember(cls, field));
                             }
                         });
-                        client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
-                    } catch (IOException | IllegalArgumentException ex) {
-                        client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+                        if (!edits.isEmpty()) {
+                            codeAction.setEdit(new WorkspaceEdit(Collections.singletonMap(uri, edits)));
+                        }
                     }
+                    future.complete(codeAction);
+                } catch (IOException | IllegalArgumentException ex) {
+                    future.completeExceptionally(ex);
                 }
             });
-        } else {
-            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        } catch (JsonSyntaxException ex) {
+            future.completeExceptionally(ex);
         }
-        return CompletableFuture.completedFuture(true);
+        return future;
     }
 }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/MoveRefactoring.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/MoveRefactoring.java
index 9aa18d7..51475e6 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/MoveRefactoring.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/MoveRefactoring.java
@@ -98,9 +98,9 @@ public final class MoveRefactoring extends CodeRefactoring {
         if (element != null) {
             QuickPickItem elementItem = new QuickPickItem(createLabel(info, element));
             elementItem.setUserData(new ElementData(element));
-            return Collections.singletonList(createCodeAction(Bundle.DN_Move(), MOVE_REFACTORING_KIND, MOVE_REFACTORING_COMMAND, uri, elementItem));
+            return Collections.singletonList(createCodeAction(Bundle.DN_Move(), MOVE_REFACTORING_KIND, null, MOVE_REFACTORING_COMMAND, uri, elementItem));
         } else {
-            return Collections.singletonList(createCodeAction(Bundle.DN_Move(), MOVE_REFACTORING_KIND, MOVE_REFACTORING_COMMAND, uri));
+            return Collections.singletonList(createCodeAction(Bundle.DN_Move(), MOVE_REFACTORING_KIND, null, MOVE_REFACTORING_COMMAND, uri));
         }
     }
 
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/OrganizeImportsCodeAction.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/OrganizeImportsCodeAction.java
new file mode 100644
index 0000000..c16187c
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/OrganizeImportsCodeAction.java
@@ -0,0 +1,87 @@
+/*
+ * 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.java.lsp.server.protocol;
+
+import com.google.gson.JsonPrimitive;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import org.eclipse.lsp4j.CodeAction;
+import org.eclipse.lsp4j.CodeActionKind;
+import org.eclipse.lsp4j.CodeActionParams;
+import org.eclipse.lsp4j.TextEdit;
+import org.eclipse.lsp4j.WorkspaceEdit;
+import org.netbeans.api.java.source.CompilationController;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.modules.java.hints.OrganizeImports;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.netbeans.modules.parsing.api.ResultIterator;
+import org.openide.filesystems.FileObject;
+import org.openide.util.NbBundle;
+import org.openide.util.lookup.ServiceProvider;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+@ServiceProvider(service = CodeActionsProvider.class, position = 90)
+public final class OrganizeImportsCodeAction extends CodeActionsProvider {
+
+    @Override
+    @NbBundle.Messages({
+        "DN_OrganizeImports=Organize Imports",
+    })
+    public List<CodeAction> getCodeActions(ResultIterator resultIterator, CodeActionParams params) throws Exception {
+        List<String> only = params.getContext().getOnly();
+        if (only == null || !only.contains(CodeActionKind.Source) && !only.contains(CodeActionKind.SourceOrganizeImports)) {
+            return Collections.emptyList();
+        }
+        CompilationController info = CompilationController.get(resultIterator.getParserResult());
+        if (info == null) {
+            return Collections.emptyList();
+        }
+        String uri = Utils.toUri(info.getFileObject());
+        return Collections.singletonList(createCodeAction(Bundle.DN_OrganizeImports(), CodeActionKind.SourceOrganizeImports, uri, null));
+    }
+
+    @Override
+    public CompletableFuture<CodeAction> resolve(NbCodeLanguageClient client, CodeAction codeAction, Object data) {
+        CompletableFuture<CodeAction> future = new CompletableFuture<>();
+        try {
+            String uri = ((JsonPrimitive) data).getAsString();
+            FileObject file = Utils.fromUri(uri);
+            JavaSource js = JavaSource.forFileObject(file);
+            if (js == null) {
+                throw new IOException("Cannot get JavaSource for: " + uri);
+            }
+            List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+                wc.toPhase(JavaSource.Phase.RESOLVED);
+                OrganizeImports.doOrganizeImports(wc, null, false);
+            });
+            if (!edits.isEmpty()) {
+                codeAction.setEdit(new WorkspaceEdit(Collections.singletonMap(uri, edits)));
+            }
+            future.complete(codeAction);
+        } catch (IOException | IllegalArgumentException ex) {
+            future.completeExceptionally(ex);
+        }
+        return future;
+    }
+}
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PullUpRefactoring.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PullUpRefactoring.java
index b146688..dea79b2 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PullUpRefactoring.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PullUpRefactoring.java
@@ -115,7 +115,7 @@ public final class PullUpRefactoring extends CodeRefactoring {
         }
         QuickPickItem elementItem = new QuickPickItem(createLabel(info, element));
         elementItem.setUserData(new ElementData(element));
-        return Collections.singletonList(createCodeAction(Bundle.DN_PullUp(), PULL_UP_REFACTORING_KIND, PULL_UP_REFACTORING_COMMAND, uri, offset, elementItem, supertypeItems));
+        return Collections.singletonList(createCodeAction(Bundle.DN_PullUp(), PULL_UP_REFACTORING_KIND, null, PULL_UP_REFACTORING_COMMAND, uri, offset, elementItem, supertypeItems));
     }
 
     @Override
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PushDownRefactoring.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PushDownRefactoring.java
index 591f071..65cc6fd 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PushDownRefactoring.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/PushDownRefactoring.java
@@ -126,7 +126,7 @@ public final class PushDownRefactoring extends CodeRefactoring {
         }
         QuickPickItem elementItem = new QuickPickItem(createLabel(info, element));
         elementItem.setUserData(new ElementData(element));
-        return Collections.singletonList(createCodeAction(Bundle.DN_PushDown(), PUSH_DOWN_REFACTORING_KIND, PUSH_DOWN_REFACTORING_COMMAND, uri, elementItem, members));
+        return Collections.singletonList(createCodeAction(Bundle.DN_PushDown(), PUSH_DOWN_REFACTORING_KIND, null, PUSH_DOWN_REFACTORING_COMMAND, uri, elementItem, members));
     }
 
     @Override
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
index f406c47..386ee69 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
@@ -43,10 +43,13 @@ import java.util.function.Function;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import com.google.gson.InstanceCreator;
+import com.google.gson.JsonObject;
 import org.eclipse.lsp4j.CodeActionKind;
 import org.eclipse.lsp4j.CodeActionOptions;
 import org.eclipse.lsp4j.CodeLensOptions;
 import org.eclipse.lsp4j.CompletionOptions;
+import org.eclipse.lsp4j.ConfigurationItem;
+import org.eclipse.lsp4j.ConfigurationParams;
 import org.eclipse.lsp4j.ExecuteCommandOptions;
 import org.eclipse.lsp4j.FoldingRangeProviderOptions;
 import org.eclipse.lsp4j.InitializeParams;
@@ -60,6 +63,7 @@ import org.eclipse.lsp4j.SemanticTokensCapabilities;
 import org.eclipse.lsp4j.ServerCapabilities;
 import org.eclipse.lsp4j.ShowMessageRequestParams;
 import org.eclipse.lsp4j.TextDocumentSyncKind;
+import org.eclipse.lsp4j.TextDocumentSyncOptions;
 import org.eclipse.lsp4j.WorkDoneProgressCancelParams;
 import org.eclipse.lsp4j.WorkDoneProgressParams;
 import org.eclipse.lsp4j.WorkspaceFolder;
@@ -280,13 +284,15 @@ public final class Server {
 
     static class LanguageServerImpl implements LanguageServer, LanguageClientAware, LspServerState {
 
+        private static final String NETBEANS_JAVA_IMPORTS = "netbeans.java.imports";
+
         // change to a greater throughput if the initialization waits on more processes than just (serialized) project open.
         private static final RequestProcessor SERVER_INIT_RP = new RequestProcessor(LanguageServerImpl.class.getName());
 
         private static final Logger LOG = Logger.getLogger(LanguageServerImpl.class.getName());
         private NbCodeClientWrapper client;
-        private final TextDocumentService textDocumentService = new TextDocumentServiceImpl(this);
-        private final WorkspaceService workspaceService = new WorkspaceServiceImpl(this);
+        private final TextDocumentServiceImpl textDocumentService = new TextDocumentServiceImpl(this);
+        private final WorkspaceServiceImpl workspaceService = new WorkspaceServiceImpl(this);
         private final InstanceContent   sessionServices = new InstanceContent();
         private final Lookup sessionLookup = new ProxyLookup(
                 new AbstractLookup(sessionServices),
@@ -610,13 +616,19 @@ public final class Server {
         private InitializeResult constructInitResponse(JavaSource src) {
             ServerCapabilities capabilities = new ServerCapabilities();
             if (src != null) {
-                capabilities.setTextDocumentSync(TextDocumentSyncKind.Incremental);
+                TextDocumentSyncOptions textDocumentSyncOptions = new TextDocumentSyncOptions();
+                textDocumentSyncOptions.setChange(TextDocumentSyncKind.Incremental);
+                textDocumentSyncOptions.setOpenClose(true);
+                textDocumentSyncOptions.setWillSaveWaitUntil(true);
+                capabilities.setTextDocumentSync(textDocumentSyncOptions);
                 CompletionOptions completionOptions = new CompletionOptions();
                 completionOptions.setResolveProvider(true);
                 completionOptions.setTriggerCharacters(Collections.singletonList("."));
                 capabilities.setCompletionProvider(completionOptions);
                 capabilities.setHoverProvider(true);
-                capabilities.setCodeActionProvider(new CodeActionOptions(Arrays.asList(CodeActionKind.QuickFix, CodeActionKind.Source, CodeActionKind.Refactor)));
+                CodeActionOptions codeActionOptions = new CodeActionOptions(Arrays.asList(CodeActionKind.QuickFix, CodeActionKind.Source, CodeActionKind.SourceOrganizeImports, CodeActionKind.Refactor));
+                codeActionOptions.setResolveProvider(true);
+                capabilities.setCodeActionProvider(codeActionOptions);
                 capabilities.setDocumentSymbolProvider(true);
                 capabilities.setDefinitionProvider(true);
                 capabilities.setTypeDefinitionProvider(true);
@@ -687,7 +699,9 @@ public final class Server {
             // chain showIndexingComplete message after initial project open.
             prjs.
                     thenApply(this::showIndexingCompleted);
-            
+
+            initializeOptions();
+
             // but complete the InitializationRequest independently of the project initialization.
             return CompletableFuture.completedFuture(
                     finishInitialization(
@@ -696,6 +710,22 @@ public final class Server {
             );
         }
 
+        private void initializeOptions() {
+            getWorkspaceProjects().thenAccept(projects -> {
+                if (projects != null && projects.length > 0) {
+                    ConfigurationItem item = new ConfigurationItem();
+                    FileObject fo = projects[0].getProjectDirectory();
+                    item.setScopeUri(Utils.toUri(fo));
+                    item.setSection(NETBEANS_JAVA_IMPORTS);
+                    client.configuration(new ConfigurationParams(Collections.singletonList(item))).thenAccept(c -> {
+                        if (c != null && !c.isEmpty() && c.get(0) instanceof JsonObject) {
+                            workspaceService.updateJavaImportPreferences(fo, (JsonObject) c.get(0));
+                        }
+                    });
+                }
+            });
+        }
+
         public CompletableFuture<Project[]> getWorkspaceProjects() {
             return workspaceProjects;
         }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/SurroundWithHint.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/SurroundWithHint.java
index 5aad8d4..5424309 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/SurroundWithHint.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/SurroundWithHint.java
@@ -134,7 +134,7 @@ public final class SurroundWithHint extends CodeActionsProvider {
                         snippet = sb.append(snippet).toString();
                     }
                     int idx = label.indexOf(' ');
-                    CodeAction codeAction = createCodeAction(Bundle.DN_SurroundWith(idx < 0 ? label : label.substring(0, idx)), CodeActionKind.RefactorRewrite, COMMAND_INSERT_SNIPPET, Collections.singletonMap(SNIPPET, snippet));
+                    CodeAction codeAction = createCodeAction(Bundle.DN_SurroundWith(idx < 0 ? label : label.substring(0, idx)), CodeActionKind.RefactorRewrite, null, COMMAND_INSERT_SNIPPET, Collections.singletonMap(SNIPPET, snippet));
                     if (!edits.isEmpty()) {
                         codeAction.setEdit(new WorkspaceEdit(Collections.singletonMap(params.getTextDocument().getUri(), edits)));
                     }
@@ -146,21 +146,11 @@ public final class SurroundWithHint extends CodeActionsProvider {
             }
         }
         if (items.size() > codeActions.size()) {
-            codeActions.add(createCodeAction(Bundle.DN_SurroundWithAll(), CodeActionKind.RefactorRewrite, COMMAND_SURROUND_WITH, items));
+            codeActions.add(createCodeAction(Bundle.DN_SurroundWithAll(), CodeActionKind.RefactorRewrite, null, COMMAND_SURROUND_WITH, items));
         }
         return codeActions;
     }
 
-    @Override
-    public Set<String> getCommands() {
-        return Collections.emptySet();
-    }
-
-    @Override
-    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
-        return CompletableFuture.completedFuture(false);
-    }
-
     private static Collection<? extends CodeTemplateFilter> getTemplateFilters(Document doc, int startOffset, int endOffset) {
         String mimeType = DocumentUtilities.getMimeType(doc);
         Collection<? extends CodeTemplateFilter.Factory> filterFactories = MimeLookup.getLookup(mimeType).lookupAll(CodeTemplateFilter.Factory.class);
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
index faa927d..a8ba7bf 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
@@ -132,6 +132,7 @@ import org.eclipse.lsp4j.TextDocumentEdit;
 import org.eclipse.lsp4j.TextEdit;
 import org.eclipse.lsp4j.TypeDefinitionParams;
 import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
+import org.eclipse.lsp4j.WillSaveTextDocumentParams;
 import org.eclipse.lsp4j.WorkspaceEdit;
 import org.eclipse.lsp4j.jsonrpc.messages.Either;
 import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
@@ -175,6 +176,7 @@ import org.netbeans.modules.java.editor.codegen.GeneratorUtils;
 import org.netbeans.modules.java.editor.options.MarkOccurencesSettings;
 import org.netbeans.modules.java.editor.overridden.ComputeOverriding;
 import org.netbeans.modules.java.editor.overridden.ElementDescription;
+import org.netbeans.modules.java.hints.OrganizeImports;
 import org.netbeans.modules.java.hints.introduce.IntroduceFixBase;
 import org.netbeans.modules.java.hints.introduce.IntroduceHint;
 import org.netbeans.modules.java.hints.introduce.IntroduceKind;
@@ -233,6 +235,7 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
     private static final String COMMAND_RUN_SINGLE = "java.run.single";         // NOI18N
     private static final String COMMAND_DEBUG_SINGLE = "java.debug.single";     // NOI18N
     private static final String NETBEANS_JAVADOC_LOAD_TIMEOUT = "netbeans.javadoc.load.timeout";// NOI18N
+    private static final String NETBEANS_JAVA_ON_SAVE_ORGANIZE_IMPORTS = "netbeans.java.onSave.organizeImports";// NOI18N
     
     private static final RequestProcessor BACKGROUND_TASKS = new RequestProcessor(TextDocumentServiceImpl.class.getName(), 1, false, false);
     private static final RequestProcessor WORKER = new RequestProcessor(TextDocumentServiceImpl.class.getName(), 1, false, false);
@@ -1001,7 +1004,23 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
         });
         return resultFuture;
     }
-                
+
+    @Override
+    public CompletableFuture<CodeAction> resolveCodeAction(CodeAction unresolved) {
+        JsonObject data = (JsonObject) unresolved.getData();
+        if (data != null) {
+            String providerClass = data.getAsJsonPrimitive(CodeActionsProvider.CODE_ACTIONS_PROVIDER_CLASS).getAsString();
+            for (CodeActionsProvider codeGenerator : Lookup.getDefault().lookupAll(CodeActionsProvider.class)) {
+                try {
+                    if (codeGenerator.getClass().getName().equals(providerClass)) {
+                        return codeGenerator.resolve(client, unresolved, data.get(CodeActionsProvider.DATA));
+                    }
+                } catch (Exception ex) {
+                }
+            }
+        }
+        return CompletableFuture.completedFuture(unresolved);
+    }
 
     @NbBundle.Messages({"# {0} - method name", "LBL_Run=Run {0}",
                         "# {0} - method name", "LBL_Debug=Debug {0}",
@@ -1443,6 +1462,30 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli
     }
 
     @Override
+    public CompletableFuture<List<TextEdit>> willSaveWaitUntil(WillSaveTextDocumentParams params) {
+        String uri = params.getTextDocument().getUri();
+        JavaSource js = getJavaSource(uri);
+        if (js == null) {
+            return CompletableFuture.completedFuture(Collections.emptyList());
+        }
+        ConfigurationItem conf = new ConfigurationItem();
+        conf.setScopeUri(uri);
+        conf.setSection(NETBEANS_JAVA_ON_SAVE_ORGANIZE_IMPORTS);
+        return client.configuration(new ConfigurationParams(Collections.singletonList(conf))).thenApply(c -> {
+            if (c != null && !c.isEmpty() && ((JsonPrimitive) c.get(0)).getAsBoolean()) {
+                try {
+                    List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+                        wc.toPhase(JavaSource.Phase.RESOLVED);
+                        OrganizeImports.doOrganizeImports(wc, null, false);
+                    });
+                    return edits;
+                } catch (IOException ex) {}
+            }
+            return Collections.emptyList();
+        });
+    }
+
+    @Override
     public void didSave(DidSaveTextDocumentParams arg0) {
         //TODO: nothing for now?
     }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ToStringGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ToStringGenerator.java
index 70dc949..f862f38 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ToStringGenerator.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ToStringGenerator.java
@@ -19,6 +19,8 @@
 package org.netbeans.modules.java.lsp.server.protocol;
 
 import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSyntaxException;
 import com.sun.source.tree.ClassTree;
 import com.sun.source.tree.MethodTree;
 import com.sun.source.util.TreePath;
@@ -26,8 +28,9 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
-import java.util.Set;
+import java.util.Map;
 import java.util.concurrent.CompletableFuture;
 import java.util.stream.Collectors;
 import javax.lang.model.element.Element;
@@ -35,12 +38,9 @@ import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.Modifier;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.element.VariableElement;
-import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
 import org.eclipse.lsp4j.CodeAction;
 import org.eclipse.lsp4j.CodeActionKind;
 import org.eclipse.lsp4j.CodeActionParams;
-import org.eclipse.lsp4j.MessageParams;
-import org.eclipse.lsp4j.MessageType;
 import org.eclipse.lsp4j.TextEdit;
 import org.eclipse.lsp4j.WorkspaceEdit;
 import org.netbeans.api.java.source.CompilationController;
@@ -60,14 +60,12 @@ import org.openide.util.lookup.ServiceProvider;
 @ServiceProvider(service = CodeActionsProvider.class, position = 50)
 public final class ToStringGenerator extends CodeActionsProvider {
 
-    public static final String GENERATE_TO_STRING =  "java.generate.toString";
+    private static final String URI =  "uri";
+    private static final String OFFSET =  "offset";
+    private static final String FIELDS =  "fields";
 
-    private final Set<String> commands = Collections.singleton(GENERATE_TO_STRING);
     private final Gson gson = new Gson();
 
-    public ToStringGenerator() {
-    }
-
     @Override
     @NbBundle.Messages({
         "DN_GenerateToString=Generate toString()...",
@@ -110,62 +108,70 @@ public final class ToStringGenerator extends CodeActionsProvider {
             }
         }
         String uri = Utils.toUri(info.getFileObject());
-        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateToString(), CODE_GENERATOR_KIND, GENERATE_TO_STRING, uri, offset, fields));
-    }
-
-    @Override
-    public Set<String> getCommands() {
-        return commands;
+        Map<String, Object> data = new HashMap<>();
+        data.put(URI, uri);
+        data.put(OFFSET, offset);
+        data.put(FIELDS, fields);
+        return Collections.singletonList(createCodeAction(Bundle.DN_GenerateToString(), CODE_GENERATOR_KIND, data, null));
     }
 
     @Override
     @NbBundle.Messages({
         "DN_SelectToString=Select fields to be included in toString()",
     })
-    public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
-        if (arguments.size() > 2) {
-            String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
-            int offset = gson.fromJson(gson.toJson(arguments.get(1)), Integer.class);
-            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(gson.toJson(arguments.get(2)), QuickPickItem[].class));
+    public CompletableFuture<CodeAction> resolve(NbCodeLanguageClient client, CodeAction codeAction, Object data) {
+        CompletableFuture<CodeAction> future = new CompletableFuture<>();
+        try {
+            String uri = ((JsonObject) data).getAsJsonPrimitive(URI).getAsString();
+            int offset = ((JsonObject) data).getAsJsonPrimitive(OFFSET).getAsInt();
+            List<QuickPickItem> fields = Arrays.asList(gson.fromJson(((JsonObject) data).get(FIELDS), QuickPickItem[].class));
             if (fields.isEmpty()) {
-                generate(client, uri, offset, fields);
+                WorkspaceEdit edit = generate(uri, offset, fields);
+                if (edit != null) {
+                    codeAction.setEdit(edit);
+                }
+                future.complete(codeAction);
             } else {
                 client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectToString(), true, fields)).thenAccept(selected -> {
-                    if (selected != null) {
-                        generate(client, uri, offset, selected);
+                    try {
+                        if (selected != null) {
+                            WorkspaceEdit edit = generate(uri, offset, fields);
+                            if (edit != null) {
+                                codeAction.setEdit(edit);
+                            }
+                        }
+                        future.complete(codeAction);
+                    } catch (IOException | IllegalArgumentException ex) {
+                        future.completeExceptionally(ex);
                     }
                 });
             }
-        } else {
-            client.logMessage(new MessageParams(MessageType.Error, String.format("Illegal number of arguments received for command: %s", command)));
+        } catch (JsonSyntaxException | IOException | IllegalArgumentException ex) {
+            future.completeExceptionally(ex);
         }
-        return CompletableFuture.completedFuture(true);
+        return future;
     }
 
-    private void generate(NbCodeLanguageClient client, String uri, int offset, List<QuickPickItem> fields) {
-        try {
-            FileObject file = Utils.fromUri(uri);
-            JavaSource js = JavaSource.forFileObject(file);
-            if (js == null) {
-                throw new IOException("Cannot get JavaSource for: " + uri);
-            }
-            List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
-                wc.toPhase(JavaSource.Phase.RESOLVED);
-                TreePath tp = wc.getTreeUtilities().pathFor(offset);
-                tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
-                if (tp != null) {
-                    ClassTree cls = (ClassTree) tp.getLeaf();
-                    List<VariableElement> selectedFields = fields.stream().map(item -> {
-                        ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
-                        return (VariableElement)data.resolve(wc);
-                    }).collect(Collectors.toList());
-                    MethodTree method = org.netbeans.modules.java.editor.codegen.ToStringGenerator.createToStringMethod(wc, selectedFields, cls.getSimpleName().toString(), true);
-                    wc.rewrite(cls, GeneratorUtilities.get(wc).insertClassMember(cls, method));
-                }
-            });
-            client.applyEdit(new ApplyWorkspaceEditParams(new WorkspaceEdit(Collections.singletonMap(uri, edits))));
-        } catch (IOException | IllegalArgumentException ex) {
-            client.logMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+    private WorkspaceEdit generate(String uri, int offset, List<QuickPickItem> fields) throws IOException, IllegalArgumentException {
+        FileObject file = Utils.fromUri(uri);
+        JavaSource js = JavaSource.forFileObject(file);
+        if (js == null) {
+            throw new IOException("Cannot get JavaSource for: " + uri);
         }
+        List<TextEdit> edits = TextDocumentServiceImpl.modify2TextEdits(js, wc -> {
+            wc.toPhase(JavaSource.Phase.RESOLVED);
+            TreePath tp = wc.getTreeUtilities().pathFor(offset);
+            tp = wc.getTreeUtilities().getPathElementOfKind(TreeUtilities.CLASS_TREE_KINDS, tp);
+            if (tp != null) {
+                ClassTree cls = (ClassTree) tp.getLeaf();
+                List<VariableElement> selectedFields = fields.stream().map(item -> {
+                    ElementData data = gson.fromJson(gson.toJson(item.getUserData()), ElementData.class);
+                    return (VariableElement)data.resolve(wc);
+                }).collect(Collectors.toList());
+                MethodTree method = org.netbeans.modules.java.editor.codegen.ToStringGenerator.createToStringMethod(wc, selectedFields, cls.getSimpleName().toString(), true);
+                wc.rewrite(cls, GeneratorUtilities.get(wc).insertClassMember(cls, method));
+            }
+        });
+        return edits.isEmpty() ? null : new WorkspaceEdit(Collections.singletonMap(uri, edits));
     }
 }
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
index eb55741..8928119 100644
--- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
@@ -44,6 +44,7 @@ import java.util.function.BiConsumer;
 import java.util.function.BiFunction;
 import java.util.function.Supplier;
 import java.util.logging.Logger;
+import java.util.prefs.Preferences;
 import java.util.stream.Collectors;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementKind;
@@ -80,6 +81,7 @@ import org.netbeans.api.project.Project;
 import org.netbeans.api.project.ProjectUtils;
 import org.netbeans.api.project.SourceGroup;
 import org.netbeans.api.project.ui.OpenProjects;
+import org.netbeans.modules.editor.indent.spi.CodeStylePreferences;
 import org.netbeans.modules.gsf.testrunner.ui.api.TestMethodController;
 import org.netbeans.modules.gsf.testrunner.ui.api.TestMethodFinder;
 import org.netbeans.modules.java.lsp.server.LspServerState;
@@ -660,8 +662,23 @@ public final class WorkspaceServiceImpl implements WorkspaceService, LanguageCli
     }
 
     @Override
-    public void didChangeConfiguration(DidChangeConfigurationParams arg0) {
-        //TODO: no real configuration right now
+    public void didChangeConfiguration(DidChangeConfigurationParams params) {
+        server.openedProjects().thenAccept(projects -> {
+            if (projects != null && projects.length > 0) {
+                updateJavaImportPreferences(projects[0].getProjectDirectory(), ((JsonObject) params.getSettings()).getAsJsonObject("netbeans").getAsJsonObject("java").getAsJsonObject("imports"));
+            }
+        });
+    }
+
+    void updateJavaImportPreferences(FileObject fo, JsonObject configuration) {
+        Preferences prefs = CodeStylePreferences.get(fo, "text/x-java").getPreferences();
+        if (prefs != null) {
+            prefs.put("importGroupsOrder", String.join(";", gson.fromJson(configuration.get("groups"), String[].class)));
+            prefs.putBoolean("allowConvertToStarImport", true);
+            prefs.putInt("countForUsingStarImport", configuration.getAsJsonPrimitive("countForUsingStarImport").getAsInt());
+            prefs.putBoolean("allowConvertToStaticStarImport", true);
+            prefs.putInt("countForUsingStaticStarImport", configuration.getAsJsonPrimitive("countForUsingStaticStarImport").getAsInt());
+        }
     }
 
     @Override
diff --git a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
index 6dd3fbe..bb18841 100644
--- a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
+++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
@@ -2818,7 +2818,6 @@ public class ServerTest extends NbTestCase {
         try (Writer w = new FileWriter(src)) {
             w.write(code);
         }
-        WorkspaceEdit[] edit = new WorkspaceEdit[1];
         Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LspClient() {
             @Override
             public void telemetryEvent(Object arg0) {
@@ -2845,8 +2844,7 @@ public class ServerTest extends NbTestCase {
 
             @Override
             public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
-                edit[0] = params.getEdit();
-                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+                throw new UnsupportedOperationException("Not supported yet.");
             }
 
         }, client.getInputStream(), client.getOutputStream());
@@ -2865,9 +2863,12 @@ public class ServerTest extends NbTestCase {
                            .filter(a -> Bundle.DN_GenerateGetterSetterFor("f2").equals(a.getTitle()))
                            .findAny();
         assertTrue(generateGetterSetter.isPresent());
-        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateGetterSetter.get().getCommand().getCommand(), generateGetterSetter.get().getCommand().getArguments())).get();
-        assertEquals(1, edit[0].getChanges().size());
-        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        CodeAction resolvedCodeAction = server.getTextDocumentService().resolveCodeAction(generateGetterSetter.get()).get();
+        assertNotNull(resolvedCodeAction);
+        WorkspaceEdit edit = resolvedCodeAction.getEdit();
+        assertNotNull(edit);
+        assertEquals(1, edit.getChanges().size());
+        List<TextEdit> fileChanges = edit.getChanges().get(uri);
         assertNotNull(fileChanges);
         assertEquals(1, fileChanges.size());
         assertEquals(new Range(new Position(6, 0),
@@ -2894,7 +2895,6 @@ public class ServerTest extends NbTestCase {
         try (Writer w = new FileWriter(src)) {
             w.write(code);
         }
-        WorkspaceEdit[] edit = new WorkspaceEdit[1];
         Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
             @Override
             public void telemetryEvent(Object arg0) {
@@ -2921,8 +2921,7 @@ public class ServerTest extends NbTestCase {
 
             @Override
             public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
-                edit[0] = params.getEdit();
-                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+                throw new UnsupportedOperationException("Not supported yet.");
             }
 
             @Override
@@ -2981,13 +2980,12 @@ public class ServerTest extends NbTestCase {
                            .filter(a -> Bundle.DN_GenerateConstructor().equals(a.getTitle()))
                            .findAny();
         assertTrue(generateConstructor.isPresent());
-        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateConstructor.get().getCommand().getCommand(), generateConstructor.get().getCommand().getArguments())).get();
-        int cnt = 0;
-        while(edit[0] == null && cnt++ < 10) {
-            Thread.sleep(1000);
-        }
-        assertEquals(1, edit[0].getChanges().size());
-        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        CodeAction resolvedCodeAction = server.getTextDocumentService().resolveCodeAction(generateConstructor.get()).get();
+        assertNotNull(resolvedCodeAction);
+        WorkspaceEdit edit = resolvedCodeAction.getEdit();
+        assertNotNull(edit);
+        assertEquals(1, edit.getChanges().size());
+        List<TextEdit> fileChanges = edit.getChanges().get(uri);
         assertNotNull(fileChanges);
         assertEquals(1, fileChanges.size());
         assertEquals(new Range(new Position(3, 0),
@@ -3010,7 +3008,6 @@ public class ServerTest extends NbTestCase {
         try (Writer w = new FileWriter(src)) {
             w.write(code);
         }
-        WorkspaceEdit[] edit = new WorkspaceEdit[1];
         Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LspClient() {
             @Override
             public void telemetryEvent(Object arg0) {
@@ -3037,8 +3034,7 @@ public class ServerTest extends NbTestCase {
 
             @Override
             public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
-                edit[0] = params.getEdit();
-                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+                throw new UnsupportedOperationException("Not supported yet.");
             }
 
         }, client.getInputStream(), client.getOutputStream());
@@ -3049,7 +3045,7 @@ public class ServerTest extends NbTestCase {
         server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
         VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
         List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(3, 0), new Position(3, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
-        assertEquals(9, codeActions.size());
+        assertEquals(10, codeActions.size());
         Optional<CodeAction> generateGetterSetter =
                 codeActions.stream()
                            .filter(Either::isRight)
@@ -3057,9 +3053,12 @@ public class ServerTest extends NbTestCase {
                            .filter(a -> Bundle.DN_GenerateGetterSetterFor("f2").equals(a.getTitle()))
                            .findAny();
         assertTrue(generateGetterSetter.isPresent());
-        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateGetterSetter.get().getCommand().getCommand(), generateGetterSetter.get().getCommand().getArguments())).get();
-        assertEquals(1, edit[0].getChanges().size());
-        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        CodeAction resolvedCodeAction = server.getTextDocumentService().resolveCodeAction(generateGetterSetter.get()).get();
+        assertNotNull(resolvedCodeAction);
+        WorkspaceEdit edit = resolvedCodeAction.getEdit();
+        assertNotNull(edit);
+        assertEquals(1, edit.getChanges().size());
+        List<TextEdit> fileChanges = edit.getChanges().get(uri);
         assertNotNull(fileChanges);
         assertEquals(1, fileChanges.size());
         assertEquals(new Range(new Position(3, 0),
@@ -3076,7 +3075,7 @@ public class ServerTest extends NbTestCase {
                      fileChanges.get(0).getNewText());
         server.getTextDocumentService().didChange(new DidChangeTextDocumentParams(id, Arrays.asList(new TextDocumentContentChangeEvent(fileChanges.get(0).getRange(), 0, fileChanges.get(0).getNewText()))));
         codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(3, 0), new Position(3, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
-        assertEquals(7, codeActions.size());
+        assertEquals(8, codeActions.size());
         Optional<CodeAction> generateGetter =
                 codeActions.stream()
                            .filter(Either::isRight)
@@ -3084,9 +3083,12 @@ public class ServerTest extends NbTestCase {
                            .filter(a -> Bundle.DN_GenerateGetterFor("f1").equals(a.getTitle()))
                            .findAny();
         assertTrue(generateGetter.isPresent());
-        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateGetter.get().getCommand().getCommand(), generateGetter.get().getCommand().getArguments())).get();
-        assertEquals(1, edit[0].getChanges().size());
-        fileChanges = edit[0].getChanges().get(uri);
+        resolvedCodeAction = server.getTextDocumentService().resolveCodeAction(generateGetter.get()).get();
+        assertNotNull(resolvedCodeAction);
+        edit = resolvedCodeAction.getEdit();
+        assertNotNull(edit);
+        assertEquals(1, edit.getChanges().size());
+        fileChanges = edit.getChanges().get(uri);
         assertNotNull(fileChanges);
         assertEquals(1, fileChanges.size());
         assertEquals(new Range(new Position(11, 0),
@@ -3108,7 +3110,6 @@ public class ServerTest extends NbTestCase {
         try (Writer w = new FileWriter(src)) {
             w.write(code);
         }
-        WorkspaceEdit[] edit = new WorkspaceEdit[1];
         Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
             @Override
             public void telemetryEvent(Object arg0) {
@@ -3135,8 +3136,7 @@ public class ServerTest extends NbTestCase {
 
             @Override
             public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
-                edit[0] = params.getEdit();
-                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+                throw new UnsupportedOperationException("Not supported yet.");
             }
 
             @Override
@@ -3187,7 +3187,7 @@ public class ServerTest extends NbTestCase {
         server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
         VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
         List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
-        assertEquals(7, codeActions.size());
+        assertEquals(8, codeActions.size());
         Optional<CodeAction> generateConstructor =
                 codeActions.stream()
                            .filter(Either::isRight)
@@ -3195,13 +3195,12 @@ public class ServerTest extends NbTestCase {
                            .filter(a -> Bundle.DN_GenerateConstructor().equals(a.getTitle()))
                            .findAny();
         assertTrue(generateConstructor.isPresent());
-        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateConstructor.get().getCommand().getCommand(), generateConstructor.get().getCommand().getArguments())).get();
-        int cnt = 0;
-        while(edit[0] == null && cnt++ < 10) {
-            Thread.sleep(1000);
-        }
-        assertEquals(1, edit[0].getChanges().size());
-        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        CodeAction resolvedCodeAction = server.getTextDocumentService().resolveCodeAction(generateConstructor.get()).get();
+        assertNotNull(resolvedCodeAction);
+        WorkspaceEdit edit = resolvedCodeAction.getEdit();
+        assertNotNull(edit);
+        assertEquals(1, edit.getChanges().size());
+        List<TextEdit> fileChanges = edit.getChanges().get(uri);
         assertNotNull(fileChanges);
         assertEquals(1, fileChanges.size());
         assertEquals(new Range(new Position(2, 0),
@@ -3239,7 +3238,6 @@ public class ServerTest extends NbTestCase {
         try (Writer w = new FileWriter(src)) {
             w.write(code);
         }
-        WorkspaceEdit[] edit = new WorkspaceEdit[1];
         Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
             @Override
             public void telemetryEvent(Object arg0) {
@@ -3266,8 +3264,7 @@ public class ServerTest extends NbTestCase {
 
             @Override
             public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
-                edit[0] = params.getEdit();
-                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+                throw new UnsupportedOperationException("Not supported yet.");
             }
 
             @Override
@@ -3318,7 +3315,7 @@ public class ServerTest extends NbTestCase {
         server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
         VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
         List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(5, 0), new Position(5, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
-        assertEquals(9, codeActions.size());
+        assertEquals(10, codeActions.size());
         Optional<CodeAction> generateEquals =
                 codeActions.stream()
                            .filter(Either::isRight)
@@ -3326,13 +3323,12 @@ public class ServerTest extends NbTestCase {
                            .filter(a -> Bundle.DN_GenerateEquals().equals(a.getTitle()))
                            .findAny();
         assertTrue(generateEquals.isPresent());
-        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateEquals.get().getCommand().getCommand(), generateEquals.get().getCommand().getArguments())).get();
-        int cnt = 0;
-        while(edit[0] == null && cnt++ < 10) {
-            Thread.sleep(1000);
-        }
-        assertEquals(1, edit[0].getChanges().size());
-        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        CodeAction resolvedCodeAction = server.getTextDocumentService().resolveCodeAction(generateEquals.get()).get();
+        assertNotNull(resolvedCodeAction);
+        WorkspaceEdit edit = resolvedCodeAction.getEdit();
+        assertNotNull(edit);
+        assertEquals(1, edit.getChanges().size());
+        List<TextEdit> fileChanges = edit.getChanges().get(uri);
         assertNotNull(fileChanges);
         assertEquals(1, fileChanges.size());
         assertEquals(new Range(new Position(13, 0),
@@ -3368,7 +3364,6 @@ public class ServerTest extends NbTestCase {
         try (Writer w = new FileWriter(src)) {
             w.write(code);
         }
-        WorkspaceEdit[] edit = new WorkspaceEdit[1];
         Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
             @Override
             public void telemetryEvent(Object arg0) {
@@ -3395,8 +3390,7 @@ public class ServerTest extends NbTestCase {
 
             @Override
             public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
-                edit[0] = params.getEdit();
-                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+                throw new UnsupportedOperationException("Not supported yet.");
             }
 
             @Override
@@ -3447,7 +3441,7 @@ public class ServerTest extends NbTestCase {
         server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
         VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
         List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
-        assertEquals(7, codeActions.size());
+        assertEquals(8, codeActions.size());
         Optional<CodeAction> generateToString =
                 codeActions.stream()
                            .filter(Either::isRight)
@@ -3455,13 +3449,12 @@ public class ServerTest extends NbTestCase {
                            .filter(a -> Bundle.DN_GenerateToString().equals(a.getTitle()))
                            .findAny();
         assertTrue(generateToString.isPresent());
-        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateToString.get().getCommand().getCommand(), generateToString.get().getCommand().getArguments())).get();
-        int cnt = 0;
-        while(edit[0] == null && cnt++ < 10) {
-            Thread.sleep(1000);
-        }
-        assertEquals(1, edit[0].getChanges().size());
-        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        CodeAction resolvedCodeAction = server.getTextDocumentService().resolveCodeAction(generateToString.get()).get();
+        assertNotNull(resolvedCodeAction);
+        WorkspaceEdit edit = resolvedCodeAction.getEdit();
+        assertNotNull(edit);
+        assertEquals(1, edit.getChanges().size());
+        List<TextEdit> fileChanges = edit.getChanges().get(uri);
         assertNotNull(fileChanges);
         assertEquals(1, fileChanges.size());
         assertEquals(new Range(new Position(2, 0),
@@ -3488,7 +3481,6 @@ public class ServerTest extends NbTestCase {
         try (Writer w = new FileWriter(src)) {
             w.write(code);
         }
-        WorkspaceEdit[] edit = new WorkspaceEdit[1];
         Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
             @Override
             public void telemetryEvent(Object arg0) {
@@ -3515,8 +3507,7 @@ public class ServerTest extends NbTestCase {
 
             @Override
             public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
-                edit[0] = params.getEdit();
-                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+                throw new UnsupportedOperationException("Not supported yet.");
             }
 
             @Override
@@ -3567,7 +3558,7 @@ public class ServerTest extends NbTestCase {
         server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
         VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
         List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
-        assertEquals(7, codeActions.size());
+        assertEquals(8, codeActions.size());
         Optional<CodeAction> generateDelegateMethod =
                 codeActions.stream()
                            .filter(Either::isRight)
@@ -3575,13 +3566,12 @@ public class ServerTest extends NbTestCase {
                            .filter(a -> Bundle.DN_GenerateDelegateMethod().equals(a.getTitle()))
                            .findAny();
         assertTrue(generateDelegateMethod.isPresent());
-        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateDelegateMethod.get().getCommand().getCommand(), generateDelegateMethod.get().getCommand().getArguments())).get();
-        int cnt = 0;
-        while(edit[0] == null && cnt++ < 10) {
-            Thread.sleep(1000);
-        }
-        assertEquals(1, edit[0].getChanges().size());
-        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        CodeAction resolvedCodeAction = server.getTextDocumentService().resolveCodeAction(generateDelegateMethod.get()).get();
+        assertNotNull(resolvedCodeAction);
+        WorkspaceEdit edit = resolvedCodeAction.getEdit();
+        assertNotNull(edit);
+        assertEquals(1, edit.getChanges().size());
+        List<TextEdit> fileChanges = edit.getChanges().get(uri);
         assertNotNull(fileChanges);
         assertEquals(2, fileChanges.size());
         assertEquals(new Range(new Position(0, 0),
@@ -3612,7 +3602,6 @@ public class ServerTest extends NbTestCase {
         try (Writer w = new FileWriter(src)) {
             w.write(code);
         }
-        WorkspaceEdit[] edit = new WorkspaceEdit[1];
         Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
             @Override
             public void telemetryEvent(Object arg0) {
@@ -3639,8 +3628,7 @@ public class ServerTest extends NbTestCase {
 
             @Override
             public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
-                edit[0] = params.getEdit();
-                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+                throw new UnsupportedOperationException("Not supported yet.");
             }
 
             @Override
@@ -3691,7 +3679,7 @@ public class ServerTest extends NbTestCase {
         server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
         VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
         List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
-        assertEquals(7, codeActions.size());
+        assertEquals(8, codeActions.size());
         Optional<CodeAction> generateOverrideMethod =
                 codeActions.stream()
                            .filter(Either::isRight)
@@ -3699,13 +3687,12 @@ public class ServerTest extends NbTestCase {
                            .filter(a -> Bundle.DN_GenerateOverrideMethod().equals(a.getTitle()))
                            .findAny();
         assertTrue(generateOverrideMethod.isPresent());
-        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateOverrideMethod.get().getCommand().getCommand(), generateOverrideMethod.get().getCommand().getArguments())).get();
-        int cnt = 0;
-        while(edit[0] == null && cnt++ < 10) {
-            Thread.sleep(1000);
-        }
-        assertEquals(1, edit[0].getChanges().size());
-        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        CodeAction resolvedCodeAction = server.getTextDocumentService().resolveCodeAction(generateOverrideMethod.get()).get();
+        assertNotNull(resolvedCodeAction);
+        WorkspaceEdit edit = resolvedCodeAction.getEdit();
+        assertNotNull(edit);
+        assertEquals(1, edit.getChanges().size());
+        List<TextEdit> fileChanges = edit.getChanges().get(uri);
         assertNotNull(fileChanges);
         assertEquals(1, fileChanges.size());
         assertEquals(new Range(new Position(2, 0),
@@ -3733,7 +3720,6 @@ public class ServerTest extends NbTestCase {
         try (Writer w = new FileWriter(src)) {
             w.write(code);
         }
-        WorkspaceEdit[] edit = new WorkspaceEdit[1];
         Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new NbCodeLanguageClient() {
             @Override
             public void telemetryEvent(Object arg0) {
@@ -3760,8 +3746,7 @@ public class ServerTest extends NbTestCase {
 
             @Override
             public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
-                edit[0] = params.getEdit();
-                return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
+                throw new UnsupportedOperationException("Not supported yet.");
             }
 
             @Override
@@ -3812,7 +3797,7 @@ public class ServerTest extends NbTestCase {
         server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
         VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
         List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(2, 0), new Position(2, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
-        assertEquals(7, codeActions.size());
+        assertEquals(8, codeActions.size());
         Optional<CodeAction> generateLogger =
                 codeActions.stream()
                            .filter(Either::isRight)
@@ -3820,13 +3805,12 @@ public class ServerTest extends NbTestCase {
                            .filter(a -> Bundle.DN_GenerateLogger().equals(a.getTitle()))
                            .findAny();
         assertTrue(generateLogger.isPresent());
-        server.getWorkspaceService().executeCommand(new ExecuteCommandParams(generateLogger.get().getCommand().getCommand(), generateLogger.get().getCommand().getArguments())).get();
-        int cnt = 0;
-        while(edit[0] == null && cnt++ < 10) {
-            Thread.sleep(1000);
-        }
-        assertEquals(1, edit[0].getChanges().size());
-        List<TextEdit> fileChanges = edit[0].getChanges().get(uri);
+        CodeAction resolvedCodeAction = server.getTextDocumentService().resolveCodeAction(generateLogger.get()).get();
+        assertNotNull(resolvedCodeAction);
+        WorkspaceEdit edit = resolvedCodeAction.getEdit();
+        assertNotNull(edit);
+        assertEquals(1, edit.getChanges().size());
+        List<TextEdit> fileChanges = edit.getChanges().get(uri);
         assertNotNull(fileChanges);
         assertEquals(2, fileChanges.size());
         assertEquals(new Range(new Position(0, 0),
@@ -3842,6 +3826,82 @@ public class ServerTest extends NbTestCase {
                      fileChanges.get(1).getNewText());
     }
 
+    public void testSourceActionOrganizeImports() throws Exception {
+        File src = new File(getWorkDir(), "Test.java");
+        src.getParentFile().mkdirs();
+        String code = "import java.util.List;\n" +
+                      "import java.util.ArrayList;\n" +
+                      "import java.util.Collection;\n" +
+                      "\n" +
+                      "public class Test {\n" +
+                      "    private final List<String> names = new ArrayList<>();\n" +
+                      "}\n";
+        try (Writer w = new FileWriter(src)) {
+            w.write(code);
+        }
+        Launcher<LanguageServer> serverLauncher = LSPLauncher.createClientLauncher(new LspClient() {
+            @Override
+            public void telemetryEvent(Object arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void publishDiagnostics(PublishDiagnosticsParams params) {
+            }
+
+            @Override
+            public void showMessage(MessageParams arg0) {
+            }
+
+            @Override
+            public CompletableFuture<MessageActionItem> showMessageRequest(ShowMessageRequestParams arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public void logMessage(MessageParams arg0) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+            @Override
+            public CompletableFuture<ApplyWorkspaceEditResponse> applyEdit(ApplyWorkspaceEditParams params) {
+                throw new UnsupportedOperationException("Not supported yet.");
+            }
+
+        }, client.getInputStream(), client.getOutputStream());
+        serverLauncher.startListening();
+        LanguageServer server = serverLauncher.getRemoteProxy();
+        server.initialize(new InitializeParams()).get();
+        String uri = src.toURI().toString();
+        server.getTextDocumentService().didOpen(new DidOpenTextDocumentParams(new TextDocumentItem(uri, "java", 0, code)));
+        VersionedTextDocumentIdentifier id = new VersionedTextDocumentIdentifier(src.toURI().toString(), 1);
+        List<Either<Command, CodeAction>> codeActions = server.getTextDocumentService().codeAction(new CodeActionParams(id, new Range(new Position(6, 0), new Position(6, 0)), new CodeActionContext(Arrays.asList(), Arrays.asList(CodeActionKind.Source)))).get();
+        assertEquals(8, codeActions.size());
+        Optional<CodeAction> organizeImports =
+                codeActions.stream()
+                           .filter(Either::isRight)
+                           .map(Either::getRight)
+                           .filter(a -> Bundle.DN_OrganizeImports().equals(a.getTitle()))
+                           .findAny();
+        assertTrue(organizeImports.isPresent());
+        CodeAction resolvedCodeAction = server.getTextDocumentService().resolveCodeAction(organizeImports.get()).get();
+        assertNotNull(resolvedCodeAction);
+        WorkspaceEdit edit = resolvedCodeAction.getEdit();
+        assertNotNull(edit);
+        assertEquals(1, edit.getChanges().size());
+        List<TextEdit> fileChanges = edit.getChanges().get(uri);
+        assertNotNull(fileChanges);
+        assertEquals(2, fileChanges.size());
+        assertEquals(new Range(new Position(0, 0),
+                               new Position(1, 0)),
+                     fileChanges.get(0).getRange());
+        assertEquals("", fileChanges.get(0).getNewText());
+        assertEquals(new Range(new Position(2, 17),
+                               new Position(2, 27)),
+                     fileChanges.get(1).getRange());
+        assertEquals("List", fileChanges.get(1).getNewText());
+    }
+
     public void testRenameDocumentChangesCapabilitiesRenameOp() throws Exception {
         doTestRename(init -> {
                         WorkspaceEditCapabilities wec = new WorkspaceEditCapabilities();
diff --git a/java/java.lsp.server/vscode/package.json b/java/java.lsp.server/vscode/package.json
index d2cdd7f..94f9793 100644
--- a/java/java.lsp.server/vscode/package.json
+++ b/java/java.lsp.server/vscode/package.json
@@ -108,6 +108,34 @@
 					"type": "integer",
 					"default": 100,
 					"description": "Timeout (in milliseconds) for loading Javadoc in code completion (-1 for unlimited)"
+				},
+				"netbeans.java.onSave.organizeImports": {
+					"type": "boolean",
+					"default": true,
+					"description": "Enable organize imports action on a document save"
+				},
+				"netbeans.java.imports.groups": {
+					"type": "array",
+					"description": "Groups of import statements (specified by their package prefixes) and their sorting order. Import statements within a group are ordered alphabetically",
+					"default": [
+						"java",
+						"javax",
+						"org",
+						"com",
+						""
+					]
+				},
+				"netbeans.java.imports.countForUsingStarImport": {
+					"type": "integer",
+					"description": "Class count to use a star-import",
+					"default": 999,
+					"minimum": 1
+				},
+				"netbeans.java.imports.countForUsingStaticStarImport": {
+					"type": "integer",
+					"description": "Members count to use a static star-import",
+					"default": 999,
+					"minimum": 1
 				}
 			}
 		},
diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts
index 2263916..008d5d8 100644
--- a/java/java.lsp.server/vscode/src/extension.ts
+++ b/java/java.lsp.server/vscode/src/extension.ts
@@ -568,7 +568,7 @@ function doActivateWithJDK(specifiedJDK: string | null, context: ExtensionContex
             // Register the server for java documents
             documentSelector: documentSelectors,
             synchronize: {
-                configurationSection: 'java',
+                configurationSection: 'netbeans.java.imports',
                 fileEvents: [
                     workspace.createFileSystemWatcher('**/*.java')
                 ]

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists