From 1e60f8fafe0b809c4ccc5232b1964f0c2422ee6a Mon Sep 17 00:00:00 2001 From: Dusan Balek Date: Mon, 17 Jul 2023 11:16:21 +0200 Subject: [PATCH 1/2] Allow for a lazy computation of CodeAction edits. --- ide/api.lsp/apichanges.xml | 12 ++ ide/api.lsp/manifest.mf | 2 +- .../org/netbeans/api/lsp/LazyCodeAction.java | 72 ++++++++++++ java/java.hints/nbproject/project.xml | 2 +- .../infrastructure/JavaErrorProvider.java | 108 +++++++++--------- java/java.lsp.server/nbproject/project.xml | 2 +- .../protocol/TextDocumentServiceImpl.java | 57 ++++++++- 7 files changed, 195 insertions(+), 60 deletions(-) create mode 100644 ide/api.lsp/src/org/netbeans/api/lsp/LazyCodeAction.java diff --git a/ide/api.lsp/apichanges.xml b/ide/api.lsp/apichanges.xml index 35df2b64c718..b14de6dd7bb9 100644 --- a/ide/api.lsp/apichanges.xml +++ b/ide/api.lsp/apichanges.xml @@ -51,6 +51,18 @@ + + + Added CodeAction with lazy edit computation + + + + + + LazyCodeAction allows for lazy edit computation. + + + Added URL to diagnostic code description diff --git a/ide/api.lsp/manifest.mf b/ide/api.lsp/manifest.mf index 5bf8a9f99546..edc6a163b9f2 100644 --- a/ide/api.lsp/manifest.mf +++ b/ide/api.lsp/manifest.mf @@ -1,5 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.api.lsp/1 OpenIDE-Module-Localizing-Bundle: org/netbeans/api/lsp/Bundle.properties -OpenIDE-Module-Specification-Version: 1.17 +OpenIDE-Module-Specification-Version: 1.18 AutoUpdate-Show-In-Client: false diff --git a/ide/api.lsp/src/org/netbeans/api/lsp/LazyCodeAction.java b/ide/api.lsp/src/org/netbeans/api/lsp/LazyCodeAction.java new file mode 100644 index 000000000000..25df864d9a82 --- /dev/null +++ b/ide/api.lsp/src/org/netbeans/api/lsp/LazyCodeAction.java @@ -0,0 +1,72 @@ +/* + * 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.api.lsp; + +import java.util.function.Supplier; + +/** + * An action over the code with lazy edit computation. + * + * @since 1.18 + */ +public final class LazyCodeAction extends CodeAction { + + private final Supplier lazyEdit; + + /** + * Constructs the {@code LazyCodeAction}. + * + * @param title the name of the action + * @param lazyEdit the lazily computed {@code WorkspaceEdit} that should be performed + */ + public LazyCodeAction(String title, Supplier lazyEdit) { + super(title, null, null); + this.lazyEdit = lazyEdit; + } + + /** + * Constructs the {@code LazyCodeAction}. + * + * @param title the name of the action + * @param command the command that should be invoked + * @param lazyEdit the lazily computed {@code WorkspaceEdit} that should be performed + */ + public LazyCodeAction(String title, Command command, Supplier lazyEdit) { + super(title, command, null); + this.lazyEdit = lazyEdit; + } + + @Override + public WorkspaceEdit getEdit() { + try { + return lazyEdit.get(); + } catch (Exception ex) { + return null; + } + } + + /** + * Returns the the lazily computed edit associated with the action. + * + * @return the the lazily computed edit associated with the action. + */ + public Supplier getLazyEdit() { + return lazyEdit; + } +} diff --git a/java/java.hints/nbproject/project.xml b/java/java.hints/nbproject/project.xml index d1fd03a8f77b..c5fa0eb0b2bd 100644 --- a/java/java.hints/nbproject/project.xml +++ b/java/java.hints/nbproject/project.xml @@ -58,7 +58,7 @@ 1 - 1.4 + 1.18 diff --git a/java/java.hints/src/org/netbeans/modules/java/hints/infrastructure/JavaErrorProvider.java b/java/java.hints/src/org/netbeans/modules/java/hints/infrastructure/JavaErrorProvider.java index b54aef547dce..9e5c5e69c40c 100644 --- a/java/java.hints/src/org/netbeans/modules/java/hints/infrastructure/JavaErrorProvider.java +++ b/java/java.hints/src/org/netbeans/modules/java/hints/infrastructure/JavaErrorProvider.java @@ -50,6 +50,7 @@ import org.netbeans.api.lsp.Command; import org.netbeans.api.lsp.Diagnostic; import org.netbeans.api.lsp.Diagnostic.Builder; +import org.netbeans.api.lsp.LazyCodeAction; import org.netbeans.api.lsp.ResourceOperation; import org.netbeans.api.lsp.ResourceOperation.CreateFile; import org.netbeans.api.lsp.TextDocumentEdit; @@ -229,66 +230,69 @@ protected void performRewrite(JavaFix.TransformationContext ctx) throws Exceptio }.toEditorFix(); } if (f instanceof JavaFixImpl) { - try { - JavaFix jf = ((JavaFixImpl) f).jf; - List edits = modify2TextEdits(js, wc -> { - wc.toPhase(JavaSource.Phase.RESOLVED); - Map resourceContentChanges = new HashMap(); - JavaFixImpl.Accessor.INSTANCE.process(jf, wc, true, resourceContentChanges, /*Ignored in editor:*/new ArrayList<>()); - }); - TextDocumentEdit te = new TextDocumentEdit(file.toURI().toString(), - edits); - CodeAction action = new CodeAction(f.getText(), new WorkspaceEdit(Collections.singletonList(Union2.createFirst(te)))); - result.add(action); - } catch (IOException ex) { - //TODO: include stack trace: - errorReporter.accept(ex); - } + JavaFix jf = ((JavaFixImpl) f).jf; + CodeAction action = new LazyCodeAction(f.getText(), () -> { + try { + List edits = modify2TextEdits(js, wc -> { + wc.toPhase(JavaSource.Phase.RESOLVED); + Map resourceContentChanges = new HashMap<>(); + JavaFixImpl.Accessor.INSTANCE.process(jf, wc, true, resourceContentChanges, /*Ignored in editor:*/new ArrayList<>()); + }); + TextDocumentEdit te = new TextDocumentEdit(file.toURI().toString(), edits); + return new WorkspaceEdit(Collections.singletonList(Union2.createFirst(te))); + } catch (IOException ex) { + //TODO: include stack trace: + errorReporter.accept(ex); + return null; + } + }); + result.add(action); } if (f instanceof ModificationResultBasedFix) { - try { - ModificationResultBasedFix cf = (ModificationResultBasedFix) f; - List> documentChanges = new ArrayList<>(); - for (ModificationResult changes : cf.getModificationResults()) { - Set newFiles = changes.getNewFiles(); - if (newFiles.size() > 1) { - throw new IllegalStateException(); - } - String newFilePath = null; - for (File newFile : newFiles) { - newFilePath = newFile.toURI().toString(); - documentChanges.add(Union2.createSecond(new CreateFile(newFilePath))); - } - outer: for (FileObject fileObject : changes.getModifiedFileObjects()) { - List diffs = changes.getDifferences(fileObject); - if (diffs != null) { - List edits = new ArrayList<>(); - for (ModificationResult.Difference diff : diffs) { - String newText = diff.getNewText(); - if (diff.getKind() == ModificationResult.Difference.Kind.CREATE) { - if (newFilePath != null) { - documentChanges.add(Union2.createFirst(new TextDocumentEdit(newFilePath, - Collections.singletonList(new TextEdit(0, 0, - newText != null ? newText : ""))))); + ModificationResultBasedFix cf = (ModificationResultBasedFix) f; + CodeAction codeAction = new LazyCodeAction(f.getText(), () -> { + try { + List> documentChanges = new ArrayList<>(); + for (ModificationResult changes : cf.getModificationResults()) { + Set newFiles = changes.getNewFiles(); + if (newFiles.size() > 1) { + throw new IllegalStateException(); + } + String newFilePath = null; + for (File newFile : newFiles) { + newFilePath = newFile.toURI().toString(); + documentChanges.add(Union2.createSecond(new CreateFile(newFilePath))); + } + outer: for (FileObject fileObject : changes.getModifiedFileObjects()) { + List diffs = changes.getDifferences(fileObject); + if (diffs != null) { + List edits = new ArrayList<>(); + for (ModificationResult.Difference diff : diffs) { + String newText = diff.getNewText(); + if (diff.getKind() == ModificationResult.Difference.Kind.CREATE) { + if (newFilePath != null) { + documentChanges.add(Union2.createFirst(new TextDocumentEdit(newFilePath, + Collections.singletonList(new TextEdit(0, 0, + newText != null ? newText : ""))))); + } + continue outer; + } else { + edits.add(new TextEdit(diff.getStartPosition().getOffset(), + diff.getEndPosition().getOffset(), + newText != null ? newText : "")); } - continue outer; - } else { - edits.add(new TextEdit(diff.getStartPosition().getOffset(), - diff.getEndPosition().getOffset(), - newText != null ? newText : "")); } + documentChanges.add(Union2.createFirst(new TextDocumentEdit(fileObject.toURI().toString(), edits))); //XXX: toURI } - documentChanges.add(Union2.createFirst(new TextDocumentEdit(fileObject.toURI().toString(), edits))); //XXX: toURI } } + return new WorkspaceEdit(documentChanges); + } catch (IOException ex) { + errorReporter.accept(ex); + return null; } - if (!documentChanges.isEmpty()) { - CodeAction codeAction = new CodeAction(f.getText(), new WorkspaceEdit(documentChanges)); - result.add(codeAction); - } - } catch (IOException ex) { - errorReporter.accept(ex); - } + }); + result.add(codeAction); } } diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml index d63e0c53580f..3c27136d34bd 100644 --- a/java/java.lsp.server/nbproject/project.xml +++ b/java/java.lsp.server/nbproject/project.xml @@ -128,7 +128,7 @@ 1 - 1.17 + 1.18 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 a73f17677477..bcdfc9322145 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 @@ -177,6 +177,7 @@ import org.netbeans.api.lsp.CallHierarchyEntry; import org.netbeans.api.lsp.Completion; import org.netbeans.api.lsp.HyperlinkLocation; +import org.netbeans.api.lsp.LazyCodeAction; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.Project; import org.netbeans.api.project.ProjectUtils; @@ -265,6 +266,8 @@ public class TextDocumentServiceImpl implements TextDocumentService, LanguageCli 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 String URL = "url";// NOI18N + private static final String INDEX = "index";// 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); @@ -882,14 +885,20 @@ private static DocumentSymbol structureElement2DocumentSymbol (StyledDocument do ds.setTags(Utils.elementTags2SymbolTags(el.getTags())); return ds; } + + private List lastCodeActions = null; @Override public CompletableFuture>> codeAction(CodeActionParams params) { + lastCodeActions = new ArrayList<>(); + AtomicInteger index = new AtomicInteger(0); + // shortcut: if the projects are not yet initialized, return empty: if (server.openedProjects().getNow(null) == null) { return CompletableFuture.completedFuture(Collections.emptyList()); } - Document rawDoc = server.getOpenedDocuments().getDocument(params.getTextDocument().getUri()); + String uri = params.getTextDocument().getUri(); + Document rawDoc = server.getOpenedDocuments().getDocument(uri); if (!(rawDoc instanceof StyledDocument)) { return CompletableFuture.completedFuture(Collections.emptyList()); } @@ -951,7 +960,13 @@ public CompletableFuture>> codeAction(CodeActio action.setCommand(new Command(inputAction.getCommand().getTitle(), inputAction.getCommand().getCommand(), commandParams)); } - if (inputAction.getEdit() != null) { + if (inputAction instanceof LazyCodeAction && ((LazyCodeAction) inputAction).getLazyEdit() != null) { + lastCodeActions.add((LazyCodeAction) inputAction); + Map data = new HashMap<>(); + data.put(URL, uri); + data.put(INDEX, index.getAndIncrement()); + action.setData(data); + } else if (inputAction.getEdit() != null) { org.netbeans.api.lsp.WorkspaceEdit edit = inputAction.getEdit(); List> documentChanges = new ArrayList<>(); for (Union2 parts : edit.getDocumentChanges()) { @@ -1066,16 +1081,48 @@ public void run(ResultIterator resultIterator) throws Exception { @Override public CompletableFuture resolveCodeAction(CodeAction unresolved) { JsonObject data = (JsonObject) unresolved.getData(); - if (data != null) { - String providerClass = data.getAsJsonPrimitive(CodeActionsProvider.CODE_ACTIONS_PROVIDER_CLASS).getAsString(); + if (data.has(CodeActionsProvider.CODE_ACTIONS_PROVIDER_CLASS)) { + String providerClass = ((JsonObject) 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)); + return codeGenerator.resolve(client, unresolved, ((JsonObject) data).get(CodeActionsProvider.DATA)); } } catch (Exception ex) { } } + } else if (data.has(URL) && data.has(INDEX)) { + LazyCodeAction inputAction = lastCodeActions.get(data.getAsJsonPrimitive(INDEX).getAsInt()); + if (inputAction != null) { + org.netbeans.api.lsp.WorkspaceEdit edit = inputAction.getLazyEdit().get(); + List> documentChanges = new ArrayList<>(); + for (Union2 parts : edit.getDocumentChanges()) { + if (parts.hasFirst()) { + String docUri = parts.first().getDocument(); + try { + FileObject file = Utils.fromUri(docUri); + if (file == null) { + file = Utils.fromUri(data.getAsJsonPrimitive(URL).getAsString()); + } + FileObject fo = file; + if (fo != null) { + List edits = parts.first().getEdits().stream().map(te -> new TextEdit(new Range(Utils.createPosition(fo, te.getStartOffset()), Utils.createPosition(fo, te.getEndOffset())), te.getNewText())).collect(Collectors.toList()); + TextDocumentEdit tde = new TextDocumentEdit(new VersionedTextDocumentIdentifier(docUri, -1), edits); + documentChanges.add(Either.forLeft(tde)); + } + } catch (Exception ex) { + client.logMessage(new MessageParams(MessageType.Error, ex.getMessage())); + } + } else { + if (parts.second() instanceof org.netbeans.api.lsp.ResourceOperation.CreateFile) { + documentChanges.add(Either.forRight(new CreateFile(((org.netbeans.api.lsp.ResourceOperation.CreateFile) parts.second()).getNewFile()))); + } else { + throw new IllegalStateException(String.valueOf(parts.second())); + } + } + } + unresolved.setEdit(new WorkspaceEdit(documentChanges)); + } } return CompletableFuture.completedFuture(unresolved); } From 15d73cdb5b1eb70d2af09ecd1ebfb8be5399c573 Mon Sep 17 00:00:00 2001 From: Dusan Balek Date: Mon, 17 Jul 2023 16:21:46 +0200 Subject: [PATCH 2/2] Test modified to allow for lazy CodeAction edits computation. --- .../java/lsp/server/protocol/ServerTest.java | 78 ++++++++++++++----- 1 file changed, 60 insertions(+), 18 deletions(-) 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 ca25bce6c7de..1c837dc2c31e 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 @@ -423,6 +423,9 @@ public void testMain() throws Exception { assertTrue(log, codeActions.get(0).isRight()); CodeAction action = codeActions.get(0).getRight(); assertEquals("Cast ...o to String", action.getTitle()); + assertNull(action.getEdit()); + action = server.getTextDocumentService().resolveCodeAction(action).get(); + assertNotNull(action.getEdit()); assertEquals(1, action.getEdit().getDocumentChanges().size()); assertEquals(1, action.getEdit().getDocumentChanges().get(0).getLeft().getEdits().size()); TextEdit edit = action.getEdit().getDocumentChanges().get(0).getLeft().getEdits().get(0); @@ -710,6 +713,9 @@ public void logMessage(MessageParams arg0) { assertTrue(log, codeActions.get(0).isRight()); CodeAction action = codeActions.get(0).getRight(); assertEquals("Remove .toString()", action.getTitle()); + assertNull(action.getEdit()); + action = server.getTextDocumentService().resolveCodeAction(action).get(); + assertNotNull(action.getEdit()); assertEquals(1, action.getEdit().getDocumentChanges().size()); assertEquals(1, action.getEdit().getDocumentChanges().get(0).getLeft().getEdits().size()); TextEdit edit = action.getEdit().getDocumentChanges().get(0).getLeft().getEdits().get(0); @@ -1672,8 +1678,12 @@ public void publishDiagnostics(PublishDiagnosticsParams params) { .filter(a -> "Add import for java.util.List".equals(a.getTitle())) .findAny(); assertTrue(addImport.isPresent()); - assertEquals(addImport.get().getKind(), CodeActionKind.QuickFix); - List> changes = addImport.get().getEdit().getDocumentChanges(); + CodeAction action = addImport.get(); + assertEquals(action.getKind(), CodeActionKind.QuickFix); + assertNull(action.getEdit()); + action = server.getTextDocumentService().resolveCodeAction(action).get(); + assertNotNull(action.getEdit()); + List> changes = action.getEdit().getDocumentChanges(); assertEquals(1, changes.size()); assertTrue(changes.get(0).isLeft()); TextDocumentEdit edit = changes.get(0).getLeft(); @@ -1928,8 +1938,12 @@ public CompletableFuture applyEdit(ApplyWorkspaceEdi .filter(a -> "Create local variable \"name\"".equals(a.getTitle())) .findAny(); assertTrue(generateVariable.isPresent()); - assertEquals(generateVariable.get().getKind(), CodeActionKind.QuickFix); - List> changes = generateVariable.get().getEdit().getDocumentChanges(); + CodeAction gvAction = generateVariable.get(); + assertEquals(gvAction.getKind(), CodeActionKind.QuickFix); + assertNull(gvAction.getEdit()); + gvAction = server.getTextDocumentService().resolveCodeAction(gvAction).get(); + assertNotNull(gvAction.getEdit()); + List> changes = gvAction.getEdit().getDocumentChanges(); assertEquals(1, changes.size()); assertTrue(changes.get(0).isLeft()); TextDocumentEdit edit = changes.get(0).getLeft(); @@ -1949,8 +1963,12 @@ public CompletableFuture applyEdit(ApplyWorkspaceEdi .filter(a -> "Create field \"name\" in Test".equals(a.getTitle())) .findAny(); assertTrue(generateField.isPresent()); - assertEquals(generateField.get().getKind(), CodeActionKind.QuickFix); - changes = generateField.get().getEdit().getDocumentChanges(); + CodeAction gfAction = generateField.get(); + assertEquals(gfAction.getKind(), CodeActionKind.QuickFix); + assertNull(gfAction.getEdit()); + gfAction = server.getTextDocumentService().resolveCodeAction(gfAction).get(); + assertNotNull(gfAction.getEdit()); + changes = gfAction.getEdit().getDocumentChanges(); assertEquals(1, changes.size()); assertTrue(changes.get(0).isLeft()); edit = changes.get(0).getLeft(); @@ -1971,8 +1989,12 @@ public CompletableFuture applyEdit(ApplyWorkspaceEdi .filter(a -> "Create parameter \"name\"".equals(a.getTitle())) .findAny(); assertTrue(generateParameter.isPresent()); - assertEquals(generateParameter.get().getKind(), CodeActionKind.QuickFix); - changes = generateParameter.get().getEdit().getDocumentChanges(); + CodeAction gpAction = generateParameter.get(); + assertEquals(gpAction.getKind(), CodeActionKind.QuickFix); + assertNull(gpAction.getEdit()); + gpAction = server.getTextDocumentService().resolveCodeAction(gpAction).get(); + assertNotNull(gpAction.getEdit()); + changes = gpAction.getEdit().getDocumentChanges(); assertEquals(1, changes.size()); assertTrue(changes.get(0).isLeft()); edit = changes.get(0).getLeft(); @@ -2056,8 +2078,12 @@ public CompletableFuture applyEdit(ApplyWorkspaceEdi .filter(a -> "Create method \"convertToString(int)\" in Test".equals(a.getTitle())) .findAny(); assertTrue(generateMehtod.isPresent()); - assertEquals(generateMehtod.get().getKind(), CodeActionKind.QuickFix); - List> changes = generateMehtod.get().getEdit().getDocumentChanges(); + CodeAction action = generateMehtod.get(); + assertEquals(action.getKind(), CodeActionKind.QuickFix); + assertNull(action.getEdit()); + action = server.getTextDocumentService().resolveCodeAction(action).get(); + assertNotNull(action.getEdit()); + List> changes = action.getEdit().getDocumentChanges(); assertEquals(1, changes.size()); assertTrue(changes.get(0).isLeft()); TextDocumentEdit edit = changes.get(0).getLeft(); @@ -2142,8 +2168,12 @@ public CompletableFuture applyEdit(ApplyWorkspaceEdi .filter(a -> "Create class \"Hello\" in Test".equals(a.getTitle())) .findAny(); assertTrue(generateClass.isPresent()); - assertEquals(generateClass.get().getKind(), CodeActionKind.QuickFix); - List> changes = generateClass.get().getEdit().getDocumentChanges(); + CodeAction action = generateClass.get(); + assertEquals(action.getKind(), CodeActionKind.QuickFix); + assertNull(action.getEdit()); + action = server.getTextDocumentService().resolveCodeAction(action).get(); + assertNotNull(action.getEdit()); + List> changes = action.getEdit().getDocumentChanges(); assertEquals(1, changes.size()); assertTrue(changes.get(0).isLeft()); TextDocumentEdit edit = changes.get(0).getLeft(); @@ -2229,8 +2259,12 @@ public CompletableFuture applyEdit(ApplyWorkspaceEdi .filter(a -> "Implement all abstract methods".equals(a.getTitle())) .findAny(); assertTrue(implementAllAbstractMethods.isPresent()); - assertEquals(implementAllAbstractMethods.get().getKind(), CodeActionKind.QuickFix); - List> changes = implementAllAbstractMethods.get().getEdit().getDocumentChanges(); + CodeAction action = implementAllAbstractMethods.get(); + assertEquals(action.getKind(), CodeActionKind.QuickFix); + assertNull(action.getEdit()); + action = server.getTextDocumentService().resolveCodeAction(action).get(); + assertNotNull(action.getEdit()); + List> changes = action.getEdit().getDocumentChanges(); assertEquals(1, changes.size()); assertTrue(changes.get(0).isLeft()); TextDocumentEdit edit = changes.get(0).getLeft(); @@ -2319,8 +2353,12 @@ public CompletableFuture applyEdit(ApplyWorkspaceEdi .filter(a -> "Implement all abstract methods".equals(a.getTitle())) .findAny(); assertTrue(implementAllAbstractMethods.isPresent()); - assertEquals(implementAllAbstractMethods.get().getKind(), CodeActionKind.QuickFix); - List> changes = implementAllAbstractMethods.get().getEdit().getDocumentChanges(); + CodeAction action = implementAllAbstractMethods.get(); + assertEquals(action.getKind(), CodeActionKind.QuickFix); + assertNull(action.getEdit()); + action = server.getTextDocumentService().resolveCodeAction(action).get(); + assertNotNull(action.getEdit()); + List> changes = action.getEdit().getDocumentChanges(); assertEquals(1, changes.size()); assertTrue(changes.get(0).isLeft()); TextDocumentEdit edit = changes.get(0).getLeft(); @@ -2406,8 +2444,12 @@ public CompletableFuture applyEdit(ApplyWorkspaceEdi .filter(a -> "Implement all abstract methods".equals(a.getTitle())) .findAny(); assertTrue(implementAllAbstractMethods.isPresent()); - assertEquals(implementAllAbstractMethods.get().getKind(), CodeActionKind.QuickFix); - List> changes = implementAllAbstractMethods.get().getEdit().getDocumentChanges(); + CodeAction action = implementAllAbstractMethods.get(); + assertEquals(action.getKind(), CodeActionKind.QuickFix); + assertNull(action.getEdit()); + action = server.getTextDocumentService().resolveCodeAction(action).get(); + assertNotNull(action.getEdit()); + List> changes = action.getEdit().getDocumentChanges(); assertEquals(1, changes.size()); assertTrue(changes.get(0).isLeft()); TextDocumentEdit edit = changes.get(0).getLeft();