PY-17265 Extracted PyBaseMoveDialog to later create new dialog for the refactoring
[idea/community.git] / python / src / com / jetbrains / python / refactoring / move / PyMoveModuleMembersDelegate.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.jetbrains.python.refactoring.move;
17
18 import com.google.common.collect.Lists;
19 import com.intellij.openapi.actionSystem.DataContext;
20 import com.intellij.openapi.editor.Document;
21 import com.intellij.openapi.editor.Editor;
22 import com.intellij.openapi.editor.SelectionModel;
23 import com.intellij.openapi.project.Project;
24 import com.intellij.openapi.util.Condition;
25 import com.intellij.openapi.util.TextRange;
26 import com.intellij.openapi.util.io.FileUtil;
27 import com.intellij.openapi.util.text.StringUtil;
28 import com.intellij.openapi.vfs.VirtualFile;
29 import com.intellij.psi.*;
30 import com.intellij.refactoring.BaseRefactoringProcessor;
31 import com.intellij.refactoring.RefactoringBundle;
32 import com.intellij.refactoring.move.MoveCallback;
33 import com.intellij.refactoring.move.MoveHandlerDelegate;
34 import com.intellij.refactoring.util.CommonRefactoringUtil;
35 import com.intellij.util.IncorrectOperationException;
36 import com.intellij.util.containers.ContainerUtil;
37 import com.jetbrains.python.PyBundle;
38 import com.jetbrains.python.psi.PyElement;
39 import com.jetbrains.python.psi.PyFile;
40 import com.jetbrains.python.psi.impl.PyPsiUtils;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43
44 import java.util.List;
45
46 /**
47  * @author vlan
48  */
49 public class PyMoveModuleMembersDelegate extends MoveHandlerDelegate {
50   @Override
51   public boolean canMove(PsiElement[] elements, @Nullable PsiElement targetContainer) {
52     for (PsiElement element : elements) {
53       if (!PyMoveModuleMembersHelper.isMovableModuleMember(element)) {
54         return false;
55       }
56     }
57     return super.canMove(elements, targetContainer);
58   }
59
60   @Override
61   public void doMove(Project project,
62                      PsiElement[] elements,
63                      @Nullable PsiElement targetContainer,
64                      @Nullable MoveCallback callback) {
65     final List<PsiNamedElement> initialElements = Lists.newArrayList();
66     for (PsiElement element : elements) {
67       final PsiNamedElement e = PyMoveModuleMembersHelper.extractNamedElement(element);
68       if (e == null) {
69         return;
70       }
71       initialElements.add(e);
72     }
73     String initialPath = null;
74     if (targetContainer instanceof PsiFile) {
75       final VirtualFile virtualFile = ((PsiFile)targetContainer).getVirtualFile();
76       if (virtualFile != null) {
77         initialPath = FileUtil.toSystemDependentName(virtualFile.getPath());
78       }
79     }
80     if (initialPath == null) {
81       initialPath = StringUtil.notNullize(PyPsiUtils.getContainingFilePath(elements[0]));
82     }
83     final PyMoveModuleMembersDialog dialog = PyMoveModuleMembersDialog.getInstance(project, initialElements, initialPath, initialPath);
84     if (!dialog.showAndGet()) {
85       return;
86     }
87     final String destination = dialog.getTargetPath();
88     final boolean previewUsages = dialog.isPreviewUsages();
89     try {
90       final PsiNamedElement[] selectedElements = ContainerUtil.findAllAsArray(dialog.getSelectedTopLevelSymbols(), PsiNamedElement.class);
91       final BaseRefactoringProcessor processor = new PyMoveModuleMembersProcessor(project, selectedElements, destination, previewUsages);
92       processor.run();
93     }
94     catch (IncorrectOperationException e) {
95       CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), e.getMessage(), null, project);
96     }
97   }
98
99   @Override
100   public boolean tryToMove(@NotNull PsiElement element,
101                            @NotNull Project project,
102                            @Nullable DataContext dataContext,
103                            @Nullable PsiReference reference,
104                            @Nullable Editor editor) {
105     PsiFile targetContainer = null;
106     if (editor != null) {
107       final Document document = editor.getDocument();
108       targetContainer = PsiDocumentManager.getInstance(project).getPsiFile(document);
109       if (targetContainer instanceof PyFile && selectionSpansMultipleLines(editor)) {
110         final List<PyElement> moduleMembers = collectAllMovableElementsInSelection(editor, (PyFile)targetContainer);
111         if (moduleMembers.isEmpty()) {
112           showBadSelectionErrorHint(project, editor);
113         }
114         else {
115           doMove(project, ContainerUtil.findAllAsArray(moduleMembers, PsiNamedElement.class), targetContainer, null);
116         }
117         return true;
118       }
119     }
120
121     // Fallback to the old way to select single element to move
122     final PsiNamedElement e = PyMoveModuleMembersHelper.extractNamedElement(element);
123     if (e != null && PyMoveModuleMembersHelper.hasMovableElementType(e)) {
124       if (PyMoveModuleMembersHelper.isMovableModuleMember(e)) {
125         doMove(project, new PsiElement[]{e}, targetContainer, null);
126       }
127       else {
128         showBadSelectionErrorHint(project, editor);
129       }
130       return true;
131     }
132     return false;
133   }
134
135   private static void showBadSelectionErrorHint(@NotNull Project project, @Nullable Editor editor) {
136     CommonRefactoringUtil.showErrorHint(project, editor,
137                                         PyBundle.message("refactoring.move.module.members.error.selection"),
138                                         RefactoringBundle.message("error.title"), null);
139   }
140
141   private static boolean selectionSpansMultipleLines(@NotNull Editor editor) {
142     final SelectionModel selectionModel = editor.getSelectionModel();
143     final Document document = editor.getDocument();
144     return document.getLineNumber(selectionModel.getSelectionStart()) != document.getLineNumber(selectionModel.getSelectionEnd());
145   }
146
147   @NotNull
148   private static List<PyElement> collectAllMovableElementsInSelection(@NotNull Editor editor, @NotNull PyFile pyFile) {
149     final SelectionModel selectionModel = editor.getSelectionModel();
150     final TextRange selectionRange = new TextRange(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
151     final List<PyElement> members = PyMoveModuleMembersHelper.getTopLevelModuleMembers(pyFile);
152     return ContainerUtil.filter(members, member -> {
153       final PsiElement body = PyMoveModuleMembersHelper.expandNamedElementBody(((PsiNamedElement)member));
154       return body != null && selectionRange.contains(body.getTextRange());
155     });
156   }
157 }