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