Skip to content

Commit

Permalink
API/SPI to apply WorkspaceEdit
Browse files Browse the repository at this point in the history
  • Loading branch information
sdedic committed May 27, 2024
1 parent b6cf63e commit 54779f9
Show file tree
Hide file tree
Showing 18 changed files with 1,485 additions and 9 deletions.
17 changes: 16 additions & 1 deletion ide/api.lsp/apichanges.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
<change id="CodeActionProvider.getSupportedCodeActionKinds">
<api name="LSP_API"/>
<summary>Adding CodeActionProvider.getSupportedCodeActionKinds method</summary>
<version major="1" minor="27"/>
<version major="1" minor="28"/>
<date day="8" month="5" year="2024"/>
<author login="jlahoda"/>
<compatibility binary="compatible" source="compatible" addition="yes" deletion="no" />
Expand All @@ -64,6 +64,21 @@
</description>
<class package="org.netbeans.spi.lsp" name="CodeActionProvider"/>
</change>
<change id="applyModifications-spi">
<api name="LSP_API"/>
<summary>API and SPI to request application of WorkspaceEdit</summary>
<version major="1" minor="27"/>
<date day="25" month="5" year="2024"/>
<author login="sdedic"/>
<compatibility binary="compatible" source="compatible" addition="yes" deletion="no" />
<description>
Added API to request that a <a href="@TOP@/org/netbeans/api/lsp/WorkspaceEdit.html#applyEdits-java.util.List-boolean-">WorkspaceEdit</a> is applied
to the resources with an optional save.
</description>
<class package="org.netbeans.api.lsp" name="WorkspaceEdit"/>
<class package="org.netbeans.api.lsp" name="ResourceModificationException"/>
<class package="org.netbeans.spi.lsp" name="ApplyEditsImplementation"/>
</change>
<change id="ErrorProvider.Context.getHintsConfigFile">
<api name="LSP_API"/>
<summary>Adding ErrorProvider.Context.getHintsConfigFile() method</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.27
OpenIDE-Module-Specification-Version: 1.28
AutoUpdate-Show-In-Client: false
2 changes: 1 addition & 1 deletion ide/api.lsp/nbproject/project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# specific language governing permissions and limitations
# under the License.
is.autoload=true
javac.source=1.8
javac.release=11
javac.compilerargs=-Xlint -Xlint:-serial
javadoc.name=LSP APIs
javadoc.title=LSP APIs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* 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.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.NonNull;
import org.openide.util.Parameters;

