diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ChangeUtil.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ChangeUtil.java index 67d764bcf1..04d8ec3fc5 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ChangeUtil.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ChangeUtil.java @@ -13,7 +13,6 @@ package org.eclipse.jdt.ls.core.internal; import java.util.ArrayList; -import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -21,6 +20,7 @@ import java.util.Objects; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.NullProgressMonitor; @@ -269,6 +269,11 @@ private static void convertTextEdit(WorkspaceEdit root, ICompilationUnit unit, T } TextEditConverter converter = new TextEditConverter(unit, edit); + List textEdits = filterTextEdits(converter.convert()); + if (textEdits == null || textEdits.isEmpty()) { + return; + } + String uri = JDTUtils.toURI(unit); if (JavaLanguageServerPlugin.getPreferencesManager().getClientPreferences().isResourceOperationSupported()) { List> changes = root.getDocumentChanges(); @@ -278,18 +283,38 @@ private static void convertTextEdit(WorkspaceEdit root, ICompilationUnit unit, T } VersionedTextDocumentIdentifier identifier = new VersionedTextDocumentIdentifier(uri, 0); - TextDocumentEdit documentEdit = new TextDocumentEdit(identifier, converter.convert()); + TextDocumentEdit documentEdit = new TextDocumentEdit(identifier, textEdits); changes.add(Either.forLeft(documentEdit)); } else { Map> changes = root.getChanges(); if (changes.containsKey(uri)) { - changes.get(uri).addAll(converter.convert()); + changes.get(uri).addAll(textEdits); } else { - changes.put(uri, converter.convert()); + changes.put(uri, textEdits); } } } + private static List filterTextEdits(List textEdits) { + if (textEdits == null || textEdits.isEmpty()) { + return textEdits; + } + + return textEdits.stream().filter(textEdit -> !isEmpty(textEdit)).collect(Collectors.toList()); + } + + private static boolean isEmpty(org.eclipse.lsp4j.TextEdit textEdit) { + if (textEdit == null || textEdit.getRange() == null) { + return true; + } + + Position start = textEdit.getRange().getStart(); + Position end = textEdit.getRange().getEnd(); + return StringUtils.isEmpty(textEdit.getNewText()) + && start.getCharacter() == end.getCharacter() + && start.getLine() == end.getLine(); + } + private static ICompilationUnit getNewCompilationUnit(IType type, String newName) { ICompilationUnit cu = type.getCompilationUnit(); if (isPrimaryType(type)) { diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ResourceUtils.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ResourceUtils.java index 968a8c4330..6b1ee615b5 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ResourceUtils.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/ResourceUtils.java @@ -25,6 +25,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -300,4 +301,33 @@ public static String dos2Unix(String str) { return str; } + /** + * Returns the longest common path for an array of paths. + */ + public static IPath getLongestCommonPath(IPath[] paths) { + if (paths == null || paths.length == 0) { + return null; + } + + int common = paths[0].segmentCount() - 1; + for (int i = 1; i < paths.length; i++) { + common = Math.min(paths[i].segmentCount() - 1, common); + if (common <= 0 || !Objects.equals(paths[0].getDevice(), paths[i].getDevice())) { + return null; + } + + for (int j = 0; j < common; j++) { + if (!Objects.equals(paths[i].segment(j), paths[0].segment(j))) { + common = j; + break; + } + } + } + + if (common <= 0) { + return null; + } + + return paths[0].uptoSegment(common); + } } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/BaseDocumentLifeCycleHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/BaseDocumentLifeCycleHandler.java index 3289f9ceb7..2a90d94322 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/BaseDocumentLifeCycleHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/BaseDocumentLifeCycleHandler.java @@ -340,6 +340,8 @@ public ICompilationUnit handleOpen(DidOpenTextDocumentParams params) { OpenableElementInfo elementInfo = (OpenableElementInfo) pkg.getElementInfo(); elementInfo.addChild(unit); } + } else { // File not exists + return unit; } } catch (CoreException e) { // ignored @@ -422,7 +424,7 @@ public ICompilationUnit handleClosed(DidCloseTextDocumentParams params) { synchronized (toReconcile) { toReconcile.remove(unit); } - if (isSyntaxMode(unit) || unit.getResource().isDerived()) { + if (isSyntaxMode(unit) || !unit.exists() || unit.getResource().isDerived()) { createDiagnosticsHandler(unit).clearDiagnostics(); } else if (hasUnsavedChanges(unit)) { unit.discardWorkingCopy(); diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/DocumentSymbolHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/DocumentSymbolHandler.java index 95af5c19f2..af6884d842 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/DocumentSymbolHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/DocumentSymbolHandler.java @@ -70,7 +70,7 @@ public DocumentSymbolHandler(boolean hierarchicalDocumentSymbolSupported) { public List> documentSymbol(DocumentSymbolParams params, IProgressMonitor monitor) { ITypeRoot unit = JDTUtils.resolveTypeRoot(params.getTextDocument().getUri()); - if (unit == null) { + if (unit == null || !unit.exists()) { return Collections.emptyList(); } @@ -90,7 +90,11 @@ private SymbolInformation[] getOutline(ITypeRoot unit, IProgressMonitor monitor) collectChildren(unit, elements, symbols, monitor); return symbols.toArray(new SymbolInformation[symbols.size()]); } catch (JavaModelException e) { - JavaLanguageServerPlugin.logException("Problem getting outline for" + unit.getElementName(), e); + if (!unit.exists()) { + JavaLanguageServerPlugin.logError("Problem getting outline for " + unit.getElementName() + ": File not found."); + } else { + JavaLanguageServerPlugin.logException("Problem getting outline for " + unit.getElementName(), e); + } } return new SymbolInformation[0]; } @@ -134,7 +138,11 @@ private List getHierarchicalOutline(ITypeRoot unit, IProgressMon } catch (OperationCanceledException e) { logInfo("User abort while collecting the document symbols."); } catch (JavaModelException e) { - JavaLanguageServerPlugin.logException("Problem getting outline for" + unit.getElementName(), e); + if (!unit.exists()) { + JavaLanguageServerPlugin.logError("Problem getting outline for " + unit.getElementName() + ": File not found."); + } else { + JavaLanguageServerPlugin.logException("Problem getting outline for " + unit.getElementName(), e); + } } return emptyList(); } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/FileEventHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/FileEventHandler.java index 77ac231867..cca4e85f41 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/FileEventHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/FileEventHandler.java @@ -13,17 +13,17 @@ package org.eclipse.jdt.ls.core.internal.handlers; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; import org.eclipse.core.resources.IResource; -import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.jdt.core.ICompilationUnit; @@ -31,7 +31,7 @@ import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; -import org.eclipse.jdt.internal.core.JavaModelManager; +import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.ls.core.internal.ChangeUtil; import org.eclipse.jdt.ls.core.internal.JDTUtils; import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; @@ -42,6 +42,10 @@ import org.eclipse.jdt.ls.core.internal.commands.BuildPathCommand.SourcePath; import org.eclipse.jdt.ls.core.internal.corext.refactoring.rename.RenamePackageProcessor; import org.eclipse.jdt.ls.core.internal.corext.refactoring.rename.RenameSupport; +import org.eclipse.jdt.ls.core.internal.corext.refactoring.reorg.IReorgDestination; +import org.eclipse.jdt.ls.core.internal.corext.refactoring.reorg.ReorgDestinationFactory; +import org.eclipse.lsp4j.FileRename; +import org.eclipse.lsp4j.RenameFilesParams; import org.eclipse.lsp4j.WorkspaceEdit; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.CheckConditionsOperation; @@ -52,38 +56,70 @@ public class FileEventHandler { - public static WorkspaceEdit handleRenameFiles(FileRenameParams params, IProgressMonitor monitor) { - if (params.files == null || params.files.isEmpty()) { + public static WorkspaceEdit handleWillRenameFiles(RenameFilesParams params, IProgressMonitor monitor) { + List files = params.getFiles(); + if (files == null || files.isEmpty()) { return null; } - FileRenameEvent[] files = params.files.stream().filter(event -> isFileNameRenameEvent(event)).toArray(FileRenameEvent[]::new); - if (files.length == 0) { + FileRename[] renameFiles = new FileRename[0]; + FileRename[] renameFolders = new FileRename[0]; + FileRename[] moveFiles = new FileRename[0]; + if (files.size() == 1) { + FileRename renameEvent = files.get(0); + if (isFileNameRenameEvent(renameEvent)) { + renameFiles = new FileRename[] { renameEvent }; + } else if (isFolderRenameEvent(renameEvent)) { + renameFolders = new FileRename[] { renameEvent }; + } else if (isMoveEvent(renameEvent)) { + moveFiles = new FileRename[] { renameEvent }; + } + } else { + moveFiles = files.stream().filter(event -> isMoveEvent(event)).toArray(FileRename[]::new); + } + + if (renameFiles.length == 0 && renameFolders.length == 0 && moveFiles.length == 0) { + return null; + } + + SourcePath[] sourcePaths = getSourcePaths(); + if (sourcePaths == null || sourcePaths.length == 0) { return null; } - SubMonitor submonitor = SubMonitor.convert(monitor, "Computing rename updates...", 100 * files.length); WorkspaceEdit root = null; - for (FileRenameEvent event : files) { - String oldUri = event.oldUri; - String newUri = event.newUri; - ICompilationUnit unit = JDTUtils.resolveCompilationUnit(newUri); + SubMonitor submonitor = SubMonitor.convert(monitor, "Computing rename updates...", renameFiles.length + renameFolders.length + moveFiles.length); + if (renameFiles.length > 0) { + WorkspaceEdit edit = computeFileRenameEdit(renameFiles, submonitor.split(renameFiles.length)); + root = ChangeUtil.mergeChanges(root, edit, true); + } + + if (renameFolders.length > 0) { + WorkspaceEdit edit = computePackageRenameEdit(renameFolders, sourcePaths, submonitor.split(renameFolders.length)); + root = ChangeUtil.mergeChanges(root, edit, true); + } + + if (moveFiles.length > 0) { + WorkspaceEdit edit = computeMoveEdit(moveFiles, sourcePaths, submonitor.split(moveFiles.length)); + root = ChangeUtil.mergeChanges(root, edit, true); + } + + submonitor.done(); + return ChangeUtil.hasChanges(root) ? root : null; + } + + private static WorkspaceEdit computeFileRenameEdit(FileRename[] renameEvents, IProgressMonitor monitor) { + SubMonitor submonitor = SubMonitor.convert(monitor, "Computing file rename updates...", 100 * renameEvents.length); + WorkspaceEdit root = null; + for (FileRename event : renameEvents) { + String oldUri = event.getOldUri(); + String newUri = event.getNewUri(); + ICompilationUnit unit = JDTUtils.resolveCompilationUnit(oldUri); SubMonitor splitedMonitor = submonitor.split(100); try { - if (unit != null && !unit.exists()) { - final ICompilationUnit[] units = new ICompilationUnit[1]; - units[0] = unit; - try { - ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() { - @Override - public void run(IProgressMonitor monitor) throws CoreException { - units[0] = createCompilationUnit(units[0]); - } - }, new NullProgressMonitor()); - } catch (CoreException e) { - JavaLanguageServerPlugin.logException(e.getMessage(), e); - } - unit = units[0]; + if (unit == null || !unit.exists()) { + JavaLanguageServerPlugin.logError("Failed to compute the file rename edit because the file '" + oldUri + "' doesn't exist."); + continue; } if (unit != null) { @@ -95,7 +131,7 @@ public void run(IProgressMonitor monitor) throws CoreException { } } } catch (CoreException e) { - JavaLanguageServerPlugin.logException("Computing the rename edit: ", e); + JavaLanguageServerPlugin.logException("Computing the file rename edit: ", e); } finally { splitedMonitor.done(); } @@ -105,73 +141,148 @@ public void run(IProgressMonitor monitor) throws CoreException { return root; } - public static WorkspaceEdit handleWillRenameFiles(FileRenameParams params, IProgressMonitor monitor) { - if (params.files == null || params.files.isEmpty()) { - return null; + private static WorkspaceEdit computePackageRenameEdit(FileRename[] renameEvents, SourcePath[] sourcePaths, IProgressMonitor monitor) { + WorkspaceEdit[] root = new WorkspaceEdit[1]; + SubMonitor submonitor = SubMonitor.convert(monitor, "Computing package rename updates...", 100 * renameEvents.length); + for (FileRename event : renameEvents) { + IPath oldLocation = ResourceUtils.filePathFromURI(event.getOldUri()); + IPath newLocation = ResourceUtils.filePathFromURI(event.getNewUri()); + IPackageFragment oldPackageFragment = resolvePackageFragment(oldLocation, sourcePaths); + SubMonitor renameMonitor = submonitor.split(100); + try { + if (oldPackageFragment != null && !oldPackageFragment.isDefaultPackage() && oldPackageFragment.getResource() != null) { + String newPackageName = resolvePackageName(newLocation, sourcePaths); + if (newPackageName == null) { + continue; + } + + oldPackageFragment.getResource().refreshLocal(IResource.DEPTH_INFINITE, null); + if (oldPackageFragment.exists()) { + ResourcesPlugin.getWorkspace().run((pm) -> { + WorkspaceEdit edit = getRenameEdit(oldPackageFragment, newPackageName, pm); + root[0] = ChangeUtil.mergeChanges(root[0], edit, true); + }, oldPackageFragment.getSchedulingRule(), IResource.NONE, renameMonitor); + } + } + } catch (CoreException e) { + JavaLanguageServerPlugin.logException("Failed to compute the package rename update", e); + } finally { + renameMonitor.done(); + } } - FileRenameEvent[] renamefolders = params.files.stream().filter(event -> isFolderRenameEvent(event)).toArray(FileRenameEvent[]::new); - if (renamefolders.length == 0) { + submonitor.done(); + return ChangeUtil.hasChanges(root[0]) ? root[0] : null; + } + + private static WorkspaceEdit computeMoveEdit(FileRename[] moveEvents, SourcePath[] sourcePaths, IProgressMonitor monitor) { + IPath[] newPaths = Stream.of(moveEvents).map(event -> ResourceUtils.filePathFromURI(event.getNewUri())).toArray(IPath[]::new); + IPath destinationPath = ResourceUtils.getLongestCommonPath(newPaths); + if (destinationPath == null) { return null; } - SourcePath[] sourcePaths = getSourcePaths(); - if (sourcePaths == null || sourcePaths.length == 0) { + // Verify all files are moving to the same destination. + for (FileRename event : moveEvents) { + IPath oldPath = ResourceUtils.filePathFromURI(event.getOldUri()); + IPath expectedNewPath = destinationPath.append(oldPath.lastSegment()); + IPath actualNewPath = ResourceUtils.filePathFromURI(event.getNewUri()); + if (!Objects.equals(expectedNewPath, actualNewPath)) { + JavaLanguageServerPlugin.logError("Failed to compute move refactoring because the files are not moving to the same destination " + destinationPath.toOSString()); + return null; + } + } + + IPackageFragment destinationPackage = resolvePackageFragment(destinationPath, sourcePaths); + if (destinationPackage == null) { return null; } - return computePackageRenameEdit(renamefolders, sourcePaths, monitor); - } + // formatter:off + ICompilationUnit[] cus = Stream.of(moveEvents) + .filter(event -> { + IPath oldPath = ResourceUtils.filePathFromURI(event.getOldUri()); + return oldPath != null && oldPath.toFile().isFile(); + }).map(event -> JDTUtils.resolveCompilationUnit(event.getOldUri())) + .filter(cu -> cu != null && cu.getJavaProject() != null) + .toArray(ICompilationUnit[]::new); + // formatter:on + List nonClasspathCus = new ArrayList<>(); + for (ICompilationUnit unit : cus) { + if (!unit.getJavaProject().isOnClasspath(unit)) { + nonClasspathCus.add(unit); + } + } - private static WorkspaceEdit computePackageRenameEdit(FileRenameEvent[] renameEvents, SourcePath[] sourcePaths, IProgressMonitor monitor) { WorkspaceEdit[] root = new WorkspaceEdit[1]; - SubMonitor submonitor = SubMonitor.convert(monitor, "Computing package rename updates...", 100 * renameEvents.length); - for (FileRenameEvent event : renameEvents) { - IPath oldLocation = ResourceUtils.filePathFromURI(event.oldUri); - IPath newLocation = ResourceUtils.filePathFromURI(event.newUri); - for (SourcePath sourcePath : sourcePaths) { - IPath sourceLocation = Path.fromOSString(sourcePath.path); - IPath sourceEntry = Path.fromOSString(sourcePath.classpathEntry); - if (sourceLocation.isPrefixOf(oldLocation)) { - SubMonitor renameMonitor = submonitor.split(100); + if (cus.length > 0) { + try { + // For the cu that's not on the project's classpath, need to become workingcopy first, + // otherwise invoking cu.getBuffer() will throw exception. + for (ICompilationUnit cu : nonClasspathCus) { + cu.becomeWorkingCopy(monitor); + } + IReorgDestination packageDestination = ReorgDestinationFactory.createDestination(destinationPackage); + ResourcesPlugin.getWorkspace().run((pm) -> { + root[0] = MoveHandler.move(new IResource[0], cus, packageDestination, true, pm); + }, monitor); + } catch (CoreException e) { + JavaLanguageServerPlugin.logException("Failed to compute the move update", e); + } finally { + for (ICompilationUnit cu : nonClasspathCus) { try { - IJavaProject javaProject = ProjectUtils.getJavaProject(sourcePath.projectName); - if (javaProject == null) { - break; - } - - IPackageFragmentRoot packageRoot = javaProject.findPackageFragmentRoot(sourceEntry); - if (packageRoot == null) { - break; - } - - String oldPackageName = String.join(".", oldLocation.makeRelativeTo(sourceLocation).segments()); - String newPackageName = String.join(".", newLocation.makeRelativeTo(sourceLocation).segments()); - IPackageFragment oldPackageFragment = packageRoot.getPackageFragment(oldPackageName); - if (oldPackageFragment != null && !oldPackageFragment.isDefaultPackage() && oldPackageFragment.getResource() != null) { - oldPackageFragment.getResource().refreshLocal(IResource.DEPTH_INFINITE, null); - if (oldPackageFragment.exists()) { - ResourcesPlugin.getWorkspace().run((pm) -> { - WorkspaceEdit edit = getRenameEdit(oldPackageFragment, newPackageName, pm); - root[0] = ChangeUtil.mergeChanges(root[0], edit, true); - }, oldPackageFragment.getSchedulingRule(), IResource.NONE, renameMonitor); - } - } - } catch (CoreException e) { - JavaLanguageServerPlugin.logException("Failed to compute the package rename update", e); - } finally { - renameMonitor.done(); + cu.discardWorkingCopy(); + } catch (JavaModelException e) { + // do nothing } - - break; } } } - submonitor.done(); return ChangeUtil.hasChanges(root[0]) ? root[0] : null; } + private static String resolvePackageName(IPath javaElementLocation, SourcePath[] sourcePaths) { + return (String) resolvePackage(javaElementLocation, sourcePaths, false); + } + + private static IPackageFragment resolvePackageFragment(IPath javaElementLocation, SourcePath[] sourcePaths) { + return (IPackageFragment) resolvePackage(javaElementLocation, sourcePaths, true); + } + + private static Object resolvePackage(IPath javaElementLocation, SourcePath[] sourcePaths, boolean returnModel) { + for (SourcePath sourcePath : sourcePaths) { + IPath sourceLocation = Path.fromOSString(sourcePath.path); + IPath sourceEntry = Path.fromOSString(sourcePath.classpathEntry); + if (sourceLocation.isPrefixOf(javaElementLocation)) { + try { + IJavaProject javaProject = ProjectUtils.getJavaProject(sourcePath.projectName); + if (javaProject == null) { + return null; + } + + IPackageFragmentRoot packageRoot = javaProject.findPackageFragmentRoot(sourceEntry); + if (packageRoot == null) { + return null; + } + + String packageName = String.join(".", javaElementLocation.makeRelativeTo(sourceLocation).segments()); + if (returnModel) { + return packageRoot.getPackageFragment(packageName); + } + + return packageName; + } catch (CoreException e) { + JavaLanguageServerPlugin.logException("Failed to resolve the package fragment", e); + } + + return null; + } + } + + return null; + } + private static SourcePath[] getSourcePaths() { SourcePath[] sourcePaths = new SourcePath[0]; ListCommandResult result = (ListCommandResult) BuildPathCommand.listSourcePaths(); @@ -186,18 +297,42 @@ private static SourcePath[] getSourcePaths() { return sourcePaths; } - private static boolean isFileNameRenameEvent(FileRenameEvent event) { - IPath oldPath = ResourceUtils.filePathFromURI(event.oldUri); - IPath newPath = ResourceUtils.filePathFromURI(event.newUri); - return newPath.toFile().isFile() && oldPath.lastSegment().endsWith(".java") - && newPath.lastSegment().endsWith(".java") + private static boolean isFileNameRenameEvent(FileRename event) { + IPath oldPath = ResourceUtils.filePathFromURI(event.getOldUri()); + IPath newPath = ResourceUtils.filePathFromURI(event.getNewUri()); + if (oldPath == null || newPath == null) { + return false; + } + + return (oldPath.toFile().isFile() || newPath.toFile().isFile()) + && oldPath.lastSegment().endsWith(".java") && newPath.lastSegment().endsWith(".java") && Objects.equals(oldPath.removeLastSegments(1), newPath.removeLastSegments(1)); } - private static boolean isFolderRenameEvent(FileRenameEvent event) { - IPath oldPath = ResourceUtils.filePathFromURI(event.oldUri); - IPath newPath = ResourceUtils.filePathFromURI(event.newUri); - return (oldPath.toFile().isDirectory() || newPath.toFile().isDirectory()) && Objects.equals(oldPath.removeLastSegments(1), newPath.removeLastSegments(1)); + private static boolean isFolderRenameEvent(FileRename event) { + IPath oldPath = ResourceUtils.filePathFromURI(event.getOldUri()); + IPath newPath = ResourceUtils.filePathFromURI(event.getNewUri()); + if (oldPath == null || newPath == null) { + return false; + } + + return oldPath.toFile().isDirectory() || newPath.toFile().isDirectory(); + } + + /** + * The only move scenario that the upstream JDT supports is moving Java files to + * another package. It does not support moving a package to another package. So the + * language server only needs to handle the file move event, not the directory move event. + */ + private static boolean isMoveEvent(FileRename event) { + IPath oldPath = ResourceUtils.filePathFromURI(event.getOldUri()); + IPath newPath = ResourceUtils.filePathFromURI(event.getNewUri()); + if (oldPath == null || newPath == null) { + return false; + } + + return oldPath.toFile().isFile() && oldPath.lastSegment().endsWith(".java") + && Objects.equals(oldPath.lastSegment(), newPath.lastSegment()); } private static String getPrimaryTypeName(String uri) { @@ -210,24 +345,6 @@ private static String getPrimaryTypeName(String uri) { return fileName; } - private static ICompilationUnit createCompilationUnit(ICompilationUnit unit) { - try { - unit.getResource().refreshLocal(IResource.DEPTH_ONE, new NullProgressMonitor()); - if (unit.getResource().exists()) { - IJavaElement parent = unit.getParent(); - if (parent instanceof IPackageFragment) { - IPackageFragment pkg = (IPackageFragment) parent; - if (JavaModelManager.determineIfOnClasspath(unit.getResource(), unit.getJavaProject()) != null) { - unit = pkg.createCompilationUnit(unit.getElementName(), unit.getSource(), true, new NullProgressMonitor()); - } - } - } - } catch (CoreException e) { - JavaLanguageServerPlugin.logException(e.getMessage(), e); - } - return unit; - } - private static WorkspaceEdit getRenameEdit(IJavaElement targetElement, String newName, IProgressMonitor monitor) throws CoreException { RenameSupport renameSupport = RenameSupport.create(targetElement, newName, RenameSupport.UPDATE_REFERENCES); if (renameSupport == null) { @@ -251,28 +368,4 @@ private static WorkspaceEdit getRenameEdit(IJavaElement targetElement, String ne change.initializeValidationData(new NotCancelableProgressMonitor(submonitor.split(rtp.getInitializeChangeTicks()))); return ChangeUtil.convertToWorkspaceEdit(change); } - - public static class FileRenameEvent { - public String oldUri; - public String newUri; - - public FileRenameEvent() { - } - - public FileRenameEvent(String oldUri, String newUri) { - this.oldUri = oldUri; - this.newUri = newUri; - } - } - - public static class FileRenameParams { - public List files; - - public FileRenameParams() { - } - - public FileRenameParams(List files) { - this.files = files; - } - } } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java index 549536394c..99e5eeffcf 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/JDTLanguageServer.java @@ -48,7 +48,6 @@ import org.eclipse.jdt.ls.core.internal.LanguageServerWorkingCopyOwner; import org.eclipse.jdt.ls.core.internal.ServiceStatus; import org.eclipse.jdt.ls.core.internal.codemanipulation.GenerateGetterSetterOperation.AccessorField; -import org.eclipse.jdt.ls.core.internal.handlers.FileEventHandler.FileRenameParams; import org.eclipse.jdt.ls.core.internal.handlers.FindLinksHandler.FindLinksParams; import org.eclipse.jdt.ls.core.internal.handlers.GenerateAccessorsHandler.GenerateAccessorsParams; import org.eclipse.jdt.ls.core.internal.handlers.GenerateConstructorsHandler.CheckConstructorsResponse; @@ -123,6 +122,7 @@ import org.eclipse.lsp4j.PrepareRenameResult; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.ReferenceParams; +import org.eclipse.lsp4j.RenameFilesParams; import org.eclipse.lsp4j.RenameParams; import org.eclipse.lsp4j.SelectionRange; import org.eclipse.lsp4j.SelectionRangeParams; @@ -776,18 +776,10 @@ public void didSave(DidSaveTextDocumentParams params) { } @Override - public CompletableFuture didRenameFiles(FileRenameParams params) { - logInfo(">> document/didRenameFiles"); + public CompletableFuture willRenameFiles(RenameFilesParams params) { + logInfo(">> workspace/willRenameFiles"); return computeAsyncWithClientProgress((monitor) -> { waitForLifecycleJobs(monitor); - return FileEventHandler.handleRenameFiles(params, monitor); - }); - } - - @Override - public CompletableFuture willRenameFiles(FileRenameParams params) { - logInfo(">> document/willRenameFiles"); - return computeAsyncWithClientProgress((monitor) -> { return FileEventHandler.handleWillRenameFiles(params, monitor); }); } diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/MoveHandler.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/MoveHandler.java index 190bef0db4..6ab709a671 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/MoveHandler.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/handlers/MoveHandler.java @@ -385,7 +385,7 @@ private static RefactorWorkspaceEdit moveCU(String[] sourceUris, String targetUr } } - private static WorkspaceEdit move(IResource[] resources, IJavaElement[] javaElements, IReorgDestination destination, boolean updateReferences, IProgressMonitor monitor) throws CoreException { + public static WorkspaceEdit move(IResource[] resources, IJavaElement[] javaElements, IReorgDestination destination, boolean updateReferences, IProgressMonitor monitor) throws CoreException { IMovePolicy policy = ReorgPolicyFactory.createMovePolicy(resources, javaElements); if (policy.canEnable()) { JavaMoveProcessor processor = new JavaMoveProcessor(policy); diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/lsp/JavaProtocolExtensions.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/lsp/JavaProtocolExtensions.java index 084de2f691..5f59f15070 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/lsp/JavaProtocolExtensions.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/lsp/JavaProtocolExtensions.java @@ -17,7 +17,6 @@ import org.eclipse.jdt.ls.core.internal.BuildWorkspaceStatus; import org.eclipse.jdt.ls.core.internal.codemanipulation.GenerateGetterSetterOperation.AccessorField; -import org.eclipse.jdt.ls.core.internal.handlers.FileEventHandler.FileRenameParams; import org.eclipse.jdt.ls.core.internal.handlers.FindLinksHandler.FindLinksParams; import org.eclipse.jdt.ls.core.internal.handlers.GenerateAccessorsHandler.GenerateAccessorsParams; import org.eclipse.jdt.ls.core.internal.handlers.GenerateConstructorsHandler.CheckConstructorsResponse; @@ -124,10 +123,4 @@ public interface JavaProtocolExtensions { @JsonRequest CompletableFuture> findLinks(FindLinksParams params); - - @JsonRequest - CompletableFuture didRenameFiles(FileRenameParams params); - - @JsonRequest - CompletableFuture willRenameFiles(FileRenameParams params); } diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/ResourceUtilsTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/ResourceUtilsTest.java index 70873356c0..ef4ada94d5 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/ResourceUtilsTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/ResourceUtilsTest.java @@ -14,8 +14,10 @@ import static org.eclipse.jdt.ls.core.internal.ResourceUtils.toGlobPattern; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.junit.Test; @@ -30,4 +32,32 @@ public void testToGlobPattern() { assertEquals("**/foo/bar/**", toGlobPattern(Path.forWindows("c:\\foo\\bar"))); assertEquals("/foo/bar/foo.jar", toGlobPattern(Path.forPosix("/foo/bar/foo.jar"))); } + + @Test + public void testGetLongestCommonPath() { + assertNull(ResourceUtils.getLongestCommonPath(new IPath[0])); + IPath[] input = new IPath[] { + new Path("C:", "/test/test1.java"), + new Path("D:", "/test/test2.java") + }; + assertNull(ResourceUtils.getLongestCommonPath(input)); + + input = new IPath[] { + new Path("C:", "/work/src/org/eclipse/test1.java"), + new Path("C:", "/work/src/org/eclipse/test2.java"), + new Path("C:", "/work/src/org/eclipse/test3.java"), + }; + IPath commonPath = ResourceUtils.getLongestCommonPath(input); + assertNotNull(commonPath); + assertEquals("C:/work/src/org/eclipse", commonPath.toPortableString()); + + input = new IPath[] { + new Path("/work/src/org/eclipse/jdt/ls/test1.java"), + new Path("/work/src/org/eclipse/jdt/core/test2.java"), + new Path("/work/src/org/eclipse/test3.java"), + }; + commonPath = ResourceUtils.getLongestCommonPath(input); + assertNotNull(commonPath); + assertEquals("/work/src/org/eclipse", commonPath.toPortableString()); + } } diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/FileEventHandlerTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/FileEventHandlerTest.java index 1564936b92..e418ca019c 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/FileEventHandlerTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/FileEventHandlerTest.java @@ -15,97 +15,131 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; +import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.apache.commons.io.FileUtils; +import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.ls.core.internal.JDTUtils; import org.eclipse.jdt.ls.core.internal.TextEditUtil; -import org.eclipse.jdt.ls.core.internal.handlers.FileEventHandler.FileRenameEvent; -import org.eclipse.jdt.ls.core.internal.handlers.FileEventHandler.FileRenameParams; import org.eclipse.jdt.ls.core.internal.managers.AbstractProjectsManagerBasedTest; import org.eclipse.jdt.ls.core.internal.preferences.ClientPreferences; import org.eclipse.jface.text.BadLocationException; +import org.eclipse.lsp4j.FileRename; +import org.eclipse.lsp4j.RenameFilesParams; import org.eclipse.lsp4j.ResourceOperation; import org.eclipse.lsp4j.TextDocumentEdit; import org.eclipse.lsp4j.TextEdit; import org.eclipse.lsp4j.WorkspaceEdit; import org.eclipse.lsp4j.jsonrpc.messages.Either; +import org.junit.After; import org.junit.Before; import org.junit.Test; public class FileEventHandlerTest extends AbstractProjectsManagerBasedTest { private ClientPreferences clientPreferences; private IPackageFragmentRoot sourceFolder; + private IJavaProject javaProject; @Before public void setup() throws Exception { - IJavaProject javaProject = newEmptyProject(); + javaProject = newEmptyProject(); sourceFolder = javaProject.getPackageFragmentRoot(javaProject.getProject().getFolder("src")); clientPreferences = preferenceManager.getClientPreferences(); } + @After + public void tearDown() throws Exception { + for (ICompilationUnit cu : JavaCore.getWorkingCopies(null)) { + cu.discardWorkingCopy(); + } + } + @Test - public void testDidRenameFiles_fileNameRenamed() throws JavaModelException, BadLocationException { + public void testRenameFiles() throws JavaModelException, BadLocationException { when(clientPreferences.isResourceOperationSupported()).thenReturn(true); IPackageFragment pack1 = sourceFolder.createPackageFragment("test1", false, null); StringBuilder builderA = new StringBuilder(); builderA.append("package test1;\n"); - builderA.append("public class A {\n"); + builderA.append("public class ObjectA {\n"); builderA.append(" public void foo() {\n"); builderA.append(" }\n"); builderA.append("}\n"); - ICompilationUnit cuA = pack1.createCompilationUnit("ANew.java", builderA.toString(), false, null); - + ICompilationUnit cuA = pack1.createCompilationUnit("ObjectA.java", builderA.toString(), false, null); + StringBuilder builderB = new StringBuilder(); builderB.append("package test1;\n"); builderB.append("public class B {\n"); builderB.append(" public void foo() {\n"); - builderB.append(" A a = new A();\n"); + builderB.append(" ObjectA a = new ObjectA();\n"); builderB.append(" a.foo();\n"); builderB.append(" }\n"); builderB.append("}\n"); ICompilationUnit cuB = pack1.createCompilationUnit("B.java", builderB.toString(), false, null); String uriA = JDTUtils.toURI(cuA); - String oldUriA = uriA.replace("ANew", "A"); - WorkspaceEdit edit = FileEventHandler.handleRenameFiles(new FileRenameParams(Arrays.asList(new FileRenameEvent(oldUriA, uriA))), new NullProgressMonitor()); + String newUriA = uriA.replace("ObjectA", "ObjectA1"); + WorkspaceEdit edit = FileEventHandler.handleWillRenameFiles(new RenameFilesParams(Arrays.asList(new FileRename(uriA, newUriA))), new NullProgressMonitor()); assertNotNull(edit); assertEquals(2, edit.getDocumentChanges().size()); assertTrue(edit.getDocumentChanges().get(0).isLeft()); - assertEquals(edit.getDocumentChanges().get(0).getLeft().getTextDocument().getUri(), uriA); - assertEquals(TextEditUtil.apply(builderA.toString(), edit.getDocumentChanges().get(0).getLeft().getEdits()), + assertEquals(edit.getDocumentChanges().get(0).getLeft().getTextDocument().getUri(), JDTUtils.toURI(cuB)); + assertEquals(TextEditUtil.apply(builderB.toString(), edit.getDocumentChanges().get(0).getLeft().getEdits()), "package test1;\n" + - "public class ANew {\n" + + "public class B {\n" + " public void foo() {\n" + + " ObjectA1 a = new ObjectA1();\n" + + " a.foo();\n" + " }\n" + "}\n" ); assertTrue(edit.getDocumentChanges().get(1).isLeft()); - assertEquals(edit.getDocumentChanges().get(1).getLeft().getTextDocument().getUri(), JDTUtils.toURI(cuB)); - assertEquals(TextEditUtil.apply(builderB.toString(), edit.getDocumentChanges().get(1).getLeft().getEdits()), + assertEquals(edit.getDocumentChanges().get(1).getLeft().getTextDocument().getUri(), uriA); + assertEquals(TextEditUtil.apply(builderA.toString(), edit.getDocumentChanges().get(1).getLeft().getEdits()), "package test1;\n" + - "public class B {\n" + + "public class ObjectA1 {\n" + " public void foo() {\n" + - " ANew a = new ANew();\n" + - " a.foo();\n" + " }\n" + "}\n" ); } + @Test + public void testRenameFiles_refactoringExists() throws JavaModelException, BadLocationException { + when(clientPreferences.isResourceOperationSupported()).thenReturn(true); + + IPackageFragment pack1 = sourceFolder.createPackageFragment("test1", false, null); + StringBuilder builderA = new StringBuilder(); + builderA.append("package test1;\n"); + builderA.append("public class ObjectA1 {\n"); + builderA.append(" public void foo() {\n"); + builderA.append(" }\n"); + builderA.append("}\n"); + ICompilationUnit cuA = pack1.createCompilationUnit("ObjectA.java", builderA.toString(), false, null); + + String uriA = JDTUtils.toURI(cuA); + String newUriA = uriA.replace("ObjectA", "ObjectA1"); + WorkspaceEdit edit = FileEventHandler.handleWillRenameFiles(new RenameFilesParams(Arrays.asList(new FileRename(uriA, newUriA))), new NullProgressMonitor()); + assertNull(edit); + } + + // Test renaming package from "parent.pack2" to "parent.newpack2" @Test public void testRenamePackage() throws JavaModelException, BadLocationException { when(clientPreferences.isResourceOperationSupported()).thenReturn(true); @@ -135,7 +169,7 @@ public void testRenamePackage() throws JavaModelException, BadLocationException String pack2Uri = JDTUtils.getFileURI(pack2.getResource()); String newPack2Uri = pack2Uri.replace("pack2", "newpack2"); - WorkspaceEdit edit = FileEventHandler.handleWillRenameFiles(new FileRenameParams(Arrays.asList(new FileRenameEvent(pack2Uri, newPack2Uri))), new NullProgressMonitor()); + WorkspaceEdit edit = FileEventHandler.handleWillRenameFiles(new RenameFilesParams(Arrays.asList(new FileRename(pack2Uri, newPack2Uri))), new NullProgressMonitor()); assertNotNull(edit); List> documentChanges = edit.getDocumentChanges(); assertEquals(2, documentChanges.size()); @@ -164,6 +198,65 @@ public void testRenamePackage() throws JavaModelException, BadLocationException ); } + // Test renaming package from "parent.pack2" to "newparent.newpack2" + @Test + public void testRenamePackage2() throws JavaModelException, BadLocationException { + when(clientPreferences.isResourceOperationSupported()).thenReturn(true); + + IPackageFragment pack1 = sourceFolder.createPackageFragment("parent.pack1", false, null); + IPackageFragment pack2 = sourceFolder.createPackageFragment("parent.pack2", false, null); + + StringBuilder codeA = new StringBuilder(); + codeA.append("package parent.pack1;\n"); + codeA.append("import parent.pack2.B;\n"); + codeA.append("public class A {\n"); + codeA.append(" public void foo() {\n"); + codeA.append(" B b = new B();\n"); + codeA.append(" b.foo();\n"); + codeA.append(" }\n"); + codeA.append("}\n"); + + StringBuilder codeB = new StringBuilder(); + codeB.append("package parent.pack2;\n"); + codeB.append("public class B {\n"); + codeB.append(" public B() {}\n"); + codeB.append(" public void foo() {}\n"); + codeB.append("}\n"); + + ICompilationUnit cuA = pack1.createCompilationUnit("A.java", codeA.toString(), false, null); + ICompilationUnit cuB = pack2.createCompilationUnit("B.java", codeB.toString(), false, null); + + String pack2Uri = JDTUtils.getFileURI(pack2.getResource()); + String newPack2Uri = pack2Uri.replace("pack2", "newpack2").replace("parent", "newparent"); + WorkspaceEdit edit = FileEventHandler.handleWillRenameFiles(new RenameFilesParams(Arrays.asList(new FileRename(pack2Uri, newPack2Uri))), new NullProgressMonitor()); + assertNotNull(edit); + List> documentChanges = edit.getDocumentChanges(); + assertEquals(2, documentChanges.size()); + + assertTrue(documentChanges.get(0).isLeft()); + assertEquals(documentChanges.get(0).getLeft().getTextDocument().getUri(), JDTUtils.toURI(cuA)); + assertEquals(TextEditUtil.apply(codeA.toString(), documentChanges.get(0).getLeft().getEdits()), + "package parent.pack1;\n" + + "import newparent.newpack2.B;\n" + + "public class A {\n" + + " public void foo() {\n" + + " B b = new B();\n" + + " b.foo();\n" + + " }\n" + + "}\n" + ); + + assertTrue(documentChanges.get(1).isLeft()); + assertEquals(documentChanges.get(1).getLeft().getTextDocument().getUri(), JDTUtils.toURI(cuB)); + assertEquals(TextEditUtil.apply(codeB.toString(), documentChanges.get(1).getLeft().getEdits()), + "package newparent.newpack2;\n" + + "public class B {\n" + + " public B() {}\n" + + " public void foo() {}\n" + + "}\n" + ); + } + @Test public void testRenameSubPackage() throws JavaModelException, BadLocationException { when(clientPreferences.isResourceOperationSupported()).thenReturn(true); @@ -194,7 +287,7 @@ public void testRenameSubPackage() throws JavaModelException, BadLocationExcepti String parentPackUri = JDTUtils.getFileURI(parentPack.getResource()); String newParentPackUri = parentPackUri.replace("parent", "newparent"); - WorkspaceEdit edit = FileEventHandler.handleWillRenameFiles(new FileRenameParams(Arrays.asList(new FileRenameEvent(parentPackUri, newParentPackUri))), new NullProgressMonitor()); + WorkspaceEdit edit = FileEventHandler.handleWillRenameFiles(new RenameFilesParams(Arrays.asList(new FileRename(parentPackUri, newParentPackUri))), new NullProgressMonitor()); assertNotNull(edit); List> documentChanges = edit.getDocumentChanges(); assertEquals(3, documentChanges.size()); @@ -227,4 +320,165 @@ public void testRenameSubPackage() throws JavaModelException, BadLocationExcepti "}\n" ); } + + @Test + public void testMoveMultiFiles() throws JavaModelException, BadLocationException { + when(clientPreferences.isResourceOperationSupported()).thenReturn(true); + + IPackageFragment pack1 = sourceFolder.createPackageFragment("jdtls.test1", false, null); + //@formatter:off + ICompilationUnit unitA = pack1.createCompilationUnit("A.java", "package jdtls.test1;\r\n" + + "\r\n" + + "public class A {\r\n" + + " private B b = new B();\r\n" + + "}", true, null); + //@formatter:on + + //@formatter:off + ICompilationUnit unitB = pack1.createCompilationUnit("B.java", "package jdtls.test1;\r\n" + + "\r\n" + + "public class B {\r\n" + + "}", true, null); + //@formatter:on + + //@formatter:off + ICompilationUnit unitC = pack1.createCompilationUnit("C.java", "package jdtls.test1;\r\n" + + "\r\n" + + "public class C {\r\n" + + " private B b = new B();\r\n" + + "}", true, null); + //@formatter:on + + IPackageFragment pack2 = sourceFolder.createPackageFragment("jdtls.test2", false, null); + + String uriA = JDTUtils.toURI(unitA); + String uriB = JDTUtils.toURI(unitB); + String newUriA = uriA.replace("test1", "test2"); + String newUriB = uriB.replace("test1", "test2"); + WorkspaceEdit edit = FileEventHandler.handleWillRenameFiles( + new RenameFilesParams(Arrays.asList( + new FileRename(uriA, newUriA), + new FileRename(uriB, newUriB))), + new NullProgressMonitor()); + + assertNotNull(edit); + List> changes = edit.getDocumentChanges(); + assertEquals(3, changes.size()); + + //@formatter:off + String expected = "package jdtls.test1;\r\n" + + "\r\n" + + "import jdtls.test2.B;\r\n" + + "\r\n" + + "public class C {\r\n" + + " private B b = new B();\r\n" + + "}"; + //@formatter:on + TextDocumentEdit textEdit = changes.get(0).getLeft(); + assertNotNull(textEdit); + List edits = new ArrayList<>(textEdit.getEdits()); + assertEquals(expected, TextEditUtil.apply(unitC.getSource(), edits)); + + //@formatter:off + expected = "package jdtls.test2;\r\n" + + "\r\n" + + "public class B {\r\n" + + "}"; + //@formatter:on + textEdit = changes.get(1).getLeft(); + assertNotNull(textEdit); + edits = new ArrayList<>(textEdit.getEdits()); + assertEquals(expected, TextEditUtil.apply(unitB.getSource(), edits)); + + //@formatter:off + expected = "package jdtls.test2;\r\n" + + "\r\n" + + "public class A {\r\n" + + " private B b = new B();\r\n" + + "}"; + //@formatter:on + textEdit = changes.get(2).getLeft(); + assertNotNull(textEdit); + edits = new ArrayList<>(textEdit.getEdits()); + assertEquals(expected, TextEditUtil.apply(unitA.getSource(), edits)); + } + + @Test + public void testMoveMultiFiles_differentDestination() throws JavaModelException, BadLocationException { + when(clientPreferences.isResourceOperationSupported()).thenReturn(true); + + IPackageFragment pack1 = sourceFolder.createPackageFragment("jdtls.test1", false, null); + //@formatter:off + ICompilationUnit unitA = pack1.createCompilationUnit("A.java", "package jdtls.test1;\r\n" + + "\r\n" + + "public class A {\r\n" + + " private B b = new B();\r\n" + + "}", true, null); + //@formatter:on + + //@formatter:off + ICompilationUnit unitB = pack1.createCompilationUnit("B.java", "package jdtls.test1;\r\n" + + "\r\n" + + "public class B {\r\n" + + "}", true, null); + //@formatter:on + + //@formatter:off + ICompilationUnit unitC = pack1.createCompilationUnit("C.java", "package jdtls.test1;\r\n" + + "\r\n" + + "public class C {\r\n" + + " private B b = new B();\r\n" + + "}", true, null); + //@formatter:on + + IPackageFragment pack2 = sourceFolder.createPackageFragment("jdtls.test2", false, null); + IPackageFragment pack3 = sourceFolder.createPackageFragment("jdtls.test3", false, null); + + String uriA = JDTUtils.toURI(unitA); + String uriB = JDTUtils.toURI(unitB); + String newUriA = uriA.replace("test1", "test2"); + String newUriB = uriB.replace("test1", "test3"); + WorkspaceEdit edit = FileEventHandler.handleWillRenameFiles( + new RenameFilesParams(Arrays.asList( + new FileRename(uriA, newUriA), + new FileRename(uriB, newUriB))), + new NullProgressMonitor()); + assertNull(edit); + } + + @Test + public void testMoveNonClasspathFile() throws Exception { + when(clientPreferences.isResourceOperationSupported()).thenReturn(true); + + IPackageFragment pack1 = sourceFolder.createPackageFragment("jdtls.test1", true, null); + File projectRoot = javaProject.getProject().getLocation().toFile(); + File file = new File(projectRoot, "Bar.java"); + file.createNewFile(); + String contents = "public class Bar {\r\n}"; + FileUtils.writeStringToFile(file, contents); + ICompilationUnit bar = JDTUtils.resolveCompilationUnit(file.toURI()); + bar.getResource().refreshLocal(IResource.DEPTH_ONE, null); + + String uri = JDTUtils.toURI(bar); + String newUri = uri.replace("Bar.java", "src/jdtls/test1/Bar.java"); + WorkspaceEdit edit = FileEventHandler.handleWillRenameFiles( + new RenameFilesParams(Arrays.asList(new FileRename(uri, newUri))), + new NullProgressMonitor()); + + assertNotNull(edit); + List> changes = edit.getDocumentChanges(); + assertEquals(1, changes.size()); + + //@formatter:off + String expected = "package jdtls.test1;\r\n" + + "public class Bar {\r\n" + + "}"; + //@formatter:on + TextDocumentEdit textEdit = changes.get(0).getLeft(); + assertNotNull(textEdit); + List edits = new ArrayList<>(textEdit.getEdits()); + bar.becomeWorkingCopy(null); + assertEquals(expected, TextEditUtil.apply(bar.getSource(), edits)); + bar.discardWorkingCopy(); + } } diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/MoveHandlerTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/MoveHandlerTest.java index 4df39c3ec8..1b19b86881 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/MoveHandlerTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/handlers/MoveHandlerTest.java @@ -163,7 +163,7 @@ public void testMoveFile() throws JavaModelException, BadLocationException { assertNotNull(refactorEdit); assertNotNull(refactorEdit.edit); List> changes = refactorEdit.edit.getDocumentChanges(); - assertEquals(4, changes.size()); + assertEquals(3, changes.size()); //@formatter:off String expected = "package jdtls.test1;\r\n" + @@ -186,12 +186,9 @@ public void testMoveFile() throws JavaModelException, BadLocationException { textEdit = changes.get(1).getLeft(); assertNotNull(textEdit); List edits = new ArrayList<>(textEdit.getEdits()); - textEdit = changes.get(2).getLeft(); - assertNotNull(textEdit); - edits.addAll(textEdit.getEdits()); assertEquals(expected, TextEditUtil.apply(unitB.getSource(), edits)); - RenameFile renameFile = (RenameFile) changes.get(3).getRight(); + RenameFile renameFile = (RenameFile) changes.get(2).getRight(); assertNotNull(renameFile); assertEquals(JDTUtils.toURI(unitB), renameFile.getOldUri()); assertEquals(ResourceUtils.fixURI(unitB.getResource().getRawLocationURI()).replace("test2", "test3"), renameFile.getNewUri()); @@ -221,43 +218,37 @@ public void testMoveMultiFiles() throws JavaModelException, BadLocationException assertNotNull(refactorEdit); assertNotNull(refactorEdit.edit); List> changes = refactorEdit.edit.getDocumentChanges(); - assertEquals(6, changes.size()); + assertEquals(4, changes.size()); //@formatter:off String expected = "package jdtls.test2;\r\n" + "\r\n" + - "public class A {\r\n" + - " private B b = new B();\r\n" + + "public class B {\r\n" + "}"; //@formatter:on TextDocumentEdit textEdit = changes.get(0).getLeft(); assertNotNull(textEdit); List edits = new ArrayList<>(textEdit.getEdits()); - textEdit = changes.get(4).getLeft(); - assertNotNull(textEdit); - edits.addAll(textEdit.getEdits()); - assertEquals(expected, TextEditUtil.apply(unitA.getSource(), edits)); + assertEquals(expected, TextEditUtil.apply(unitB.getSource(), edits)); + + RenameFile renameFileB = (RenameFile) changes.get(1).getRight(); + assertNotNull(renameFileB); + assertEquals(JDTUtils.toURI(unitB), renameFileB.getOldUri()); + assertEquals(ResourceUtils.fixURI(unitB.getResource().getRawLocationURI()).replace("test1", "test2"), renameFileB.getNewUri()); //@formatter:off expected = "package jdtls.test2;\r\n" + "\r\n" + - "public class B {\r\n" + + "public class A {\r\n" + + " private B b = new B();\r\n" + "}"; //@formatter:on - textEdit = changes.get(1).getLeft(); - assertNotNull(textEdit); - edits = new ArrayList<>(textEdit.getEdits()); textEdit = changes.get(2).getLeft(); assertNotNull(textEdit); - edits.addAll(textEdit.getEdits()); - assertEquals(expected, TextEditUtil.apply(unitB.getSource(), edits)); - - RenameFile renameFileB = (RenameFile) changes.get(3).getRight(); - assertNotNull(renameFileB); - assertEquals(JDTUtils.toURI(unitB), renameFileB.getOldUri()); - assertEquals(ResourceUtils.fixURI(unitB.getResource().getRawLocationURI()).replace("test1", "test2"), renameFileB.getNewUri()); + edits = new ArrayList<>(textEdit.getEdits()); + assertEquals(expected, TextEditUtil.apply(unitA.getSource(), edits)); - RenameFile renameFileA = (RenameFile) changes.get(5).getRight(); + RenameFile renameFileA = (RenameFile) changes.get(3).getRight(); assertNotNull(renameFileA); assertEquals(JDTUtils.toURI(unitA), renameFileA.getOldUri()); assertEquals(ResourceUtils.fixURI(unitA.getResource().getRawLocationURI()).replace("test1", "test2"), renameFileA.getNewUri());