2 * Copyright 2000-2016 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.refactoring.copy;
18 import com.intellij.CommonBundle;
19 import com.intellij.ide.CopyPasteDelegator;
20 import com.intellij.ide.util.EditorHelper;
21 import com.intellij.ide.util.PlatformPackageUtil;
22 import com.intellij.openapi.application.ApplicationManager;
23 import com.intellij.openapi.application.Result;
24 import com.intellij.openapi.application.WriteAction;
25 import com.intellij.openapi.command.WriteCommandAction;
26 import com.intellij.openapi.diagnostic.Logger;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.roots.ProjectRootManager;
29 import com.intellij.openapi.ui.Messages;
30 import com.intellij.openapi.util.ThrowableComputable;
31 import com.intellij.openapi.vfs.VfsUtil;
32 import com.intellij.openapi.vfs.VirtualFile;
33 import com.intellij.openapi.vfs.encoding.EncodingRegistry;
34 import com.intellij.openapi.wm.ToolWindowManager;
35 import com.intellij.psi.*;
36 import com.intellij.psi.impl.file.PsiDirectoryFactory;
37 import com.intellij.psi.util.PsiTreeUtil;
38 import com.intellij.refactoring.RefactoringBundle;
39 import com.intellij.refactoring.move.moveFilesOrDirectories.MoveFilesOrDirectoriesUtil;
40 import com.intellij.refactoring.util.CommonRefactoringUtil;
41 import com.intellij.util.ArrayUtil;
42 import com.intellij.util.IncorrectOperationException;
43 import org.jetbrains.annotations.NotNull;
44 import org.jetbrains.annotations.Nullable;
46 import java.io.IOException;
47 import java.util.Collections;
48 import java.util.HashSet;
51 public class CopyFilesOrDirectoriesHandler extends CopyHandlerDelegateBase {
52 private static Logger LOG = Logger.getInstance("com.intellij.refactoring.copy.CopyFilesOrDirectoriesHandler");
55 public boolean canCopy(PsiElement[] elements, boolean fromUpdate) {
56 Set<String> names = new HashSet<>();
57 for (PsiElement element : elements) {
58 if (!(element instanceof PsiFileSystemItem)) return false;
59 if (!element.isValid()) return false;
60 if (element instanceof PsiCompiledFile) return false;
62 String name = ((PsiFileSystemItem) element).getName();
63 if (names.contains(name)) {
69 PsiElement[] filteredElements = PsiTreeUtil.filterAncestors(elements);
70 return filteredElements.length == elements.length;
74 public void doCopy(final PsiElement[] elements, PsiDirectory defaultTargetDirectory) {
75 if (defaultTargetDirectory == null) {
76 defaultTargetDirectory = getCommonParentDirectory(elements);
78 Project project = defaultTargetDirectory != null ? defaultTargetDirectory.getProject() : elements [0].getProject();
79 if (defaultTargetDirectory != null) {
80 defaultTargetDirectory = resolveDirectory(defaultTargetDirectory);
81 if (defaultTargetDirectory == null) return;
84 defaultTargetDirectory = tryNotNullizeDirectory(project, defaultTargetDirectory);
86 copyAsFiles(elements, defaultTargetDirectory, project);
90 private static PsiDirectory tryNotNullizeDirectory(@NotNull Project project, @Nullable PsiDirectory defaultTargetDirectory) {
91 if (defaultTargetDirectory == null) {
92 VirtualFile root = ArrayUtil.getFirstElement(ProjectRootManager.getInstance(project).getContentRoots());
93 if (root == null) root = project.getBaseDir();
94 if (root == null) root = VfsUtil.getUserHomeDir();
95 defaultTargetDirectory = root != null ? PsiManager.getInstance(project).findDirectory(root) : null;
97 if (defaultTargetDirectory == null) {
98 LOG.warn("No directory found for project: " + project.getName() +", root: " + root);
101 return defaultTargetDirectory;
104 public static void copyAsFiles(PsiElement[] elements, @Nullable PsiDirectory defaultTargetDirectory, Project project) {
105 doCopyAsFiles(elements, defaultTargetDirectory, project);
108 private static void doCopyAsFiles(PsiElement[] elements, @Nullable PsiDirectory defaultTargetDirectory, Project project) {
109 PsiDirectory targetDirectory = null;
110 String newName = null;
111 boolean openInEditor = true;
113 if (ApplicationManager.getApplication().isUnitTestMode()) {
114 targetDirectory = defaultTargetDirectory;
117 CopyFilesOrDirectoriesDialog dialog = new CopyFilesOrDirectoriesDialog(elements, defaultTargetDirectory, project, false);
118 if (dialog.showAndGet()) {
119 newName = elements.length == 1 ? dialog.getNewName() : null;
120 targetDirectory = dialog.getTargetDirectory();
121 openInEditor = dialog.openInEditor();
125 if (targetDirectory != null) {
127 for (PsiElement element : elements) {
128 PsiFileSystemItem psiElement = (PsiFileSystemItem)element;
129 if (psiElement.isDirectory()) {
130 MoveFilesOrDirectoriesUtil.checkIfMoveIntoSelf(psiElement, targetDirectory);
134 catch (IncorrectOperationException e) {
135 CommonRefactoringUtil.showErrorHint(project, null, e.getMessage(), CommonBundle.getErrorTitle(), null);
139 copyImpl(elements, newName, targetDirectory, false, openInEditor);
144 public void doClone(final PsiElement element) {
145 doCloneFile(element);
148 public static void doCloneFile(PsiElement element) {
149 PsiDirectory targetDirectory;
150 if (element instanceof PsiDirectory) {
151 targetDirectory = ((PsiDirectory)element).getParentDirectory();
154 targetDirectory = PlatformPackageUtil.getDirectory(element);
156 targetDirectory = tryNotNullizeDirectory(element.getProject(), targetDirectory);
157 if (targetDirectory == null) return;
159 PsiElement[] elements = {element};
160 CopyFilesOrDirectoriesDialog dialog = new CopyFilesOrDirectoriesDialog(elements, null, element.getProject(), true);
161 if (dialog.showAndGet()) {
162 String newName = dialog.getNewName();
163 copyImpl(elements, newName, targetDirectory, true, true);
168 private static PsiDirectory getCommonParentDirectory(PsiElement[] elements){
169 PsiDirectory result = null;
171 for (PsiElement element : elements) {
172 PsiDirectory directory;
174 if (element instanceof PsiDirectory) {
175 directory = (PsiDirectory)element;
176 directory = directory.getParentDirectory();
178 else if (element instanceof PsiFile) {
179 directory = PlatformPackageUtil.getDirectory(element);
182 throw new IllegalArgumentException("unexpected element " + element);
185 if (directory == null) continue;
187 if (result == null) {
191 if (PsiTreeUtil.isAncestor(directory, result, true)) {
202 * @param newName can be not null only if elements.length == 1
203 * @param targetDirectory
204 * @param openInEditor
206 private static void copyImpl(@NotNull final PsiElement[] elements,
207 @Nullable final String newName,
208 @NotNull final PsiDirectory targetDirectory,
209 final boolean doClone,
210 final boolean openInEditor) {
211 if (doClone && elements.length != 1) {
212 throw new IllegalArgumentException("invalid number of elements to clone:" + elements.length);
215 if (newName != null && elements.length != 1) {
216 throw new IllegalArgumentException("no new name should be set; number of elements is: " + elements.length);
219 final Project project = targetDirectory.getProject();
220 if (!CommonRefactoringUtil.checkReadOnlyStatus(project, Collections.singleton(targetDirectory), false)) {
224 String title = RefactoringBundle.message(doClone ? "copy,handler.clone.files.directories" : "copy.handler.copy.files.directories");
226 PsiFile firstFile = null;
227 final int[] choice = elements.length > 1 || elements[0] instanceof PsiDirectory ? new int[]{-1} : null;
228 for (PsiElement element : elements) {
229 PsiFile f = copyToDirectory((PsiFileSystemItem)element, newName, targetDirectory, choice, title);
230 if (firstFile == null) {
235 if (firstFile != null && openInEditor) {
236 CopyHandler.updateSelectionInActiveProjectView(firstFile, project, doClone);
237 if (!(firstFile instanceof PsiBinaryFile)) {
238 EditorHelper.openInEditor(firstFile);
239 ToolWindowManager.getInstance(project).activateEditorComponent();
243 catch (final IncorrectOperationException ex) {
244 Messages.showErrorDialog(project, ex.getMessage(), RefactoringBundle.message("error.title"));
246 catch (final IOException ex) {
247 Messages.showErrorDialog(project, ex.getMessage(), RefactoringBundle.message("error.title"));
252 * @param elementToCopy PsiFile or PsiDirectory
253 * @param newName can be not null only if elements.length == 1
254 * @return first copied PsiFile (recursively); null if no PsiFiles copied
257 public static PsiFile copyToDirectory(@NotNull PsiFileSystemItem elementToCopy,
258 @Nullable String newName,
259 @NotNull PsiDirectory targetDirectory) throws IncorrectOperationException, IOException {
260 return copyToDirectory(elementToCopy, newName, targetDirectory, null, null);
264 * @param elementToCopy PsiFile or PsiDirectory
265 * @param newName can be not null only if elements.length == 1
266 * @param choice a horrible way to pass/keep user preference
267 * @return first copied PsiFile (recursively); null if no PsiFiles copied
270 public static PsiFile copyToDirectory(@NotNull PsiFileSystemItem elementToCopy,
271 @Nullable String newName,
272 @NotNull PsiDirectory targetDirectory,
273 @Nullable int[] choice,
274 @Nullable String title) throws IncorrectOperationException, IOException {
275 if (elementToCopy instanceof PsiFile) {
276 PsiFile file = (PsiFile)elementToCopy;
277 String name = newName == null ? file.getName() : newName;
278 if (checkFileExist(targetDirectory, choice, file, name, "Copy")) return null;
279 return new WriteCommandAction<PsiFile>(targetDirectory.getProject(), title) {
281 protected void run(@NotNull Result<PsiFile> result) throws Throwable {
282 result.setResult(targetDirectory.copyFileFrom(name, file));
284 }.execute().getResultObject();
286 else if (elementToCopy instanceof PsiDirectory) {
287 PsiDirectory directory = (PsiDirectory)elementToCopy;
288 if (directory.equals(targetDirectory)) {
291 if (newName == null) newName = directory.getName();
292 final PsiDirectory existing = targetDirectory.findSubdirectory(newName);
293 final PsiDirectory subdirectory = existing == null ? targetDirectory.createSubdirectory(newName) : existing;
294 EncodingRegistry.doActionAndRestoreEncoding(directory.getVirtualFile(),
295 (ThrowableComputable<VirtualFile, IOException>)() -> subdirectory.getVirtualFile());
297 PsiFile firstFile = null;
298 PsiElement[] children = directory.getChildren();
299 for (PsiElement child : children) {
300 PsiFileSystemItem item = (PsiFileSystemItem)child;
301 PsiFile f = copyToDirectory(item, item.getName(), subdirectory, choice, title);
302 if (firstFile == null) {
309 throw new IllegalArgumentException("unexpected elementToCopy: " + elementToCopy);
313 public static boolean checkFileExist(@Nullable PsiDirectory targetDirectory, int[] choice, PsiFile file, String name, String title) {
314 if (targetDirectory == null) return false;
315 final PsiFile existing = targetDirectory.findFile(name);
316 if (existing != null && !existing.equals(file)) {
318 if (choice == null || choice[0] == -1) {
319 String message = String.format("File '%s' already exists in directory '%s'", name, targetDirectory.getVirtualFile().getPath());
320 String[] options = choice == null ? new String[]{"Overwrite", "Skip"}
321 : new String[]{"Overwrite", "Skip", "Overwrite for all", "Skip for all"};
322 selection = Messages.showDialog(message, title, options, 0, Messages.getQuestionIcon());
325 selection = choice[0];
328 if (choice != null && selection > 1) {
329 choice[0] = selection % 2;
330 selection = choice[0];
333 if (selection == 0 && file != existing) {
334 WriteAction.run(() -> existing.delete());
345 protected static PsiDirectory resolveDirectory(@NotNull PsiDirectory defaultTargetDirectory) {
346 final Project project = defaultTargetDirectory.getProject();
347 final Boolean showDirsChooser = defaultTargetDirectory.getCopyableUserData(CopyPasteDelegator.SHOW_CHOOSER_KEY);
348 if (showDirsChooser != null && showDirsChooser.booleanValue()) {
349 final PsiDirectoryContainer directoryContainer =
350 PsiDirectoryFactory.getInstance(project).getDirectoryContainer(defaultTargetDirectory);
351 if (directoryContainer == null) {
352 return defaultTargetDirectory;
354 return MoveFilesOrDirectoriesUtil.resolveToDirectory(project, directoryContainer);
356 return defaultTargetDirectory;