/**
* Records a failure from applying {@link WorkspaceEdit}s to resources. Callers may
* inspect exception details to recover from the failure, revert the applied operations etc.
*
* @author sdedic
* @since 1.27
*/
public final class ResourceModificationException extends Exception {
private final List<WorkspaceEdit> appliedEdits;
private final WorkspaceEdit failedEdit;
private final int failedOperationIndex;
private final List<String> savedResources;
private final int failedEditIndex;

/**
* Index that indicates a failure before the first edit in {@link #getFailedEdit()}.
*/
public static final int BEFORE_FIRST_EDIT = -1;

/**
* Index that indicates that the exact failing edit is unspecified.
*/
public static final int UNSPECIFIED_EDIT = -2;

/**
* Index that indicates that the exact failing WorkspaceEdit operation is not known.
*/
public static final int UNSPECIFIED_OPERATIION = -2;

/**
* Initializes the exception for a failure from applying the workspace edits.
* @param message error message.
* @param appliedEdits Edits that have been fully applied to the resource, prior to the failure.
* @param failedOperationIndex index of operation within failedEdit that caused the failure
* @param savedResources resources that have been already saved.
* @param failedEdit the WorkspaeEdit that failed to apply.
* @param cause more specific exception that causes the failure, if any.
*/
public ResourceModificationException(List<WorkspaceEdit> appliedEdits, WorkspaceEdit failedEdit,
int failedOperationIndex, int failedEditIndex, Collection<String> savedResources, String message, Throwable cause) {
super(message, cause);
Parameters.notNull("appliedEdits", appliedEdits);
this.appliedEdits = appliedEdits;
this.failedEdit = failedEdit;
this.savedResources = savedResources == null ? Collections.emptyList() : new ArrayList<>(savedResources);
this.failedOperationIndex = failedOperationIndex;
this.failedEditIndex = failedEditIndex;
}

/**
* Initializes the exception for a failure from saving the resources. All edits of the operation have been applied.
* @param appliedEdits list of edits applied during the operation.
* @param savedResources resources that were successfully saved
* @param message failure message
*/
public ResourceModificationException(List<WorkspaceEdit> appliedEdits, List<String> savedResources, String message) {
super(message);
Parameters.notNull("appliedEdits", appliedEdits);
Parameters.notNull("savedResources", savedResources);
this.appliedEdits = appliedEdits;
this.failedEdit = null;
this.failedOperationIndex = UNSPECIFIED_OPERATIION;
this.failedEditIndex = UNSPECIFIED_EDIT;
this.savedResources = savedResources;
}

/**
* The index of the failed edit, from the failed operation (see {@link #getFailedOperationIndex()}).
* Will be set to {@link #UNSPECIFIED_EDIT}, if the exact failed edit is not known, or if the failure
* is the resource operation that does not use edits.
* @return index of the failed edit operation.
*/
public int getFailedEditIndex() {
return failedEditIndex;
}

/**
* Returns true, if the edit that caused the failure is specified.
* @return true, if the failing edit is known, false otherwise.
*/
public boolean isUnspecifiedEdit() {
return failedOperationIndex >= 0 && failedEditIndex != UNSPECIFIED_EDIT;
}


/**
* Edits that have been fully applied.
* @return applied edits
*/
public @NonNull List<WorkspaceEdit> getAppliedEdits() {
return appliedEdits;
}

/**
* Workspace edit that failed to apply. Possibly {@code null} (and then
* {@link #getFailedOperationIndex()} returns {@link #UNSPECIFIED_OPERATIION}). If the request fails
* during save, there's no failed edit.
* @return failed edit.
*/
public @CheckForNull WorkspaceEdit getFailedEdit() {
return failedEdit;
}

/**
* Index of the failed edit operation. If no failed operation is reported,
* the index is set to {@link #UNSPECIFIED_OPERATIION}.
* @return index of the failed operation, within {@link #getFailedEdit()}.
*/
public int getFailedOperationIndex() {
return failedOperationIndex;
}

/**
* List of modified resources saved to the disk prior to the failure. The method
* returns uris
* @return resource uris.
*/
public @NonNull List<String> getSavedResources() {
return savedResources;
}
}
30 changes: 30 additions & 0 deletions ide/api.lsp/src/org/netbeans/api/lsp/WorkspaceEdit.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.openide.util.Lookup;
import org.openide.util.Union2;
import org.netbeans.spi.lsp.ApplyEditsImplementation;

/**
* A set of edits over the workspace.
Expand Down Expand Up @@ -50,4 +53,31 @@ public List<Union2<TextDocumentEdit, ResourceOperation>> getDocumentChanges() {
return documentChanges;
}


/**
* Attempts to apply workspace edits to the resources. The resource(s) are optionally
* saved after modification. The caller may request just save of the resources, by supplying
* a {@link WorkspaceEdit} with {@link TextDocumentEdit}s that have empty set of changes.
* The implementation must apply edits so they result in the same result as if the contents
* of the WorkspaceEdit are applied in the order.
* <p/>
* Upon failure, the returned Future completes with {@link ResourceModificationException}.
* Completed WorkspaceEdits, the failed one and the index of a failed operation within it should be reported.
* If any resource was saved before the failure, it should be reported as saved.
*
* @param edits edits to apply.
* @param save if true, resources are saved after they are modified.
* @return future that completes with a list of resource URIs modified, or fails with {@link ResourceModificationException}.
* @since 1.27
*/
public static CompletableFuture<List<String>> applyEdits(List<WorkspaceEdit> edits, boolean save) {
ApplyEditsImplementation impl = Lookup.getDefault().lookup(ApplyEditsImplementation.class);
if (impl == null) {
ResourceModificationException ex = new ResourceModificationException(Collections.<WorkspaceEdit>emptyList(),
null, -1, -1, Collections.emptyList(), "Unsupported operation", new UnsupportedOperationException());
return CompletableFuture.failedFuture(ex);
}
return impl.applyChanges(edits, save);
}
}

