c977bbe66d6147ce477244bba30871e79640bce4
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / refactoring / ui / MethodOrClosureScopeChooser.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 org.jetbrains.plugins.groovy.refactoring.ui;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.editor.Editor;
20 import com.intellij.openapi.editor.colors.EditorColors;
21 import com.intellij.openapi.editor.colors.EditorColorsManager;
22 import com.intellij.openapi.editor.markup.*;
23 import com.intellij.openapi.ui.popup.JBPopup;
24 import com.intellij.openapi.ui.popup.JBPopupAdapter;
25 import com.intellij.openapi.ui.popup.JBPopupFactory;
26 import com.intellij.openapi.ui.popup.LightweightWindowEvent;
27 import com.intellij.openapi.util.Iconable;
28 import com.intellij.openapi.util.Pair;
29 import com.intellij.openapi.util.TextRange;
30 import com.intellij.openapi.wm.IdeFocusManager;
31 import com.intellij.psi.PsiElement;
32 import com.intellij.psi.PsiMethod;
33 import com.intellij.psi.PsiSubstitutor;
34 import com.intellij.psi.util.PsiFormatUtil;
35 import com.intellij.psi.util.PsiFormatUtilBase;
36 import com.intellij.ui.ScrollPaneFactory;
37 import com.intellij.ui.components.JBList;
38 import com.intellij.util.PairFunction;
39 import icons.JetgroovyIcons;
40 import org.jetbrains.annotations.NonNls;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43 import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
44 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrParametersOwner;
45 import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
46 import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
47 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrAssignmentExpression;
48 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression;
49 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression;
50 import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
51
52 import javax.swing.*;
53 import javax.swing.event.ListSelectionEvent;
54 import javax.swing.event.ListSelectionListener;
55 import java.awt.*;
56 import java.awt.event.ActionEvent;
57 import java.awt.event.ActionListener;
58 import java.awt.event.KeyEvent;
59 import java.util.ArrayList;
60 import java.util.Collections;
61 import java.util.List;
62
63 /**
64  * @author Max Medvedev
65  */
66 public class MethodOrClosureScopeChooser {
67   private static final Logger LOG = Logger.getInstance(MethodOrClosureScopeChooser.class);
68
69   @NonNls private static final String USE_SUPER_METHOD_OF = "Change base method";
70   @NonNls private static final String CHANGE_USAGES_OF = "Change usages";
71
72   public interface JBPopupOwner {
73     JBPopup get();
74   }
75
76   /**
77    * @param callback is invoked if any scope was chosen. The first arg is this scope and the second arg is a psielement to search for (super method of chosen method or
78    *                 variable if the scope is a closure)
79    */
80   public static JBPopup create(List<? extends GrParametersOwner> scopes,
81                                final Editor editor,
82                                final JBPopupOwner popupRef,
83                                final PairFunction<GrParametersOwner, PsiElement, Object> callback) {
84     final JPanel panel = new JPanel(new BorderLayout());
85     final JCheckBox superMethod = new JCheckBox(USE_SUPER_METHOD_OF, true);
86     superMethod.setMnemonic('U');
87     panel.add(superMethod, BorderLayout.SOUTH);
88     final JBList list = new JBList(scopes.toArray());
89     list.setVisibleRowCount(5);
90     list.setCellRenderer(new DefaultListCellRenderer() {
91       @Override
92       public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
93         super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
94
95         final String text;
96         if (value instanceof PsiMethod) {
97           final PsiMethod method = (PsiMethod)value;
98           text = PsiFormatUtil.formatMethod(method, PsiSubstitutor.EMPTY,
99                                             PsiFormatUtilBase.SHOW_CONTAINING_CLASS |
100                                             PsiFormatUtilBase.SHOW_NAME |
101                                             PsiFormatUtilBase.SHOW_PARAMETERS,
102                                             PsiFormatUtilBase.SHOW_TYPE);
103           final int flags = Iconable.ICON_FLAG_VISIBILITY;
104           final Icon icon = method.getIcon(flags);
105           if (icon != null) setIcon(icon);
106         }
107         else {
108           LOG.assertTrue(value instanceof GrClosableBlock);
109           setIcon(JetgroovyIcons.Groovy.Groovy_16x16);
110           text = "{...}";
111         }
112         setText(text);
113         return this;
114       }
115     });
116     list.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
117     list.setSelectedIndex(0);
118     final List<RangeHighlighter> highlighters = new ArrayList<>();
119     final TextAttributes attributes =
120       EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
121     list.addListSelectionListener(new ListSelectionListener() {
122       @Override
123       public void valueChanged(final ListSelectionEvent e) {
124         final GrParametersOwner selectedMethod = (GrParametersOwner)list.getSelectedValue();
125         if (selectedMethod == null) return;
126         dropHighlighters(highlighters);
127         updateView(selectedMethod, editor, attributes, highlighters, superMethod);
128       }
129     });
130     updateView(scopes.get(0), editor, attributes, highlighters, superMethod);
131     final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(list);
132     scrollPane.setBorder(null);
133     panel.add(scrollPane, BorderLayout.CENTER);
134
135     final List<Pair<ActionListener, KeyStroke>> keyboardActions = Collections.singletonList(
136       Pair.<ActionListener, KeyStroke>create(new ActionListener() {
137         @Override
138         public void actionPerformed(ActionEvent e) {
139           final GrParametersOwner ToSearchIn = (GrParametersOwner)list.getSelectedValue();
140           final JBPopup popup = popupRef.get();
141           if (popup != null && popup.isVisible()) {
142             popup.cancel();
143           }
144
145
146           final PsiElement toSearchFor;
147           if (ToSearchIn instanceof GrMethod) {
148             final GrMethod method = (GrMethod)ToSearchIn;
149             toSearchFor = superMethod.isEnabled() && superMethod.isSelected() ? method.findDeepestSuperMethod() : method;
150           }
151           else {
152             toSearchFor = superMethod.isEnabled() && superMethod.isSelected() ? ToSearchIn.getParent() : null;
153           }
154           IdeFocusManager.findInstance().doWhenFocusSettlesDown(() -> callback.fun(ToSearchIn, toSearchFor));
155         }
156       }, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)));
157
158
159     return JBPopupFactory.getInstance().createComponentPopupBuilder(panel, list)
160       .setTitle("Introduce parameter to")
161       .setMovable(false)
162       .setResizable(false)
163       .setRequestFocus(true)
164       .setKeyboardActions(keyboardActions).addListener(new JBPopupAdapter() {
165         @Override
166         public void onClosed(LightweightWindowEvent event) {
167           dropHighlighters(highlighters);
168         }
169       }).createPopup();
170   }
171
172
173   public static void updateView(GrParametersOwner selectedMethod,
174                                 Editor editor,
175                                 TextAttributes attributes,
176                                 List<RangeHighlighter> highlighters,
177                                 JCheckBox superMethod) {
178     final MarkupModel markupModel = editor.getMarkupModel();
179     final TextRange textRange = selectedMethod.getTextRange();
180     final RangeHighlighter rangeHighlighter =
181       markupModel.addRangeHighlighter(textRange.getStartOffset(), textRange.getEndOffset(), HighlighterLayer.SELECTION - 1, attributes,
182                                       HighlighterTargetArea.EXACT_RANGE);
183     highlighters.add(rangeHighlighter);
184     if (selectedMethod instanceof GrMethod) {
185       superMethod.setText(USE_SUPER_METHOD_OF);
186       superMethod.setEnabled(((GrMethod)selectedMethod).findDeepestSuperMethod() != null);
187     }
188     else {
189       superMethod.setText(CHANGE_USAGES_OF);
190       superMethod.setEnabled(findVariableToUse(selectedMethod) != null);
191     }
192   }
193
194   @Nullable
195   public static GrVariable findVariableToUse(@NotNull GrParametersOwner owner) {
196     final PsiElement parent = owner.getParent();
197     if (parent instanceof GrVariable) return (GrVariable)parent;
198     if (parent instanceof GrAssignmentExpression &&
199         ((GrAssignmentExpression)parent).getRValue() == owner &&
200         ((GrAssignmentExpression)parent).getOperationTokenType() == GroovyTokenTypes.mASSIGN) {
201       final GrExpression lValue = ((GrAssignmentExpression)parent).getLValue();
202       if (lValue instanceof GrReferenceExpression) {
203         final PsiElement resolved = ((GrReferenceExpression)lValue).resolve();
204         if (resolved instanceof GrVariable) {
205           return (GrVariable)resolved;
206         }
207       }
208     }
209     return null;
210   }
211
212   private static void dropHighlighters(List<RangeHighlighter> highlighters) {
213     for (RangeHighlighter highlighter : highlighters) {
214       highlighter.dispose();
215     }
216     highlighters.clear();
217   }
218 }