Skip to content

Commit e27c3c2

Browse files
authored
feat: language-server powered file/folder create, rename, delete (#1415)
1 parent 03e08c6 commit e27c3c2

File tree

17 files changed

+1491
-7
lines changed

17 files changed

+1491
-7
lines changed

org.eclipse.lsp4e.test/META-INF/MANIFEST.MF

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Require-Bundle: org.eclipse.core.runtime,
2222
org.eclipse.lsp4j,
2323
org.eclipse.lsp4j.debug,
2424
org.eclipse.jdt.annotation,
25+
org.eclipse.ltk.core.refactoring,
2526
org.eclipse.ui.tests.harness,
2627
org.eclipse.ui.monitoring,
2728
org.eclipse.core.variables,
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Vegard IT GmbH and others.
3+
* This program and the accompanying materials are made
4+
* available under the terms of the Eclipse Public License 2.0
5+
* which is available at https://www.eclipse.org/legal/epl-2.0/
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Sebastian Thomschke (Vegard IT GmbH) - initial implementation
11+
*******************************************************************************/
12+
package org.eclipse.lsp4e.test.operations.rename;
13+
14+
import static org.junit.jupiter.api.Assertions.*;
15+
16+
import java.net.URI;
17+
import java.util.List;
18+
19+
import org.eclipse.core.resources.IFile;
20+
import org.eclipse.lsp4e.LSPEclipseUtils;
21+
import org.eclipse.lsp4e.LanguageServers;
22+
import org.eclipse.lsp4e.operations.rename.LSPFileOperationParticipantSupport;
23+
import org.eclipse.lsp4e.test.utils.AbstractTestWithProject;
24+
import org.eclipse.lsp4e.test.utils.TestUtils;
25+
import org.eclipse.lsp4e.tests.mock.MockLanguageServer;
26+
import org.eclipse.lsp4e.tests.mock.MockWorkspaceService;
27+
import org.eclipse.lsp4j.CreateFilesParams;
28+
import org.eclipse.lsp4j.DeleteFilesParams;
29+
import org.eclipse.lsp4j.FileCreate;
30+
import org.eclipse.lsp4j.FileDelete;
31+
import org.eclipse.lsp4j.FileOperationFilter;
32+
import org.eclipse.lsp4j.FileOperationOptions;
33+
import org.eclipse.lsp4j.FileOperationPattern;
34+
import org.eclipse.lsp4j.FileOperationsServerCapabilities;
35+
import org.eclipse.lsp4j.FileRename;
36+
import org.eclipse.lsp4j.RenameFilesParams;
37+
import org.eclipse.lsp4j.ServerCapabilities;
38+
import org.eclipse.lsp4j.WorkspaceServerCapabilities;
39+
import org.junit.jupiter.api.BeforeEach;
40+
import org.junit.jupiter.api.Test;
41+
42+
class FileOperationParticipantsTest extends AbstractTestWithProject {
43+
44+
@BeforeEach
45+
void setupCaps() {
46+
MockLanguageServer.reset(() -> {
47+
ServerCapabilities caps = MockLanguageServer.defaultServerCapabilities();
48+
var ws = new WorkspaceServerCapabilities();
49+
var fileOps = new FileOperationsServerCapabilities();
50+
fileOps.setWillCreate(new FileOperationOptions());
51+
fileOps.setWillRename(new FileOperationOptions());
52+
fileOps.setWillDelete(new FileOperationOptions());
53+
ws.setFileOperations(fileOps);
54+
caps.setWorkspace(ws);
55+
return caps;
56+
});
57+
}
58+
59+
@Test
60+
void testFilterGlobMatching() throws Exception {
61+
// Reconfigure with a forward-slash glob filter
62+
MockLanguageServer.reset(() -> {
63+
ServerCapabilities caps = MockLanguageServer.defaultServerCapabilities();
64+
var ws = new WorkspaceServerCapabilities();
65+
var fileOps = new FileOperationsServerCapabilities();
66+
var pattern = new FileOperationPattern("**/*.lspt");
67+
var filter = new FileOperationFilter(pattern, "file");
68+
var opts = new FileOperationOptions(List.of(filter));
69+
fileOps.setWillRename(opts);
70+
ws.setFileOperations(fileOps);
71+
caps.setWorkspace(ws);
72+
return caps;
73+
});
74+
75+
IFile file = TestUtils.createUniqueTestFile(project, "content");
76+
TestUtils.openTextViewer(file);
77+
TestUtils.waitForAndAssertCondition(5_000, () -> LanguageServers.forProject(project).anyMatching());
78+
var executor = LSPFileOperationParticipantSupport.createFileOperationExecutor(file,
79+
FileOperationsServerCapabilities::getWillRename);
80+
assertTrue(executor.anyMatching());
81+
}
82+
83+
@Test
84+
void testWillRename() throws Exception {
85+
IFile file = TestUtils.createUniqueTestFile(project, "content");
86+
TestUtils.openTextViewer(file);
87+
URI uri = LSPEclipseUtils.toUri(file);
88+
assertNotNull(uri);
89+
90+
// Ensure an LS is available
91+
assertTrue(LanguageServers.forProject(project).anyMatching());
92+
93+
var executor = LSPFileOperationParticipantSupport.createFileOperationExecutor(file,
94+
FileOperationsServerCapabilities::getWillRename);
95+
assertTrue(executor.anyMatching());
96+
97+
var params = new RenameFilesParams();
98+
URI newUri = LSPEclipseUtils.toUri(project.getFile("renamed-" + file.getName()));
99+
params.getFiles().add(new FileRename(uri.toString(), newUri.toString()));
100+
101+
// Exercise helper to trigger server call
102+
LSPFileOperationParticipantSupport.computePreChange("rename", params, executor,
103+
(ws, p) -> ws.willRenameFiles(p));
104+
105+
MockWorkspaceService ws = MockLanguageServer.INSTANCE.getWorkspaceService();
106+
assertNotNull(ws.getLastWillRename());
107+
assertEquals(1, ws.getLastWillRename().getFiles().size());
108+
assertEquals(uri.toString(), ws.getLastWillRename().getFiles().get(0).getOldUri());
109+
assertEquals(newUri.toString(), ws.getLastWillRename().getFiles().get(0).getNewUri());
110+
}
111+
112+
@Test
113+
void testWillCreate() throws Exception {
114+
IFile file = TestUtils.createUniqueTestFile(project, "content");
115+
TestUtils.openTextViewer(file);
116+
URI uri = LSPEclipseUtils.toUri(file);
117+
assertNotNull(uri);
118+
119+
var executor = LSPFileOperationParticipantSupport.createFileOperationExecutor(file,
120+
FileOperationsServerCapabilities::getWillCreate);
121+
assertTrue(executor.anyMatching());
122+
123+
var params = new CreateFilesParams();
124+
params.getFiles().add(new FileCreate(uri.toString()));
125+
126+
LSPFileOperationParticipantSupport.computePreChange("create", params, executor,
127+
(ws, p) -> ws.willCreateFiles(p));
128+
129+
MockWorkspaceService ws = MockLanguageServer.INSTANCE.getWorkspaceService();
130+
assertNotNull(ws.getLastWillCreate());
131+
assertEquals(1, ws.getLastWillCreate().getFiles().size());
132+
assertEquals(uri.toString(), ws.getLastWillCreate().getFiles().get(0).getUri());
133+
}
134+
135+
@Test
136+
void testWillDelete() throws Exception {
137+
IFile file = TestUtils.createUniqueTestFile(project, "content");
138+
TestUtils.openTextViewer(file);
139+
URI uri = LSPEclipseUtils.toUri(file);
140+
assertNotNull(uri);
141+
142+
var executor = LSPFileOperationParticipantSupport.createFileOperationExecutor(file,
143+
FileOperationsServerCapabilities::getWillDelete);
144+
assertTrue(executor.anyMatching());
145+
146+
var params = new DeleteFilesParams();
147+
params.getFiles().add(new FileDelete(uri.toString()));
148+
149+
LSPFileOperationParticipantSupport.computePreChange("delete", params, executor,
150+
(ws, p) -> ws.willDeleteFiles(p));
151+
152+
MockWorkspaceService ws = MockLanguageServer.INSTANCE.getWorkspaceService();
153+
assertNotNull(ws.getLastWillDelete());
154+
assertEquals(1, ws.getLastWillDelete().getFiles().size());
155+
assertEquals(uri.toString(), ws.getLastWillDelete().getFiles().get(0).getUri());
156+
}
157+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Vegard IT GmbH and others.
3+
* This program and the accompanying materials are made
4+
* available under the terms of the Eclipse Public License 2.0
5+
* which is available at https://www.eclipse.org/legal/epl-2.0/
6+
*
7+
* SPDX-License-Identifier: EPL-2.0
8+
*
9+
* Contributors:
10+
* Sebastian Thomschke (Vegard IT GmbH) - initial implementation
11+
*******************************************************************************/
12+
package org.eclipse.lsp4e.test.operations.rename;
13+
14+
import static org.junit.jupiter.api.Assertions.*;
15+
16+
import java.net.URI;
17+
import java.util.List;
18+
19+
import org.eclipse.core.resources.IFile;
20+
import org.eclipse.core.resources.IFolder;
21+
import org.eclipse.lsp4e.LSPEclipseUtils;
22+
import org.eclipse.lsp4e.LanguageServers;
23+
import org.eclipse.lsp4e.operations.rename.LSPFileOperationParticipantSupport;
24+
import org.eclipse.lsp4e.test.utils.AbstractTestWithProject;
25+
import org.eclipse.lsp4e.test.utils.TestUtils;
26+
import org.eclipse.lsp4e.tests.mock.MockLanguageServer;
27+
import org.eclipse.lsp4e.tests.mock.MockWorkspaceService;
28+
import org.eclipse.lsp4j.CreateFilesParams;
29+
import org.eclipse.lsp4j.DeleteFilesParams;
30+
import org.eclipse.lsp4j.FileCreate;
31+
import org.eclipse.lsp4j.FileDelete;
32+
import org.eclipse.lsp4j.FileOperationFilter;
33+
import org.eclipse.lsp4j.FileOperationOptions;
34+
import org.eclipse.lsp4j.FileOperationPattern;
35+
import org.eclipse.lsp4j.FileOperationPatternKind;
36+
import org.eclipse.lsp4j.FileOperationsServerCapabilities;
37+
import org.eclipse.lsp4j.FileRename;
38+
import org.eclipse.lsp4j.RenameFilesParams;
39+
import org.eclipse.lsp4j.ServerCapabilities;
40+
import org.eclipse.lsp4j.WorkspaceServerCapabilities;
41+
import org.junit.jupiter.api.Test;
42+
43+
class FolderOperationParticipantsTest extends AbstractTestWithProject {
44+
45+
@Test
46+
void testFolderFilterGlobMatching() throws Exception {
47+
// Reconfigure with a folder-only glob filter
48+
MockLanguageServer.reset(() -> {
49+
ServerCapabilities caps = MockLanguageServer.defaultServerCapabilities();
50+
var ws = new WorkspaceServerCapabilities();
51+
var fileOps = new FileOperationsServerCapabilities();
52+
var pattern = new FileOperationPattern("**/foo");
53+
pattern.setMatches(FileOperationPatternKind.Folder);
54+
var filter = new FileOperationFilter(pattern, "file");
55+
var opts = new FileOperationOptions(List.of(filter));
56+
fileOps.setWillRename(opts);
57+
ws.setFileOperations(fileOps);
58+
caps.setWorkspace(ws);
59+
return caps;
60+
});
61+
62+
// Start LS with updated capabilities
63+
IFile starter = TestUtils.createUniqueTestFile(project, "content");
64+
TestUtils.openTextViewer(starter);
65+
TestUtils.waitForAndAssertCondition(5_000, () -> LanguageServers.forProject(project).anyMatching());
66+
67+
// Create a folder named 'foo'
68+
IFolder folder = project.getFolder("foo");
69+
if (!folder.exists()) {
70+
folder.create(true, true, null);
71+
}
72+
73+
var executor = LSPFileOperationParticipantSupport.createFileOperationExecutor(folder,
74+
FileOperationsServerCapabilities::getWillRename);
75+
assertTrue(executor.anyMatching());
76+
}
77+
78+
@Test
79+
void testFolderWillRename() throws Exception {
80+
// Enable unfiltered file ops
81+
MockLanguageServer.reset(() -> {
82+
ServerCapabilities caps = MockLanguageServer.defaultServerCapabilities();
83+
var ws = new WorkspaceServerCapabilities();
84+
var fileOps = new FileOperationsServerCapabilities();
85+
fileOps.setWillRename(new FileOperationOptions());
86+
ws.setFileOperations(fileOps);
87+
caps.setWorkspace(ws);
88+
return caps;
89+
});
90+
91+
// Start LS with updated capabilities
92+
IFile starter = TestUtils.createUniqueTestFile(project, "content");
93+
TestUtils.openTextViewer(starter);
94+
TestUtils.waitForAndAssertCondition(5_000, () -> LanguageServers.forProject(project).anyMatching());
95+
96+
IFolder folder = project.getFolder("oldDir");
97+
if (!folder.exists()) {
98+
folder.create(true, true, null);
99+
}
100+
URI oldUri = LSPEclipseUtils.toUri(folder);
101+
assertNotNull(oldUri);
102+
103+
var executor = LSPFileOperationParticipantSupport.createFileOperationExecutor(folder,
104+
FileOperationsServerCapabilities::getWillRename);
105+
assertTrue(executor.anyMatching());
106+
107+
var params = new RenameFilesParams();
108+
URI newUri = LSPEclipseUtils.toUri(project.getFolder("newDir"));
109+
params.getFiles().add(new FileRename(oldUri.toString(), newUri.toString()));
110+
111+
LSPFileOperationParticipantSupport.computePreChange("rename-folder", params, executor,
112+
(ws, p) -> ws.willRenameFiles(p));
113+
114+
MockWorkspaceService ws = MockLanguageServer.INSTANCE.getWorkspaceService();
115+
assertNotNull(ws.getLastWillRename());
116+
assertEquals(1, ws.getLastWillRename().getFiles().size());
117+
assertEquals(oldUri.toString(), ws.getLastWillRename().getFiles().get(0).getOldUri());
118+
assertEquals(newUri.toString(), ws.getLastWillRename().getFiles().get(0).getNewUri());
119+
}
120+
121+
@Test
122+
void testFolderWillCreate() throws Exception {
123+
MockLanguageServer.reset(() -> {
124+
ServerCapabilities caps = MockLanguageServer.defaultServerCapabilities();
125+
var ws = new WorkspaceServerCapabilities();
126+
var fileOps = new FileOperationsServerCapabilities();
127+
fileOps.setWillCreate(new FileOperationOptions());
128+
ws.setFileOperations(fileOps);
129+
caps.setWorkspace(ws);
130+
return caps;
131+
});
132+
133+
IFile starter = TestUtils.createUniqueTestFile(project, "content");
134+
TestUtils.openTextViewer(starter);
135+
TestUtils.waitForAndAssertCondition(5_000, () -> LanguageServers.forProject(project).anyMatching());
136+
137+
IFolder folder = project.getFolder("toCreate");
138+
URI uri = LSPEclipseUtils.toUri(folder);
139+
assertNotNull(uri);
140+
141+
var executor = LSPFileOperationParticipantSupport.createFileOperationExecutor(folder,
142+
FileOperationsServerCapabilities::getWillCreate);
143+
assertTrue(executor.anyMatching());
144+
145+
var params = new CreateFilesParams();
146+
params.getFiles().add(new FileCreate(uri.toString()));
147+
148+
LSPFileOperationParticipantSupport.computePreChange("create-folder", params, executor,
149+
(ws, p) -> ws.willCreateFiles(p));
150+
151+
MockWorkspaceService ws = MockLanguageServer.INSTANCE.getWorkspaceService();
152+
assertNotNull(ws.getLastWillCreate());
153+
assertEquals(1, ws.getLastWillCreate().getFiles().size());
154+
assertEquals(uri.toString(), ws.getLastWillCreate().getFiles().get(0).getUri());
155+
}
156+
157+
@Test
158+
void testFolderWillDelete() throws Exception {
159+
MockLanguageServer.reset(() -> {
160+
ServerCapabilities caps = MockLanguageServer.defaultServerCapabilities();
161+
var ws = new WorkspaceServerCapabilities();
162+
var fileOps = new FileOperationsServerCapabilities();
163+
fileOps.setWillDelete(new FileOperationOptions());
164+
ws.setFileOperations(fileOps);
165+
caps.setWorkspace(ws);
166+
return caps;
167+
});
168+
169+
IFile starter = TestUtils.createUniqueTestFile(project, "content");
170+
TestUtils.openTextViewer(starter);
171+
TestUtils.waitForAndAssertCondition(5_000, () -> LanguageServers.forProject(project).anyMatching());
172+
173+
IFolder folder = project.getFolder("toDelete");
174+
if (!folder.exists()) {
175+
folder.create(true, true, null);
176+
}
177+
URI uri = LSPEclipseUtils.toUri(folder);
178+
assertNotNull(uri);
179+
180+
var executor = LSPFileOperationParticipantSupport.createFileOperationExecutor(folder,
181+
FileOperationsServerCapabilities::getWillDelete);
182+
183+
var params = new DeleteFilesParams();
184+
params.getFiles().add(new FileDelete(uri.toString()));
185+
186+
LSPFileOperationParticipantSupport.computePreChange("delete-folder", params, executor,
187+
(ws, p) -> ws.willDeleteFiles(p));
188+
189+
MockWorkspaceService ws = MockLanguageServer.INSTANCE.getWorkspaceService();
190+
assertNotNull(ws.getLastWillDelete());
191+
assertEquals(1, ws.getLastWillDelete().getFiles().size());
192+
assertEquals(uri.toString(), ws.getLastWillDelete().getFiles().get(0).getUri());
193+
}
194+
}

0 commit comments

Comments
 (0)