From 2afaa6c7b00ceccfaa06961bb906ab8f26008243 Mon Sep 17 00:00:00 2001 From: Sheng Chen Date: Wed, 29 Nov 2023 01:10:42 -0800 Subject: [PATCH] Support add/remove imported projects (#2972) Signed-off-by: Sheng Chen --- org.eclipse.jdt.ls.core/plugin.xml | 3 + .../jdt/ls/core/internal/EventType.java | 2 + .../internal/JDTDelegateCommandHandler.java | 11 ++ .../internal/commands/ProjectCommand.java | 27 +++- .../internal/managers/ProjectsManager.java | 126 ++++++++++++++++++ .../managers/ProjectsManagerTest.java | 44 ++++++ 6 files changed, 209 insertions(+), 4 deletions(-) diff --git a/org.eclipse.jdt.ls.core/plugin.xml b/org.eclipse.jdt.ls.core/plugin.xml index fba9139db8..85cf439840 100644 --- a/org.eclipse.jdt.ls.core/plugin.xml +++ b/org.eclipse.jdt.ls.core/plugin.xml @@ -94,6 +94,9 @@ + + diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/EventType.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/EventType.java index 1613d74119..848fea0b32 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/EventType.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/EventType.java @@ -25,6 +25,8 @@ public enum EventType { */ ProjectsImported(200), + ProjectsDeleted(210), + /** * Incompatible issue between Gradle and Jdk event. */ diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java index 3996be68ee..b047170e7b 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/JDTDelegateCommandHandler.java @@ -27,6 +27,7 @@ import org.eclipse.jdt.ls.core.internal.commands.OrganizeImportsCommand; import org.eclipse.jdt.ls.core.internal.commands.ProjectCommand; import org.eclipse.jdt.ls.core.internal.commands.ProjectCommand.ClasspathOptions; +import org.eclipse.jdt.ls.core.internal.commands.ProjectCommand.GetAllProjectOptions; import org.eclipse.jdt.ls.core.internal.commands.SourceAttachmentCommand; import org.eclipse.jdt.ls.core.internal.commands.TypeHierarchyCommand; import org.eclipse.jdt.ls.core.internal.commands.VmCommand; @@ -97,6 +98,12 @@ public Object executeCommand(String commandId, List arguments, IProgress case "java.project.isTestFile": return ProjectCommand.isTestFile((String) arguments.get(0)); case "java.project.getAll": + if (!arguments.isEmpty()) { + GetAllProjectOptions option = JSONUtility.toModel(arguments.get(0), GetAllProjectOptions.class); + if (option.includeNonJava) { + return ProjectCommand.getAllProjects(); + } + } return ProjectCommand.getAllJavaProjects(); case "java.project.refreshDiagnostics": if (arguments.size() < 4) { @@ -106,6 +113,10 @@ public Object executeCommand(String commandId, List arguments, IProgress case "java.project.import": ProjectCommand.importProject(monitor); return null; + case "java.project.changeImportedProjects": + ProjectCommand.changeImportedProjects((ArrayList) arguments.get(0), + (ArrayList) arguments.get(1), (ArrayList) arguments.get(2), monitor); + return null; case "java.project.resolveStackTraceLocation": List projectNames = null; if (arguments.size() > 1) { diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommand.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommand.java index 1a664dd3ae..738ed16acd 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommand.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/commands/ProjectCommand.java @@ -19,6 +19,7 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Comparator; import java.util.Deque; import java.util.HashMap; @@ -222,17 +223,31 @@ public static boolean isTestFile(String uri) throws CoreException { } public static List getAllJavaProjects() { - List javaProjects = new LinkedList<>(); - for (IJavaProject javaProject : ProjectUtils.getJavaProjects()) { - javaProjects.add(ProjectUtils.getProjectRealFolder(javaProject.getProject()).toFile().toURI()); + return getProjectUris(Arrays.stream(ProjectUtils.getJavaProjects()) + .map(IJavaProject::getProject).toArray(IProject[]::new)); + } + + public static List getAllProjects() { + return getProjectUris(ProjectUtils.getAllProjects()); + } + + private static List getProjectUris(IProject[] projects) { + List projectUris = new LinkedList<>(); + for (IProject project : projects) { + projectUris.add(ProjectUtils.getProjectRealFolder(project).toFile().toURI()); } - return javaProjects; + return projectUris; } public static void importProject(IProgressMonitor monitor) { JavaLanguageServerPlugin.getProjectsManager().importProjects(monitor); } + public static void changeImportedProjects(Collection toUpdate, Collection toImport, + Collection toDelete, IProgressMonitor monitor) { + JavaLanguageServerPlugin.getProjectsManager().changeImportedProjects(toUpdate, toImport, toDelete, monitor); + } + private static IPath[] listTestSourcePaths(IJavaProject project) throws JavaModelException { List result = new ArrayList<>(); for (IClasspathEntry entry : project.getRawClasspath()) { @@ -319,6 +334,10 @@ public ClasspathResult(URI projectRoot, String[] classpaths, String[] modulepath } } + public static class GetAllProjectOptions { + public boolean includeNonJava; + } + public static SymbolInformation resolveWorkspaceSymbol(SymbolInformation request) { ITypeRoot unit = JDTUtils.resolveTypeRoot(request.getLocation().getUri()); if (unit == null || !unit.exists()) { diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ProjectsManager.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ProjectsManager.java index 43921f9a60..94e6772ac3 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ProjectsManager.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/managers/ProjectsManager.java @@ -20,7 +20,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -35,6 +38,8 @@ import org.eclipse.core.internal.resources.CharsetManager; import org.eclipse.core.internal.resources.Workspace; import org.eclipse.core.resources.FileInfoMatcherDescription; +import org.eclipse.core.resources.IContainer; +import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; @@ -73,6 +78,7 @@ import org.eclipse.jdt.ls.core.internal.IConstants; import org.eclipse.jdt.ls.core.internal.IProjectImporter; import org.eclipse.jdt.ls.core.internal.JDTEnvironmentUtils; +import org.eclipse.jdt.ls.core.internal.JDTUtils; import org.eclipse.jdt.ls.core.internal.JavaClientConnection.JavaLanguageClient; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; import org.eclipse.jdt.ls.core.internal.JobHelpers; @@ -83,6 +89,7 @@ import org.eclipse.jdt.ls.core.internal.handlers.BaseInitHandler; import org.eclipse.jdt.ls.core.internal.handlers.ProjectEncodingMode; import org.eclipse.jdt.ls.core.internal.preferences.PreferenceManager; +import org.eclipse.lsp4j.PublishDiagnosticsParams; public abstract class ProjectsManager implements ISaveParticipant, IProjectsManager { @@ -233,6 +240,42 @@ public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException { job.schedule(); } + public void changeImportedProjects(Collection toImport, Collection toUpdate, + Collection toDelete, IProgressMonitor monitor) { + Set filesToImport = new HashSet<>(); + for (String uri : toImport) { + filesToImport.add(ResourceUtils.canonicalFilePathFromURI(uri)); + } + + Set projectsToUpdate = new HashSet<>(); + for (String uri : toUpdate) { + IContainer folder = JDTUtils.findFolder(uri); + if (folder == null) { + continue; + } + IProject project = folder.getProject(); + if (project != null) { + projectsToUpdate.add(project); + } + } + + Set projectsToDelete = new HashSet<>(); + for (String uri : toDelete) { + IContainer folder = JDTUtils.findFolder(uri); + if (folder == null) { + continue; + } + IProject project = folder.getProject(); + if (project != null) { + projectsToDelete.add(project); + } + } + + WorkspaceJob job = new ImportProjectsFromSelectionJob(filesToImport, projectsToUpdate, projectsToDelete); + job.setRule(getWorkspaceRoot()); + job.schedule(); + } + @Override public Job updateWorkspaceFolders(Collection addedRootPaths, Collection removedRootPaths) { JavaLanguageServerPlugin.sendStatus(ServiceStatus.Message, "Updating workspace folders: Adding " + addedRootPaths.size() + " folder(s), removing " + removedRootPaths.size() + " folders."); @@ -750,4 +793,87 @@ private void onDidConfigurationUpdated(MultiStatus status, IProgressMonitor moni } } } + + private final class ImportProjectsFromSelectionJob extends WorkspaceJob { + private final Set filesToImport; + private final Set projectsToUpdate; + private final Set projectsToDelete; + + private ImportProjectsFromSelectionJob(Set filesToImport, Set projectsToUpdate, Set projectsToDelete) { + super("Applying the selected build files..."); + this.filesToImport = filesToImport; + this.projectsToUpdate = projectsToUpdate; + this.projectsToDelete = projectsToDelete; + } + + @Override + public IStatus runInWorkspace(IProgressMonitor monitor) throws CoreException { + try { + SubMonitor subMonitor = SubMonitor.convert(monitor, 100); + deleteProjects(subMonitor); + importProjects(subMonitor); + updateProjects(projectsToUpdate, true); + return Status.OK_STATUS; + } catch (OperationCanceledException e) { + return Status.CANCEL_STATUS; + } catch (CoreException e) { + return new Status(IStatus.ERROR, IConstants.PLUGIN_ID, "Applying the selected build files failed.", e); + } + } + + private void importProjects(SubMonitor subMonitor) throws CoreException { + importProjectsFromConfigurationFiles( + preferenceManager.getPreferences().getRootPaths(), + filesToImport, + subMonitor.split(70) + ); + List projectUris = filesToImport.stream() + .map(path -> { + IFile file = JDTUtils.findFile(path.toFile().toURI().toString()); + return file == null ? null : file.getProject(); + }) + .filter(Objects::nonNull) + .map(project -> ProjectUtils.getProjectRealFolder(project).toFile().toURI()) + .collect(Collectors.toList()); + if (!projectUris.isEmpty()) { + EventNotification notification = new EventNotification().withType(EventType.ProjectsImported).withData(projectUris); + client.sendEventNotification(notification); + } + } + + private void deleteProjects(SubMonitor subMonitor) throws CoreException { + List projectUris = new LinkedList<>(); + for (IProject project : projectsToDelete) { + clearDiagnostics(project); + // once the project is deleted, the project.getLocationURI() will return null, so we + // store the uri before deleting happens. + projectUris.add(ProjectUtils.getProjectRealFolder(project).toFile().toURI()); + project.delete(false /*deleteContent*/, false /*force*/, subMonitor.split(1)); + } + if (!projectUris.isEmpty()) { + EventNotification notification = new EventNotification().withType(EventType.ProjectsDeleted).withData(projectUris); + client.sendEventNotification(notification); + } + } + + private void clearDiagnostics(IProject project) throws CoreException { + IMarker[] markers = project.findMarkers(null, true, IResource.DEPTH_INFINITE); + Set uris = new HashSet<>(); + for (IMarker marker : markers) { + URI locationURI = marker.getResource().getLocationURI(); + if (locationURI != null) { + String uriString = locationURI.toString(); + if (new File(locationURI).isDirectory() && Platform.OS_WIN32.equals(Platform.getOS()) && + !uriString.endsWith("/")) { + uriString += "/"; + } + uris.add(uriString); + } + } + for (String uri : uris) { + PublishDiagnosticsParams diagnostics = new PublishDiagnosticsParams(ResourceUtils.toClientUri(uri), Collections.emptyList()); + client.publishDiagnostics(diagnostics); + } + } + } } diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/managers/ProjectsManagerTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/managers/ProjectsManagerTest.java index 24a90ec863..5aff50c95b 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/managers/ProjectsManagerTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/managers/ProjectsManagerTest.java @@ -251,6 +251,50 @@ public void testImportMavenSubModule() throws IOException, OperationCanceledExce } } + @Test + public void testChangeImportedMavenSubModule() throws Exception { + File projectDir = copyFiles("maven/multimodule", true); + Path projectDirPath = projectDir.toPath(); + Collection configurationPaths = new ArrayList<>(); + Path subModuleConfiguration = projectDirPath.resolve("module1/pom.xml"); + IPath filePath = ResourceUtils.canonicalFilePathFromURI(subModuleConfiguration.toUri().toString()); + configurationPaths.add(filePath); + subModuleConfiguration = projectDirPath.resolve("module2/pom.xml"); + filePath = ResourceUtils.canonicalFilePathFromURI(subModuleConfiguration.toUri().toString()); + configurationPaths.add(filePath); + preferenceManager.getPreferences().setProjectConfigurations(configurationPaths); + projectsManager.initializeProjects(Collections.singleton(new org.eclipse.core.runtime.Path(projectDir.getAbsolutePath())), monitor); + IProject[] allProjects = ProjectUtils.getAllProjects(); + Set expectedProjects = new HashSet<>(Arrays.asList( + "module1", + "childmodule", + "module2", + "jdt.ls-java-project" + )); + assertEquals(4, allProjects.length); + for (IProject project : allProjects) { + assertTrue(expectedProjects.contains(project.getName())); + } + + Path newBuildFile = projectDirPath.resolve("module3/pom.xml"); + List toImport = Collections.singletonList(newBuildFile.toUri().toString()); + IProject projectToRemove = WorkspaceHelper.getProject("module2"); + List toDelete = Collections.singletonList(projectToRemove.getLocationURI().toString()); + projectsManager.changeImportedProjects(toImport, Collections.emptyList(), toDelete, monitor); + waitForBackgroundJobs(); + allProjects = ProjectUtils.getAllProjects(); + expectedProjects = new HashSet<>(Arrays.asList( + "module1", + "childmodule", + "module3", + "jdt.ls-java-project" + )); + assertEquals(4, allProjects.length); + for (IProject project : allProjects) { + assertTrue(expectedProjects.contains(project.getName())); + } + } + @Test public void testImportMixedProjects() throws IOException, OperationCanceledException, CoreException { File projectDir = copyFiles("mixed", true);