dispose preview after balloon is closed so no green rec is visible
[idea/community.git] / platform / lang-impl / src / com / intellij / refactoring / changeSignature / inplace / InplaceChangeSignature.java
1 /*
2  * Copyright 2000-2016 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.intellij.refactoring.changeSignature.inplace;
17
18 import com.intellij.codeInsight.highlighting.HighlightManager;
19 import com.intellij.openapi.application.Result;
20 import com.intellij.openapi.command.WriteCommandAction;
21 import com.intellij.openapi.command.impl.FinishMarkAction;
22 import com.intellij.openapi.command.impl.StartMarkAction;
23 import com.intellij.openapi.editor.*;
24 import com.intellij.openapi.editor.colors.CodeInsightColors;
25 import com.intellij.openapi.editor.colors.EditorColors;
26 import com.intellij.openapi.editor.colors.EditorColorsManager;
27 import com.intellij.openapi.editor.event.DocumentEvent;
28 import com.intellij.openapi.editor.event.DocumentListener;
29 import com.intellij.openapi.editor.ex.EditorEx;
30 import com.intellij.openapi.editor.markup.HighlighterLayer;
31 import com.intellij.openapi.editor.markup.HighlighterTargetArea;
32 import com.intellij.openapi.editor.markup.RangeHighlighter;
33 import com.intellij.openapi.editor.markup.TextAttributes;
34 import com.intellij.openapi.fileEditor.OpenFileDescriptor;
35 import com.intellij.openapi.project.Project;
36 import com.intellij.openapi.ui.Messages;
37 import com.intellij.openapi.ui.popup.Balloon;
38 import com.intellij.openapi.ui.popup.BalloonBuilder;
39 import com.intellij.openapi.ui.popup.JBPopupFactory;
40 import com.intellij.openapi.util.Disposer;
41 import com.intellij.openapi.util.Key;
42 import com.intellij.openapi.util.TextRange;
43 import com.intellij.openapi.vfs.VirtualFile;
44 import com.intellij.psi.PsiDocumentManager;
45 import com.intellij.psi.PsiElement;
46 import com.intellij.psi.PsiFile;
47 import com.intellij.psi.PsiWhiteSpace;
48 import com.intellij.psi.util.PsiTreeUtil;
49 import com.intellij.psi.util.PsiUtilCore;
50 import com.intellij.refactoring.RefactoringBundle;
51 import com.intellij.refactoring.changeSignature.ChangeInfo;
52 import com.intellij.refactoring.changeSignature.ChangeSignatureHandler;
53 import com.intellij.refactoring.rename.inplace.InplaceRefactoring;
54 import com.intellij.ui.awt.RelativePoint;
55 import com.intellij.ui.components.JBCheckBox;
56 import com.intellij.util.ui.PositionTracker;
57 import org.jetbrains.annotations.NotNull;
58 import org.jetbrains.annotations.Nullable;
59
60 import javax.swing.*;
61 import java.awt.*;
62 import java.util.ArrayList;
63
64 public class InplaceChangeSignature implements DocumentListener {
65   public static final Key<InplaceChangeSignature> INPLACE_CHANGE_SIGNATURE = Key.create("EditorInplaceChangeSignature");
66   private ChangeInfo myCurrentInfo;
67   private ChangeInfo myStableChange;
68   private String myInitialSignature;
69   private Editor myEditor;
70   private LanguageChangeSignatureDetector<ChangeInfo> myDetector;
71
72   private final Project myProject;
73   private final PsiDocumentManager myDocumentManager;
74   private final ArrayList<RangeHighlighter> myHighlighters = new ArrayList<>();
75   private StartMarkAction myMarkAction;
76   private Balloon myBalloon;
77   private boolean myDelegate;
78   private EditorEx myPreview;
79
80   public InplaceChangeSignature(Project project, Editor editor, @NotNull PsiElement element) {
81     myDocumentManager = PsiDocumentManager.getInstance(project);
82     myProject = project;
83     try {
84       myMarkAction = StartMarkAction.start(editor, project, ChangeSignatureHandler.REFACTORING_NAME);
85     }
86     catch (StartMarkAction.AlreadyStartedException e) {
87       final int exitCode = Messages.showYesNoDialog(myProject, e.getMessage(), ChangeSignatureHandler.REFACTORING_NAME, "Navigate to Started", "Cancel", Messages.getErrorIcon());
88       if (exitCode == Messages.CANCEL) return;
89       PsiElement method = myStableChange.getMethod();
90       VirtualFile virtualFile = PsiUtilCore.getVirtualFile(method);
91       new OpenFileDescriptor(project, virtualFile, method.getTextOffset()).navigate(true);
92       return;
93     }
94
95
96     myEditor = editor;
97     myDetector = LanguageChangeSignatureDetectors.INSTANCE.forLanguage(element.getLanguage());
98     myStableChange = myDetector.createInitialChangeInfo(element);
99     myInitialSignature = myDetector.extractSignature(myStableChange);
100     TextRange highlightingRange = myDetector.getHighlightingRange(myStableChange);
101
102     HighlightManager highlightManager = HighlightManager.getInstance(myProject);
103     TextAttributes attributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.LIVE_TEMPLATE_ATTRIBUTES);
104     highlightManager.addRangeHighlight(editor, highlightingRange.getStartOffset(), highlightingRange.getEndOffset(), attributes, false, myHighlighters);
105     for (RangeHighlighter highlighter : myHighlighters) {
106       highlighter.setGreedyToRight(true);
107       highlighter.setGreedyToLeft(true);
108     }
109     myEditor.getDocument().addDocumentListener(this);
110     myEditor.putUserData(INPLACE_CHANGE_SIGNATURE, this);
111     myPreview = InplaceRefactoring.createPreviewComponent(project, myDetector.getFileType());
112     showBalloon();
113   }
114
115   @Nullable
116   public static InplaceChangeSignature getCurrentRefactoring(@NotNull Editor editor) {
117     return editor.getUserData(INPLACE_CHANGE_SIGNATURE);
118   }
119
120   public ChangeInfo getCurrentInfo() {
121     return myCurrentInfo;
122   }
123
124   public String getInitialSignature() {
125     return myInitialSignature;
126   }
127
128   @NotNull
129   public ChangeInfo getStableChange() {
130     return myStableChange;
131   }
132
133   public void cancel() {
134     TextRange highlightingRange = myDetector.getHighlightingRange(getStableChange());
135     Document document = myEditor.getDocument();
136     String initialSignature = myInitialSignature;
137     detach();
138     temporallyRevertChanges(highlightingRange, document, initialSignature, myProject);
139   }
140
141   @Override
142   public void beforeDocumentChange(DocumentEvent event) {}
143
144   @Override
145   public void documentChanged(DocumentEvent event) {
146     RangeMarker marker = event.getDocument().createRangeMarker(event.getOffset(), event.getOffset());
147     myDocumentManager.performWhenAllCommitted(() -> {
148       if (myDetector == null) {
149         return;
150       }
151       PsiFile file = myDocumentManager.getPsiFile(event.getDocument());
152       if (file == null) {
153         return;
154       }
155       PsiElement element = file.findElementAt(marker.getStartOffset());
156       marker.dispose();
157       if (element == null || myDetector.ignoreChanges(element)) return;
158
159       if (element instanceof PsiWhiteSpace) {
160         PsiElement method = myStableChange.getMethod();
161         if (PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace.class) == method) {
162           return;
163         }
164       }
165
166       if (!myDetector.isChangeSignatureAvailableOnElement(element, myStableChange)) {
167         detach();
168         return;
169       }
170
171       if (myCurrentInfo == null) {
172         myCurrentInfo = myStableChange;
173       }
174       String signature = myDetector.extractSignature(myCurrentInfo);
175       ChangeInfo changeInfo = myDetector.createNextChangeInfo(signature, myCurrentInfo, myDelegate);
176       if (changeInfo == null && myCurrentInfo != null) {
177         myStableChange = myCurrentInfo;
178       }
179       if (changeInfo != null) {
180         updateMethodSignature(changeInfo);
181       }
182       myCurrentInfo = changeInfo;
183     });
184   }
185
186   private void updateMethodSignature(ChangeInfo changeInfo) {
187     ArrayList<TextRange> deleteRanges = new ArrayList<>();
188     ArrayList<TextRange> newRanges = new ArrayList<>();
189     String methodSignature = myDetector.getMethodSignaturePreview(changeInfo, deleteRanges, newRanges);
190
191     myPreview.getMarkupModel().removeAllHighlighters();
192     new WriteCommandAction(null) {
193       @Override
194       protected void run(@NotNull Result result) throws Throwable {
195         myPreview.getDocument().replaceString(0, myPreview.getDocument().getTextLength(), methodSignature);
196       }
197     }.execute();
198     TextAttributes deprecatedAttributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.DEPRECATED_ATTRIBUTES);
199     for (TextRange range : deleteRanges) {
200       myPreview.getMarkupModel().addRangeHighlighter(range.getStartOffset(), range.getEndOffset(), HighlighterLayer.ADDITIONAL_SYNTAX,
201                                                      deprecatedAttributes, HighlighterTargetArea.EXACT_RANGE);
202     }
203     TextAttributes todoAttributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(CodeInsightColors.TODO_DEFAULT_ATTRIBUTES);
204     for (TextRange range : newRanges) {
205       myPreview.getMarkupModel().addRangeHighlighter(range.getStartOffset(), range.getEndOffset(), HighlighterLayer.ADDITIONAL_SYNTAX,
206                                                      todoAttributes, HighlighterTargetArea.EXACT_RANGE);
207     }
208   }
209
210   protected void showBalloon() {
211     JBCheckBox checkBox = new JBCheckBox(RefactoringBundle.message("delegation.panel.delegate.via.overloading.method"));
212     checkBox.addActionListener(e -> myDelegate = checkBox.isSelected());
213     JPanel content = new JPanel(new BorderLayout());
214     content.add(myPreview.getComponent(), BorderLayout.NORTH);
215     updateMethodSignature(myStableChange);
216     content.add(checkBox, BorderLayout.SOUTH);
217     final BalloonBuilder balloonBuilder = JBPopupFactory.getInstance().createDialogBalloonBuilder(content, null).setSmallVariant(true);
218     myBalloon = balloonBuilder.createBalloon();
219     myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
220     myBalloon.show(new PositionTracker<Balloon>(myEditor.getContentComponent()) {
221       @Override
222       public RelativePoint recalculateLocation(Balloon object) {
223         int offset = myStableChange.getMethod().getTextOffset();
224         VisualPosition visualPosition = myEditor.offsetToVisualPosition(offset);
225         Point point = myEditor.visualPositionToXY(new VisualPosition(visualPosition.line, visualPosition.column));
226         return new RelativePoint(myEditor.getContentComponent(), point);
227       }
228     }, Balloon.Position.above);
229     Disposer.register(myBalloon, () -> {
230       EditorFactory.getInstance().releaseEditor(myPreview);
231       myPreview = null;
232     });
233   }
234
235   public void detach() {
236     myEditor.getDocument().removeDocumentListener(this);
237     HighlightManager highlightManager = HighlightManager.getInstance(myProject);
238     for (RangeHighlighter highlighter : myHighlighters) {
239       highlightManager.removeSegmentHighlighter(myEditor, highlighter);
240     }
241     myHighlighters.clear();
242     myBalloon.hide();
243     myDetector = null;
244     FinishMarkAction.finish(myProject, myEditor, myMarkAction);
245     myEditor.putUserData(INPLACE_CHANGE_SIGNATURE, null);
246   }
247
248   public static void temporallyRevertChanges(final TextRange signatureRange,
249                                              final Document document,
250                                              final String initialSignature,
251                                              Project project) {
252     WriteCommandAction.runWriteCommandAction(project, () -> {
253       document.replaceString(signatureRange.getStartOffset(), signatureRange.getEndOffset(), initialSignature);
254       PsiDocumentManager.getInstance(project).commitDocument(document);
255     });
256   }
257 }