Smart step into function literal
This commit is contained in:
@@ -228,7 +228,7 @@ public class JetPositionManager(private val myDebugProcess: DebugProcess) : Mult
|
|||||||
}
|
}
|
||||||
|
|
||||||
public fun classNameForPosition(sourcePosition: SourcePosition): String? {
|
public fun classNameForPosition(sourcePosition: SourcePosition): String? {
|
||||||
val psiElement = sourcePosition.getElementAt()
|
val psiElement = runReadAction { sourcePosition.getElementAt() }
|
||||||
if (psiElement == null) {
|
if (psiElement == null) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.debugger.stepping
|
||||||
|
|
||||||
|
import com.intellij.debugger.SourcePosition
|
||||||
|
import com.intellij.debugger.engine.BreakpointStepMethodFilter
|
||||||
|
import com.intellij.debugger.engine.DebugProcessImpl
|
||||||
|
import com.intellij.debugger.engine.LambdaMethodFilter
|
||||||
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
|
import com.intellij.psi.PsiElementFactory
|
||||||
|
import com.intellij.util.Range
|
||||||
|
import com.sun.jdi.Location
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
|
import org.jetbrains.kotlin.psi.JetBlockExpression
|
||||||
|
import org.jetbrains.kotlin.psi.JetFunctionLiteralExpression
|
||||||
|
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
||||||
|
|
||||||
|
public class KotlinLambdaMethodFilter(
|
||||||
|
lambda: JetFunctionLiteralExpression,
|
||||||
|
private val myCallingExpressionLines: Range<Int>
|
||||||
|
): BreakpointStepMethodFilter {
|
||||||
|
private val myFirstStatementPosition: SourcePosition?
|
||||||
|
private val myLastStatementLine: Int
|
||||||
|
|
||||||
|
init {
|
||||||
|
var firstStatementPosition: SourcePosition? = null
|
||||||
|
var lastStatementPosition: SourcePosition? = null
|
||||||
|
val body = lambda.getBodyExpression()!!
|
||||||
|
val statements = body.getStatements()
|
||||||
|
if (statements.isNotEmpty()) {
|
||||||
|
firstStatementPosition = SourcePosition.createFromElement(statements.first())
|
||||||
|
if (firstStatementPosition != null) {
|
||||||
|
val lastStatement = statements.last()
|
||||||
|
lastStatementPosition = SourcePosition.createFromOffset(firstStatementPosition.getFile(), lastStatement.getTextRange().getEndOffset())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
myFirstStatementPosition = firstStatementPosition
|
||||||
|
myLastStatementLine = if (lastStatementPosition != null) lastStatementPosition.getLine() else -1
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBreakpointPosition(): SourcePosition? {
|
||||||
|
return myFirstStatementPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLastStatementLine(): Int {
|
||||||
|
return myLastStatementLine
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean {
|
||||||
|
val method = location.method()
|
||||||
|
return isLambdaName(method.name())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCallingExpressionLines(): Range<Int>? {
|
||||||
|
return myCallingExpressionLines
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
public fun isLambdaName(name: String?): Boolean {
|
||||||
|
return name == OperatorConventions.INVOKE.asString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.debugger.stepping
|
||||||
|
|
||||||
|
import com.intellij.debugger.actions.MethodSmartStepTarget
|
||||||
|
import com.intellij.debugger.actions.SmartStepTarget
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiMethod
|
||||||
|
import com.intellij.util.Range
|
||||||
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.JetIcons
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.psi.JetElement
|
||||||
|
import org.jetbrains.kotlin.psi.JetFunctionLiteralExpression
|
||||||
|
import org.jetbrains.kotlin.types.expressions.OperatorConventions
|
||||||
|
import javax.swing.Icon
|
||||||
|
|
||||||
|
public class KotlinLambdaSmartStepTarget(
|
||||||
|
label: String,
|
||||||
|
highlightElement: JetFunctionLiteralExpression,
|
||||||
|
lines: Range<Int>
|
||||||
|
): SmartStepTarget(label, highlightElement, true, lines) {
|
||||||
|
override fun getIcon() = JetIcons.LAMBDA
|
||||||
|
|
||||||
|
fun getLambda() = getHighlightElement() as JetFunctionLiteralExpression
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun calcLabel(descriptor: DeclarationDescriptor, paramName: Name): String {
|
||||||
|
return "${descriptor.getName().asString()}: ${paramName.asString()}.${OperatorConventions.INVOKE.asString()}()"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,25 +20,23 @@ import com.intellij.debugger.SourcePosition
|
|||||||
import com.intellij.debugger.actions.JvmSmartStepIntoHandler
|
import com.intellij.debugger.actions.JvmSmartStepIntoHandler
|
||||||
import com.intellij.debugger.actions.MethodSmartStepTarget
|
import com.intellij.debugger.actions.MethodSmartStepTarget
|
||||||
import com.intellij.debugger.actions.SmartStepTarget
|
import com.intellij.debugger.actions.SmartStepTarget
|
||||||
import com.intellij.debugger.engine.BasicStepMethodFilter
|
|
||||||
import com.intellij.debugger.engine.DebugProcessImpl
|
|
||||||
import com.intellij.debugger.engine.MethodFilter
|
import com.intellij.debugger.engine.MethodFilter
|
||||||
import com.intellij.psi.PsiDocumentManager
|
import com.intellij.psi.PsiDocumentManager
|
||||||
import com.intellij.psi.PsiMethod
|
import com.intellij.psi.PsiMethod
|
||||||
import com.intellij.util.Range
|
import com.intellij.util.Range
|
||||||
import com.intellij.util.containers.OrderedSet
|
import com.intellij.util.containers.OrderedSet
|
||||||
import com.sun.jdi.Location
|
|
||||||
import org.jetbrains.kotlin.asJava.LightClassUtil
|
import org.jetbrains.kotlin.asJava.LightClassUtil
|
||||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||||
import org.jetbrains.kotlin.idea.debugger.MockSourcePosition
|
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext
|
import org.jetbrains.kotlin.resolve.BindingContext
|
||||||
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getParentCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
|
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||||
|
|
||||||
public class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
|
public class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
|
||||||
|
|
||||||
@@ -68,7 +66,18 @@ public class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
|
|||||||
element.accept(object: JetTreeVisitorVoid() {
|
element.accept(object: JetTreeVisitorVoid() {
|
||||||
|
|
||||||
override fun visitFunctionLiteralExpression(expression: JetFunctionLiteralExpression) {
|
override fun visitFunctionLiteralExpression(expression: JetFunctionLiteralExpression) {
|
||||||
// skip calls in function literals
|
val context = expression.analyzeAndGetResult().bindingContext
|
||||||
|
val resolvedCall = expression.getParentCall(context).getResolvedCall(context)
|
||||||
|
if (resolvedCall != null && !InlineUtil.isInline(resolvedCall.getResultingDescriptor())) {
|
||||||
|
val arguments = resolvedCall.getValueArguments()
|
||||||
|
for ((param, argument) in arguments) {
|
||||||
|
if (argument.getArguments().any { it.getArgumentExpression() == expression}) {
|
||||||
|
val label = KotlinLambdaSmartStepTarget.calcLabel(resolvedCall.getResultingDescriptor(), param.getName())
|
||||||
|
result.add(KotlinLambdaSmartStepTarget(label, expression, lines))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitObjectLiteralExpression(expression: JetObjectLiteralExpression) {
|
override fun visitObjectLiteralExpression(expression: JetObjectLiteralExpression) {
|
||||||
@@ -177,9 +186,10 @@ public class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun createMethodFilter(stepTarget: SmartStepTarget?): MethodFilter? {
|
override fun createMethodFilter(stepTarget: SmartStepTarget?): MethodFilter? {
|
||||||
if (stepTarget is KotlinMethodSmartStepTarget) {
|
return when (stepTarget) {
|
||||||
return KotlinBasicStepMethodFilter(stepTarget)
|
is KotlinMethodSmartStepTarget -> KotlinBasicStepMethodFilter(stepTarget)
|
||||||
|
is KotlinLambdaSmartStepTarget -> KotlinLambdaMethodFilter(stepTarget.getLambda(), stepTarget.getCallingExpressionLines()!! )
|
||||||
|
else -> super.createMethodFilter(stepTarget)
|
||||||
}
|
}
|
||||||
return super.createMethodFilter(stepTarget)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -7,4 +7,4 @@ fun foo() {
|
|||||||
fun f1(f: () -> Unit) {}
|
fun f1(f: () -> Unit) {}
|
||||||
fun f2() {}
|
fun f2() {}
|
||||||
|
|
||||||
// EXISTS: f1(Function0<? extends Unit>)
|
// EXISTS: f1(Function0<? extends Unit>), f1: f.invoke()
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
fun foo() {
|
||||||
|
<caret>f1() { }
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun f1(f: () -> Unit) {}
|
||||||
|
|
||||||
|
// EXISTS: f1(Function0<? extends Unit>)
|
||||||
@@ -23,6 +23,7 @@ import com.intellij.psi.util.PsiFormatUtil
|
|||||||
import com.intellij.psi.util.PsiFormatUtilBase
|
import com.intellij.psi.util.PsiFormatUtilBase
|
||||||
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
|
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
|
||||||
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
|
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.stepping.KotlinLambdaSmartStepTarget
|
||||||
import org.jetbrains.kotlin.idea.debugger.stepping.KotlinSmartStepIntoHandler
|
import org.jetbrains.kotlin.idea.debugger.stepping.KotlinSmartStepIntoHandler
|
||||||
import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase
|
import org.jetbrains.kotlin.idea.test.JetLightCodeInsightFixtureTestCase
|
||||||
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
|
||||||
@@ -40,7 +41,7 @@ public abstract class AbstractSmartStepIntoTest : JetLightCodeInsightFixtureTest
|
|||||||
|
|
||||||
val position = MockSourcePosition(_file = fixture.getFile(), _line = line, _offset = offset, _editor = fixture.getEditor())
|
val position = MockSourcePosition(_file = fixture.getFile(), _line = line, _offset = offset, _editor = fixture.getEditor())
|
||||||
|
|
||||||
val actual = KotlinSmartStepIntoHandler().findSmartStepTargets(position).map { renderTarget(it) }
|
val actual = KotlinSmartStepIntoHandler().findSmartStepTargets(position).map { it.getPresentation() }
|
||||||
|
|
||||||
val expected = InTextDirectivesUtils.findListWithPrefixes(fixture.getFile()?.getText()!!.replace("\\,", "+++"), "// EXISTS: ").map { it.replace("+++", ",") }
|
val expected = InTextDirectivesUtils.findListWithPrefixes(fixture.getFile()?.getText()!!.replace("\\,", "+++"), "// EXISTS: ").map { it.replace("+++", ",") }
|
||||||
|
|
||||||
@@ -73,30 +74,6 @@ public abstract class AbstractSmartStepIntoTest : JetLightCodeInsightFixtureTest
|
|||||||
return sb.toString()
|
return sb.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderTarget(target: SmartStepTarget): String {
|
|
||||||
val sb = StringBuilder()
|
|
||||||
|
|
||||||
val label = target.getLabel()
|
|
||||||
if (label != null) {
|
|
||||||
sb.append(label)
|
|
||||||
}
|
|
||||||
when (target) {
|
|
||||||
is MethodSmartStepTarget -> {
|
|
||||||
sb.append(PsiFormatUtil.formatMethod(
|
|
||||||
target.getMethod(),
|
|
||||||
PsiSubstitutor.EMPTY,
|
|
||||||
PsiFormatUtilBase.SHOW_NAME or PsiFormatUtilBase.SHOW_PARAMETERS,
|
|
||||||
PsiFormatUtilBase.SHOW_TYPE,
|
|
||||||
999
|
|
||||||
))
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
sb.append("Renderer for ${target.javaClass} should be implemented")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getTestDataPath(): String? {
|
override fun getTestDataPath(): String? {
|
||||||
return PluginTestCaseBase.getTestDataPathBase() + "/debugger/smartStepInto"
|
return PluginTestCaseBase.getTestDataPathBase() + "/debugger/smartStepInto"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,12 @@ public class SmartStepIntoTestGenerated extends AbstractSmartStepIntoTest {
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("inlinedFunLiteral.kt")
|
||||||
|
public void testInlinedFunLiteral() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/smartStepInto/inlinedFunLiteral.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("invoke.kt")
|
@TestMetadata("invoke.kt")
|
||||||
public void testInvoke() throws Exception {
|
public void testInvoke() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/smartStepInto/invoke.kt");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/debugger/smartStepInto/invoke.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user