IDEA-141772 Debugger: Smart Step Into inside lambda suggests method calls outside...
[idea/community.git] / java / debugger / impl / src / com / intellij / debugger / actions / JavaSmartStepIntoHandler.java
1 /*
2  * Copyright 2000-2015 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.debugger.actions;
17
18 import com.intellij.debugger.SourcePosition;
19 import com.intellij.debugger.impl.DebuggerUtilsEx;
20 import com.intellij.lang.java.JavaLanguage;
21 import com.intellij.openapi.editor.Document;
22 import com.intellij.openapi.fileEditor.FileDocumentManager;
23 import com.intellij.openapi.util.Ref;
24 import com.intellij.openapi.util.TextRange;
25 import com.intellij.openapi.vfs.VirtualFile;
26 import com.intellij.psi.*;
27 import com.intellij.util.DocumentUtil;
28 import com.intellij.util.Range;
29 import com.intellij.util.containers.OrderedSet;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
32
33 import java.util.Collections;
34 import java.util.List;
35 import java.util.Stack;
36
37 /**
38  * User: Alexander Podkhalyuzin
39  * Date: 22.11.11
40  */
41 public class JavaSmartStepIntoHandler extends JvmSmartStepIntoHandler {
42   @Override
43   public boolean isAvailable(final SourcePosition position) {
44     final PsiFile file = position.getFile();
45     return file.getLanguage().isKindOf(JavaLanguage.INSTANCE);
46   }
47
48   @Override
49   @NotNull
50   public List<SmartStepTarget> findSmartStepTargets(final SourcePosition position) {
51     final int line = position.getLine();
52     if (line < 0) {
53       return Collections.emptyList(); // the document has been changed
54     }
55
56     final PsiFile file = position.getFile();
57     final VirtualFile vFile = file.getVirtualFile();
58     if (vFile == null) {
59       // the file is not physical
60       return Collections.emptyList();
61     }
62
63     final Document doc = FileDocumentManager.getInstance().getDocument(vFile);
64     if (doc == null) return Collections.emptyList();
65     if (line >= doc.getLineCount()) {
66       return Collections.emptyList(); // the document has been changed
67     }
68     TextRange curLineRange = DocumentUtil.getLineTextRange(doc, line);
69     PsiElement element = position.getElementAt();
70     PsiElement method = getBody(DebuggerUtilsEx.getContainingMethod(element));
71     final TextRange lineRange = (method != null) ? curLineRange.intersection(method.getTextRange()) : curLineRange;
72
73     if (lineRange == null || lineRange.isEmpty()) {
74       return Collections.emptyList();
75     }
76
77     if (element != null && !(element instanceof PsiCompiledElement)) {
78       do {
79         final PsiElement parent = element.getParent();
80         if (parent == null || (parent.getTextOffset() < lineRange.getStartOffset())) {
81           break;
82         }
83         element = parent;
84       }
85       while(true);
86
87       //noinspection unchecked
88       final List<SmartStepTarget> targets = new OrderedSet<SmartStepTarget>();
89
90       final Ref<TextRange> textRange = new Ref<TextRange>(lineRange);
91
92       final PsiElementVisitor methodCollector = new JavaRecursiveElementVisitor() {
93         final Stack<PsiMethod> myContextStack = new Stack<PsiMethod>();
94         final Stack<String> myParamNameStack = new Stack<String>();
95         private int myNextLambdaExpressionOrdinal = 0;
96
97         @Nullable
98         private String getCurrentParamName() {
99           return myParamNameStack.isEmpty() ? null : myParamNameStack.peek();
100         }
101
102         @Override
103         public void visitAnonymousClass(PsiAnonymousClass aClass) {
104           for (PsiMethod psiMethod : aClass.getMethods()) {
105             targets.add(new MethodSmartStepTarget(psiMethod, getCurrentParamName(), psiMethod.getBody(), true, null));
106           }
107         }
108
109         public void visitLambdaExpression(PsiLambdaExpression expression) {
110           super.visitLambdaExpression(expression);
111           targets.add(new LambdaSmartStepTarget(expression, getCurrentParamName(), expression.getBody(), myNextLambdaExpressionOrdinal++, null));
112         }
113
114         @Override
115         public void visitMethodReferenceExpression(PsiMethodReferenceExpression expression) {
116           PsiElement element = expression.resolve();
117           if (element instanceof PsiMethod) {
118             PsiElement navMethod = element.getNavigationElement();
119             if (navMethod instanceof PsiMethod) {
120               targets.add(new MethodSmartStepTarget(((PsiMethod)navMethod), null, expression, true, null));
121             }
122           }
123         }
124
125         @Override
126         public void visitStatement(PsiStatement statement) {
127           TextRange range = statement.getTextRange();
128           if (lineRange.intersects(range)) {
129             textRange.set(textRange.get().union(range));
130             super.visitStatement(statement);
131           }
132         }
133
134         @Override
135         public void visitExpression(PsiExpression expression) {
136           TextRange range = expression.getTextRange();
137           if (lineRange.intersects(range)) {
138             textRange.set(textRange.get().union(range));
139           }
140           super.visitExpression(expression);
141         }
142
143         public void visitExpressionList(PsiExpressionList expressionList) {
144           final PsiMethod psiMethod = myContextStack.isEmpty()? null : myContextStack.peek();
145           if (psiMethod != null) {
146             final String methodName = psiMethod.getName();
147             final PsiExpression[] expressions = expressionList.getExpressions();
148             final PsiParameter[] parameters = psiMethod.getParameterList().getParameters();
149             for (int idx = 0; idx < expressions.length; idx++) {
150               final String paramName = (idx < parameters.length && !parameters[idx].isVarArgs())? parameters[idx].getName() : "arg"+(idx+1);
151               myParamNameStack.push(methodName + ": " + paramName + ".");
152               final PsiExpression argExpression = expressions[idx];
153               try {
154                 argExpression.accept(this);
155               }
156               finally {
157                 myParamNameStack.pop();
158               }
159             }
160           }
161           else {
162             super.visitExpressionList(expressionList);
163           }
164         }
165
166         @Override
167         public void visitCallExpression(final PsiCallExpression expression) {
168           final PsiMethod psiMethod = expression.resolveMethod();
169           if (psiMethod != null) {
170             myContextStack.push(psiMethod);
171             targets.add(new MethodSmartStepTarget(
172               psiMethod,
173               null,
174               expression instanceof PsiMethodCallExpression?
175                 ((PsiMethodCallExpression)expression).getMethodExpression().getReferenceNameElement()
176                 : expression instanceof PsiNewExpression? ((PsiNewExpression)expression).getClassOrAnonymousClassReference() : expression,
177               false,
178               null
179             ));
180           }
181           try {
182             super.visitCallExpression(expression);
183           }
184           finally {
185             if (psiMethod != null) {
186               myContextStack.pop();
187             }
188           }
189         }
190
191       };
192
193       element.accept(methodCollector);
194       for (PsiElement sibling = element.getNextSibling(); sibling != null; sibling = sibling.getNextSibling()) {
195         if (!lineRange.intersects(sibling.getTextRange())) {
196           break;
197         }
198         sibling.accept(methodCollector);
199       }
200
201       Range<Integer> lines = new Range<Integer>(doc.getLineNumber(textRange.get().getStartOffset()), doc.getLineNumber(textRange.get().getEndOffset()));
202       for (SmartStepTarget target : targets) {
203         target.setCallingExpressionLines(lines);
204       }
205       return targets;
206     }
207     return Collections.emptyList();
208   }
209
210   private static PsiElement getBody(@Nullable PsiElement containingMethod) {
211     if (containingMethod instanceof PsiMethod) {
212       return ((PsiMethod)containingMethod).getBody();
213     }
214     else if (containingMethod instanceof PsiLambdaExpression) {
215       return ((PsiLambdaExpression)containingMethod).getBody();
216     }
217     return null;
218   }
219 }