IDEA-141082 Smart step into several lambdas
authorEgor.Ushakov <egor.ushakov@jetbrains.com>
Thu, 4 Jun 2015 14:30:40 +0000 (17:30 +0300)
committerEgor.Ushakov <egor.ushakov@jetbrains.com>
Thu, 4 Jun 2015 15:05:27 +0000 (18:05 +0300)
java/debugger/impl/src/com/intellij/debugger/engine/DefaultSyntheticProvider.java
java/debugger/impl/src/com/intellij/debugger/engine/LambdaMethodFilter.java
java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerImpl.java
java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/StepIntoBreakpoint.java

index 19cf48059d5eabf6d93a5a167b105968ff0ef437..772e997da66883c5dca4460db62ec643ddaf8993 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2014 JetBrains s.r.o.
+ * Copyright 2000-2015 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,7 +26,7 @@ public class DefaultSyntheticProvider implements SyntheticTypeComponentProvider
   @Override
   public boolean isSynthetic(TypeComponent typeComponent) {
     String name = typeComponent.name();
-    if (name.startsWith(LambdaMethodFilter.LAMBDA_METHOD_PREFIX)) {
+    if (LambdaMethodFilter.isLambdaName(name)) {
       return false;
     }
     else {
index f85733082a90876f07a47f24b981c8b731470819..31e0ff4ce7196cc70d3946b9b72f2284d45bc99d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * Copyright 2000-2015 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package com.intellij.debugger.engine;
 import com.intellij.debugger.SourcePosition;
 import com.intellij.debugger.engine.evaluation.EvaluateException;
 import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
+import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.psi.PsiCodeBlock;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiLambdaExpression;
@@ -25,13 +26,14 @@ import com.intellij.psi.PsiStatement;
 import com.intellij.util.Range;
 import com.sun.jdi.Location;
 import com.sun.jdi.Method;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 /**
  * @author Eugene Zhuravlev
  *         Date: 10/26/13
  */
-public class LambdaMethodFilter implements BreakpointStepMethodFilter{
+public class LambdaMethodFilter implements BreakpointStepMethodFilter {
   public static final String LAMBDA_METHOD_PREFIX = "lambda$";
   private final int myLambdaOrdinal;
   @Nullable
@@ -52,15 +54,16 @@ public class LambdaMethodFilter implements BreakpointStepMethodFilter{
         firstStatementPosition = SourcePosition.createFromElement(statements[0]);
         if (firstStatementPosition != null) {
           final PsiStatement lastStatement = statements[statements.length - 1];
-          lastStatementPosition = SourcePosition.createFromOffset(firstStatementPosition.getFile(), lastStatement.getTextRange().getEndOffset());
+          lastStatementPosition =
+            SourcePosition.createFromOffset(firstStatementPosition.getFile(), lastStatement.getTextRange().getEndOffset());
         }
       }
     }
-    else if (body != null){
+    else if (body != null) {
       firstStatementPosition = SourcePosition.createFromElement(body);
     }
     myFirstStatementPosition = firstStatementPosition;
-    myLastStatementLine = lastStatementPosition != null? lastStatementPosition.getLine() : -1;
+    myLastStatementLine = lastStatementPosition != null ? lastStatementPosition.getLine() : -1;
   }
 
   public int getLambdaOrdinal() {
@@ -79,7 +82,7 @@ public class LambdaMethodFilter implements BreakpointStepMethodFilter{
   public boolean locationMatches(DebugProcessImpl process, Location location) throws EvaluateException {
     final VirtualMachineProxyImpl vm = process.getVirtualMachineProxy();
     final Method method = location.method();
-    return method.name().startsWith(LAMBDA_METHOD_PREFIX) && (!vm.canGetSyntheticAttribute() || method.isSynthetic());
+    return isLambdaName(method.name()) && (!vm.canGetSyntheticAttribute() || method.isSynthetic());
   }
 
   @Nullable
@@ -87,4 +90,20 @@ public class LambdaMethodFilter implements BreakpointStepMethodFilter{
   public Range<Integer> getCallingExpressionLines() {
     return myCallingExpressionLines;
   }
+
+  public static boolean isLambdaName(@Nullable String name) {
+    return !StringUtil.isEmpty(name) && name.startsWith(LAMBDA_METHOD_PREFIX);
+  }
+
+  public static int getLambdaOrdinal(@NotNull String name) {
+    int pos = name.lastIndexOf('$');
+    if (pos > -1) {
+      try {
+        return Integer.parseInt(name.substring(pos + 1));
+      }
+      catch (NumberFormatException ignored) {
+      }
+    }
+    return -1;
+  }
 }
index dc8cb6478af22323eca4b223e45909ff82c1cf94..00acfce7396ab27611324a3cc8d463739d739af6 100644 (file)
@@ -38,6 +38,7 @@ import com.intellij.psi.*;
 import com.intellij.psi.search.FilenameIndex;
 import com.intellij.psi.search.GlobalSearchScope;
 import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.util.containers.ContainerUtil;
 import com.intellij.util.containers.EmptyIterable;
 import com.intellij.xdebugger.impl.ui.ExecutionPointHighlighter;
 import com.intellij.xdebugger.ui.DebuggerColors;
@@ -149,16 +150,18 @@ public class PositionManagerImpl implements PositionManager, MultiRequestPositio
       }
     }
 
+    Method method = location.method();
+
     if (psiFile instanceof PsiCompiledElement || lineNumber < 0) {
-      final String methodSignature = location.method().signature();
+      final String methodSignature = method.signature();
       if (methodSignature == null) {
         return SourcePosition.createFromLine(psiFile, -1);
       }
-      final String methodName = location.method().name();
-      if(methodName == null) {
+      final String methodName = method.name();
+      if (methodName == null) {
         return SourcePosition.createFromLine(psiFile, -1);
       }
-      if(location.declaringType() == null) {
+      if (location.declaringType() == null) {
         return SourcePosition.createFromLine(psiFile, -1);
       }
 
@@ -176,17 +179,38 @@ public class PositionManagerImpl implements PositionManager, MultiRequestPositio
       return sourcePosition;
     }
 
-    return new JavaSourcePosition(SourcePosition.createFromLine(psiFile, lineNumber), location.declaringType(), location.method());
+    SourcePosition sourcePosition = SourcePosition.createFromLine(psiFile, lineNumber);
+    int lambdaOrdinal = -1;
+    if (LambdaMethodFilter.isLambdaName(method.name())) {
+      List<Location> lambdas = ContainerUtil.filter(locationsOfLine(location.declaringType(), sourcePosition), new Condition<Location>() {
+        @Override
+        public boolean value(Location location) {
+          return LambdaMethodFilter.isLambdaName(location.method().name());
+        }
+      });
+      if (lambdas.size() > 1) {
+        Collections.sort(lambdas, new Comparator<Location>() {
+          @Override
+          public int compare(Location o1, Location o2) {
+            return LambdaMethodFilter.getLambdaOrdinal(o1.method().name()) - LambdaMethodFilter.getLambdaOrdinal(o2.method().name());
+          }
+        });
+        lambdaOrdinal = lambdas.indexOf(location);
+      }
+    }
+    return new JavaSourcePosition(sourcePosition, location.declaringType(), method, lambdaOrdinal);
   }
 
   private static class JavaSourcePosition extends RemappedSourcePosition implements ExecutionPointHighlighter.HighlighterProvider {
     private final String myExpectedClassName;
     private final String myExpectedMethodName;
+    private final int myLambdaOrdinal;
 
-    public JavaSourcePosition(SourcePosition delegate, ReferenceType declaringType, Method method) {
+    public JavaSourcePosition(SourcePosition delegate, ReferenceType declaringType, Method method, int lambdaOrdinal) {
       super(delegate);
       myExpectedClassName = declaringType != null ? declaringType.name() : null;
       myExpectedMethodName = method != null ? method.name() : null;
+      myLambdaOrdinal = lambdaOrdinal;
     }
 
     private PsiElement remapElement(PsiElement element) {
@@ -202,9 +226,10 @@ public class PositionManagerImpl implements PositionManager, MultiRequestPositio
         else if ((method instanceof PsiMethod && myExpectedMethodName.equals(((PsiMethod)method).getName()))) {
           if (insideBody(element, ((PsiMethod)method).getBody())) return element;
         }
-        else if (method instanceof PsiLambdaExpression && myExpectedMethodName.startsWith(LambdaMethodFilter.LAMBDA_METHOD_PREFIX)) {
-          if (insideBody(element, ((PsiLambdaExpression)method).getBody())) return element;
-        }
+        //else if (method instanceof PsiLambdaExpression && (myLambdaOrdinal < 0 || myLambdaOrdinal == lambdaOrdinal)
+        //         && LambdaMethodFilter.isLambdaName(myExpectedMethodName)) {
+        //  if (insideBody(element, ((PsiLambdaExpression)method).getBody())) return element;
+        //}
       }
       return null;
     }
@@ -222,12 +247,64 @@ public class PositionManagerImpl implements PositionManager, MultiRequestPositio
       return ApplicationManager.getApplication().runReadAction(new Computable<SourcePosition>() {
         @Override
         public SourcePosition compute() {
-          // There may be more than one class/method code on the line, so we need to find out the correct place
-          for (PsiElement elem : getLineElements(original.getFile(), original.getLine())) {
-            PsiElement remappedElement = remapElement(elem);
-            if (remappedElement != null) {
-              if (remappedElement.getTextOffset() <= original.getOffset()) break;
-              return SourcePosition.createFromElement(remappedElement);
+          PsiFile file = original.getFile();
+          int line = original.getLine();
+          if (LambdaMethodFilter.isLambdaName(myExpectedMethodName) && myLambdaOrdinal > -1) {
+            Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
+            if (document == null || line >= document.getLineCount()) {
+              return original;
+            }
+            PsiElement element = original.getElementAt();
+            TextRange lineRange = new TextRange(document.getLineStartOffset(line), document.getLineEndOffset(line));
+            do {
+              PsiElement parent = element.getParent();
+              if (parent == null || (parent.getTextOffset() < lineRange.getStartOffset())) {
+                break;
+              }
+              element = parent;
+            }
+            while(true);
+            final List<PsiLambdaExpression> lambdas = new ArrayList<PsiLambdaExpression>(3);
+            // add initial lambda if we're inside already
+            NavigatablePsiElement method = PsiTreeUtil.getParentOfType(element, PsiMethod.class, PsiLambdaExpression.class);
+            if (method instanceof PsiLambdaExpression) {
+              lambdas.add((PsiLambdaExpression)method);
+            }
+            final PsiElementVisitor lambdaCollector = new JavaRecursiveElementVisitor() {
+              @Override
+              public void visitLambdaExpression(PsiLambdaExpression expression) {
+                super.visitLambdaExpression(expression);
+                lambdas.add(expression);
+              }
+            };
+            element.accept(lambdaCollector);
+            for (PsiElement sibling = getNextElement(element); sibling != null; sibling = getNextElement(sibling)) {
+              if (!lineRange.intersects(sibling.getTextRange())) {
+                break;
+              }
+              sibling.accept(lambdaCollector);
+            }
+            if (myLambdaOrdinal < lambdas.size()) {
+              PsiElement body = lambdas.get(myLambdaOrdinal).getBody();
+              if (body instanceof PsiCodeBlock) {
+                for (PsiStatement statement : ((PsiCodeBlock)body).getStatements()) {
+                  if (lineRange.intersects(statement.getTextRange())) {
+                    body = statement;
+                    break;
+                  }
+                }
+              }
+              return SourcePosition.createFromElement(body);
+            }
+          }
+          else {
+            // There may be more than one class/method code on the line, so we need to find out the correct place
+            for (PsiElement elem : getLineElements(file, line)) {
+              PsiElement remappedElement = remapElement(elem);
+              if (remappedElement != null) {
+                if (remappedElement.getTextOffset() <= original.getOffset()) break;
+                return SourcePosition.createFromElement(remappedElement);
+              }
             }
           }
           return original;
@@ -235,6 +312,14 @@ public class PositionManagerImpl implements PositionManager, MultiRequestPositio
       });
     }
 
+    private static PsiElement getNextElement(PsiElement element) {
+      PsiElement sibling = element.getNextSibling();
+      if (sibling != null) return sibling;
+      element = element.getParent();
+      if (element != null) return getNextElement(element);
+      return null;
+    }
+
     @Nullable
     @Override
     public RangeHighlighter createHighlighter(Document document, Project project, TextAttributes attributes) {
index 72820e0ebc64666eaf5739d5d0789465c849200b..be538d33116d10b4a7109b165af0e174c4f3ddd9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * Copyright 2000-2015 JetBrains s.r.o.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -81,7 +81,7 @@ public class StepIntoBreakpoint extends RunToCursorBreakpoint {
               final Method[] candidates = methods.toArray(new Method[methodsFound]);
               Arrays.sort(candidates, new Comparator<Method>() {
                 public int compare(Method m1, Method m2) {
-                  return getMethodOrdinal(m1) - getMethodOrdinal(m2);
+                  return LambdaMethodFilter.getLambdaOrdinal(m1.name()) - LambdaMethodFilter.getLambdaOrdinal(m2.name());
                 }
               });
               location = candidates[lambdaFilter.getLambdaOrdinal()].location();
@@ -118,20 +118,6 @@ public class StepIntoBreakpoint extends RunToCursorBreakpoint {
     }
   }
 
-  private static int getMethodOrdinal(Method m) {
-    final String name = m.name();
-    final int dollarIndex = name.lastIndexOf("$");
-    if (dollarIndex < 0) {
-      return 0;
-    }
-    try {
-      return Integer.parseInt(name.substring(dollarIndex + 1));
-    }
-    catch (NumberFormatException e) {
-      return 0;
-    }
-  }
-
   protected boolean acceptLocation(DebugProcessImpl debugProcess, ReferenceType classType, Location loc) {
     try {
       return myFilter.locationMatches(debugProcess, loc);