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.changeSignature.inplace;
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;
62 import java.util.ArrayList;
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;
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;
80 public InplaceChangeSignature(Project project, Editor editor, @NotNull PsiElement element) {
81 myDocumentManager = PsiDocumentManager.getInstance(project);
84 myMarkAction = StartMarkAction.start(editor, project, ChangeSignatureHandler.REFACTORING_NAME);
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);
97 myDetector = LanguageChangeSignatureDetectors.INSTANCE.forLanguage(element.getLanguage());
98 myStableChange = myDetector.createInitialChangeInfo(element);
99 myInitialSignature = myDetector.extractSignature(myStableChange);
100 TextRange highlightingRange = myDetector.getHighlightingRange(myStableChange);
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);
109 myEditor.getDocument().addDocumentListener(this);
110 myEditor.putUserData(INPLACE_CHANGE_SIGNATURE, this);
111 myPreview = InplaceRefactoring.createPreviewComponent(project, myDetector.getFileType());
116 public static InplaceChangeSignature getCurrentRefactoring(@NotNull Editor editor) {
117 return editor.getUserData(INPLACE_CHANGE_SIGNATURE);
120 public ChangeInfo getCurrentInfo() {
121 return myCurrentInfo;
124 public String getInitialSignature() {
125 return myInitialSignature;
129 public ChangeInfo getStableChange() {
130 return myStableChange;
133 public void cancel() {
134 TextRange highlightingRange = myDetector.getHighlightingRange(getStableChange());
135 Document document = myEditor.getDocument();
136 String initialSignature = myInitialSignature;
138 temporallyRevertChanges(highlightingRange, document, initialSignature, myProject);
142 public void beforeDocumentChange(DocumentEvent event) {}
145 public void documentChanged(DocumentEvent event) {
146 RangeMarker marker = event.getDocument().createRangeMarker(event.getOffset(), event.getOffset());
147 myDocumentManager.performWhenAllCommitted(() -> {
148 if (myDetector == null) {
151 PsiFile file = myDocumentManager.getPsiFile(event.getDocument());
155 PsiElement element = file.findElementAt(marker.getStartOffset());
157 if (element == null || myDetector.ignoreChanges(element)) return;
159 if (element instanceof PsiWhiteSpace) {
160 PsiElement method = myStableChange.getMethod();
161 if (PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace.class) == method) {
166 if (!myDetector.isChangeSignatureAvailableOnElement(element, myStableChange)) {
171 if (myCurrentInfo == null) {
172 myCurrentInfo = myStableChange;
174 String signature = myDetector.extractSignature(myCurrentInfo);
175 ChangeInfo changeInfo = myDetector.createNextChangeInfo(signature, myCurrentInfo, myDelegate);
176 if (changeInfo == null && myCurrentInfo != null) {
177 myStableChange = myCurrentInfo;
179 if (changeInfo != null) {
180 updateMethodSignature(changeInfo);
182 myCurrentInfo = changeInfo;
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);
191 myPreview.getMarkupModel().removeAllHighlighters();
192 new WriteCommandAction(null) {
194 protected void run(@NotNull Result result) throws Throwable {
195 myPreview.getDocument().replaceString(0, myPreview.getDocument().getTextLength(), methodSignature);
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);
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);
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()) {
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);
228 }, Balloon.Position.above);
229 Disposer.register(myBalloon, () -> {
230 EditorFactory.getInstance().releaseEditor(myPreview);
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);
241 myHighlighters.clear();
244 FinishMarkAction.finish(myProject, myEditor, myMarkAction);
245 myEditor.putUserData(INPLACE_CHANGE_SIGNATURE, null);
248 public static void temporallyRevertChanges(final TextRange signatureRange,
249 final Document document,
250 final String initialSignature,
252 WriteCommandAction.runWriteCommandAction(project, () -> {
253 document.replaceString(signatureRange.getStartOffset(), signatureRange.getEndOffset(), initialSignature);
254 PsiDocumentManager.getInstance(project).commitDocument(document);