Skip to content

Commit

Permalink
Merge pull request #6208 from dbalek/dbalek/lazy-code-actions
Browse files Browse the repository at this point in the history
Allow for a lazy computation of CodeAction edits.
  • Loading branch information
dbalek authored Jul 17, 2023
2 parents af92e2c + 15d73cd commit f49ef02
Show file tree
Hide file tree
Showing 8 changed files with 255 additions and 78 deletions.
12 changes: 12 additions & 0 deletions ide/api.lsp/apichanges.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@
<!-- ACTUAL CHANGES BEGIN HERE: -->

<changes>
<change id="LazyCodeAction">
<api name="LSP_API"/>
<summary>Added CodeAction with lazy edit computation</summary>
<version major="1" minor="18"/>
<date day="17" month="7" year="2023"/>
<author login="dbalek"/>
<compatibility binary="compatible" source="compatible" addition="yes" deletion="no"/>
<description>
<a href="@TOP@/org/netbeans/api/lsp/LazyCodeAction.html">LazyCodeAction</a> allows for lazy edit computation.
</description>
<class package="org.netbeans.api.lsp" name="LazyCodeAction"/>
</change>
<change id="Completion_getCommand">
<api name="LSP_API"/>
<summary>Added URL to diagnostic code description</summary>
Expand Down
2 changes: 1 addition & 1 deletion ide/api.lsp/manifest.mf
Original file line number Diff line number Diff line change
@@ -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
72 changes: 72 additions & 0 deletions ide/api.lsp/src/org/netbeans/api/lsp/LazyCodeAction.java
Original file line number Diff line number Diff line change
@@ -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<WorkspaceEdit> 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<WorkspaceEdit> 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<WorkspaceEdit> 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<WorkspaceEdit> getLazyEdit() {
return lazyEdit;
}
}
2 changes: 1 addition & 1 deletion java/java.hints/nbproject/project.xml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
<specification-version>1.4</specification-version>
<specification-version>1.18</specification-version>
</run-dependency>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -229,66 +230,69 @@ protected void performRewrite(JavaFix.TransformationContext ctx) throws Exceptio
}.toEditorFix();
}
if (f instanceof JavaFixImpl) {
try {
JavaFix jf = ((JavaFixImpl) f).jf;
List<TextEdit> edits = modify2TextEdits(js, wc -> {
wc.toPhase(JavaSource.Phase.RESOLVED);
Map<FileObject, byte[]> resourceContentChanges = new HashMap<FileObject, byte[]>();
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<TextEdit> edits = modify2TextEdits(js, wc -> {
wc.toPhase(JavaSource.Phase.RESOLVED);
Map<FileObject, byte[]> 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<Union2<TextDocumentEdit, ResourceOperation>> documentChanges = new ArrayList<>();
for (ModificationResult changes : cf.getModificationResults()) {
Set<File> 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<? extends ModificationResult.Difference> diffs = changes.getDifferences(fileObject);
if (diffs != null) {
List<TextEdit> 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<Union2<TextDocumentEdit, ResourceOperation>> documentChanges = new ArrayList<>();
for (ModificationResult changes : cf.getModificationResults()) {
Set<File> 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<? extends ModificationResult.Difference> diffs = changes.getDifferences(fileObject);
if (diffs != null) {
List<TextEdit> 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);
}
}

Expand Down
2 changes: 1 addition & 1 deletion java/java.lsp.server/nbproject/project.xml
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
<specification-version>1.17</specification-version>
<specification-version>1.18</specification-version>
</run-dependency>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -882,14 +885,20 @@ private static DocumentSymbol structureElement2DocumentSymbol (StyledDocument do
ds.setTags(Utils.elementTags2SymbolTags(el.getTags()));
return ds;
}

private List<LazyCodeAction> lastCodeActions = null;

@Override
public CompletableFuture<List<Either<Command, CodeAction>>> 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());
}
Expand Down Expand Up @@ -951,7 +960,13 @@ public CompletableFuture<List<Either<Command, CodeAction>>> 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<String, Object> 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<Either<TextDocumentEdit, ResourceOperation>> documentChanges = new ArrayList<>();
for (Union2<org.netbeans.api.lsp.TextDocumentEdit, org.netbeans.api.lsp.ResourceOperation> parts : edit.getDocumentChanges()) {
Expand Down Expand Up @@ -1066,16 +1081,48 @@ public void run(ResultIterator resultIterator) throws Exception {
@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();
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<Either<TextDocumentEdit, ResourceOperation>> documentChanges = new ArrayList<>();
for (Union2<org.netbeans.api.lsp.TextDocumentEdit, org.netbeans.api.lsp.ResourceOperation> 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<TextEdit> 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);
}
Expand Down
Loading

0 comments on commit f49ef02

Please sign in to comment.