54 changes: 54 additions & 0 deletions ide/api.lsp/src/org/netbeans/spi/lsp/ApplyEditsImplementation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.spi.lsp;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import org.netbeans.api.lsp.ResourceModificationException;
import org.netbeans.api.lsp.ResourceOperation;
import org.netbeans.api.lsp.TextDocumentEdit;
import org.netbeans.api.lsp.WorkspaceEdit;

/**
* This service takes care of "applying" resource modifications to the resources
* and potentially saving them. Various LSP commands or services result in
* {@link WorkspaceEdit} that need to be applied eventually. Depending on
* "deployment" of NetBeans platform, the application needs to be done by
* NetBeans application itself, or delegated to a LSP client.
*
* @author sdedic
*/
public interface ApplyEditsImplementation {

/**
* Requests that the edits are applied to the in-memory representation of
* the resources, and then possibly saved. The returned Future will be completed
* after the resources are modified and optionally saved, and will complete with a list of
* affected resources. The returned identifiers will be the same as in {@link TextDocumentEdit#getDocument()}
* or {@link ResourceOperation}.
* <p>
* If the caller requests save, the Future completes after the save is complete.
* <p>
* If the operation fails, the Future completes exceptionally with {@link ResourceModificationException}.
*
* @param edits edits to apply
* @param saveResources instructs to save the resources on the disk.
*/
public CompletableFuture<List<String>> applyChanges(List<WorkspaceEdit> edits, boolean saveResources);
}
3 changes: 2 additions & 1 deletion ide/project.dependency/nbproject/project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# under the License.

is.autoload=true
javac.source=1.8
javac.source=11
javac.target=11
javac.compilerargs=-Xlint -Xlint:-serial
spec.version.base=1.9.0
41 changes: 40 additions & 1 deletion ide/project.dependency/nbproject/project.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
<compile-dependency/>
<run-dependency>
<release-version>1</release-version>
<specification-version>1.21</specification-version>
<specification-version>1.28</specification-version>
</run-dependency>
</dependency>
<dependency>
Expand Down Expand Up @@ -117,6 +117,45 @@
</run-dependency>
</dependency>
</module-dependencies>
<test-dependencies>
<test-type>
<name>unit</name>
<test-dependency>
<code-name-base>org.netbeans.libs.junit4</code-name-base>
<compile-dependency/>
</test-dependency>
<test-dependency>
<code-name-base>org.netbeans.modules.nbjunit</code-name-base>
<recursive/>
<compile-dependency/>
</test-dependency>
<test-dependency>
<!-- Because otherwise data systems and their services will not materialize -->
<code-name-base>org.netbeans.modules.settings</code-name-base>
</test-dependency>
<test-dependency>
<code-name-base>org.netbeans.modules.editor.mimelookup.impl</code-name-base>
<recursive/>
</test-dependency>
<test-dependency>
<code-name-base>org.netbeans.modules.editor.plain</code-name-base>
<recursive/>
<compile-dependency/>
</test-dependency>
<test-dependency>
<code-name-base>org.netbeans.modules.editor.lib</code-name-base>
<recursive/>
</test-dependency>
<test-dependency>
<code-name-base>org.netbeans.modules.editor.actions</code-name-base>
<recursive/>
</test-dependency>
<test-dependency>
<code-name-base>org.netbeans.modules.projectapi.nb</code-name-base>
<compile-dependency/>
</test-dependency>
</test-type>
</test-dependencies>
<public-packages/>
</data>
</configuration>
Expand Down
Loading

0 comments on commit 54779f9

Please sign in to comment.