diff --git a/org.eclipse.lsp4e/plugin.xml b/org.eclipse.lsp4e/plugin.xml
index 612355b3d..e0e85cb2a 100644
--- a/org.eclipse.lsp4e/plugin.xml
+++ b/org.eclipse.lsp4e/plugin.xml
@@ -754,4 +754,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java
index 7b387d898..508778dce 100644
--- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java
+++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java
@@ -35,6 +35,7 @@
import org.eclipse.lsp4j.DocumentSymbolCapabilities;
import org.eclipse.lsp4j.ExecuteCommandCapabilities;
import org.eclipse.lsp4j.FailureHandlingKind;
+import org.eclipse.lsp4j.FileOperationsWorkspaceCapabilities;
import org.eclipse.lsp4j.FoldingRangeCapabilities;
import org.eclipse.lsp4j.FormattingCapabilities;
import org.eclipse.lsp4j.HoverCapabilities;
@@ -134,6 +135,9 @@ public class SupportedFeatures {
workspaceClientCapabilities.setExecuteCommand(new ExecuteCommandCapabilities(Boolean.TRUE));
workspaceClientCapabilities.setSymbol(new SymbolCapabilities(Boolean.TRUE));
workspaceClientCapabilities.setWorkspaceFolders(Boolean.TRUE);
+ FileOperationsWorkspaceCapabilities cpb = new FileOperationsWorkspaceCapabilities();
+ cpb.setWillRename(true);
+ workspaceClientCapabilities.setFileOperations(cpb);
WorkspaceEditCapabilities editCapabilities = new WorkspaceEditCapabilities();
editCapabilities.setDocumentChanges(Boolean.TRUE);
editCapabilities.setResourceOperations(Arrays.asList(ResourceOperationKind.Create,
diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/rename/LSPMoveParticipant.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/rename/LSPMoveParticipant.java
new file mode 100644
index 000000000..6fdac5648
--- /dev/null
+++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/rename/LSPMoveParticipant.java
@@ -0,0 +1,69 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Dawid Pakuła and others.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Dawid Pakuła - initial implementation
+ *******************************************************************************/
+package org.eclipse.lsp4e.operations.rename;
+
+import java.util.List;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.lsp4e.LSPEclipseUtils;
+import org.eclipse.lsp4e.LanguageServerWrapper;
+import org.eclipse.lsp4e.internal.Pair;
+import org.eclipse.lsp4j.FileRename;
+import org.eclipse.lsp4j.RenameFilesParams;
+import org.eclipse.lsp4j.services.LanguageServer;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
+import org.eclipse.ltk.core.refactoring.participants.MoveParticipant;
+
+public class LSPMoveParticipant extends MoveParticipant {
+
+ private IFile file;
+ private List> servers;
+
+ @Override
+ protected boolean initialize(Object element) {
+ if (element instanceof IFile && getArguments().getDestination() instanceof IFolder) {
+ file = (IFile) element;
+ this.servers = LSPRenameParticipant.collectServers(file);
+
+ return !servers.isEmpty();
+ }
+ return false;
+ }
+
+
+ @Override
+ public String getName() {
+ return "LSP4E Move";
+ }
+
+ @Override
+ public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context)
+ throws OperationCanceledException {
+ return null;
+ }
+
+ @Override
+ public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
+ var params = new RenameFilesParams();
+ params.getFiles().add(new FileRename(LSPEclipseUtils.toUri(file).toString(), LSPEclipseUtils
+ .toUri(((IFolder) getArguments().getDestination()).getRawLocation().append(file.getName())).toString()));
+
+ return LSPRenameParticipant.buildChange(servers, params, getName());
+ }
+
+}
diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/rename/LSPRenameParticipant.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/rename/LSPRenameParticipant.java
new file mode 100644
index 000000000..c4d056033
--- /dev/null
+++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/operations/rename/LSPRenameParticipant.java
@@ -0,0 +1,118 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Dawid Pakuła and others.
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Dawid Pakuła - initial implementation
+ *******************************************************************************/
+package org.eclipse.lsp4e.operations.rename;
+
+import java.nio.file.FileSystems;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.lsp4e.LSPEclipseUtils;
+import org.eclipse.lsp4e.LanguageServerWrapper;
+import org.eclipse.lsp4e.LanguageServers;
+import org.eclipse.lsp4e.internal.Pair;
+import org.eclipse.lsp4j.FileOperationOptions;
+import org.eclipse.lsp4j.FileRename;
+import org.eclipse.lsp4j.RenameFilesParams;
+import org.eclipse.lsp4j.services.LanguageServer;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.CompositeChange;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
+import org.eclipse.ltk.core.refactoring.participants.RenameParticipant;
+
+public class LSPRenameParticipant extends RenameParticipant {
+
+ private IFile file;
+ private List> servers;
+
+ @SuppressWarnings("null")
+ static List> collectServers(IFile file)
+ {
+ return LanguageServers.forProject(file.getProject()).withFilter(f -> {
+ if (f.getWorkspace() == null || f.getWorkspace().getFileOperations() == null) {
+ return false;
+ }
+ FileOperationOptions willRename = f.getWorkspace().getFileOperations().getWillRename();
+ if (willRename == null) {
+ return false;
+ }
+ if (willRename.getFilters() == null || willRename.getFilters().isEmpty()) {
+ return true;
+ }
+ return willRename.getFilters().stream().anyMatch(filter -> {
+ return FileSystems.getDefault().getPathMatcher("glob:" + filter.getPattern().getGlob()) //$NON-NLS-1$
+ .matches(FileSystems.getDefault().getPath(file.getRawLocation().toOSString()));
+ });
+
+ }).collectAll((w, ls) -> CompletableFuture.completedFuture(ls).thenApply(r -> Pair.of(w,r))).join();
+ }
+
+
+ @Override
+ protected boolean initialize(Object element) {
+ if (element instanceof IFile) {
+ file = (IFile) element;
+ this.servers = collectServers(file);
+
+ return !servers.isEmpty();
+ }
+ return false;
+ }
+
+ @Override
+ public String getName() {
+ return "LSP4E Rename";
+ }
+
+ @Override
+ public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context)
+ throws OperationCanceledException {
+
+ return new RefactoringStatus();
+ }
+
+
+ static Change buildChange(List> servers, RenameFilesParams params, String name)
+ {
+ List changes = servers.stream()
+ .map(p -> p.getSecond().getWorkspaceService().willRenameFiles(params).thenApply(edits -> {
+ if (edits == null) {
+ return new CompositeChange(name);
+ }
+
+ return LSPEclipseUtils.toCompositeChange(edits, p.getFirst().serverDefinition.label);
+
+ })).map(CompletableFuture::join).filter(c -> c != null && c.getChildren().length > 0).toList();
+ if (changes.isEmpty()) {
+ return null;
+ }
+ if (changes.size() == 1) {
+ return changes.get(0);
+ }
+ return new CompositeChange(name, changes.toArray(new CompositeChange[0]));
+ }
+
+ @Override
+ public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
+ var params = new RenameFilesParams();
+ params.getFiles().add(new FileRename(LSPEclipseUtils.toUri(file).toString(), LSPEclipseUtils
+ .toUri(file.getParent().getRawLocation().append(getArguments().getNewName())).toString()));
+
+
+ return buildChange(servers, params, getName());
+ }
+
+}