Refactoring: extract top-level functions from KotlinSteppingCommandProvider
(cherry picked from commit 2daac45)
This commit is contained in:
committed by
Nikolay Krasko
parent
dd42420a1e
commit
60fbcb7e38
+184
-179
@@ -72,13 +72,15 @@ class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
|
|||||||
val file = sourcePosition.file as? KtFile ?: return null
|
val file = sourcePosition.file as? KtFile ?: return null
|
||||||
if (sourcePosition.line < 0) return null
|
if (sourcePosition.line < 0) return null
|
||||||
|
|
||||||
val containingFunction = sourcePosition.elementAt.parents.firstOrNull { it is KtNamedFunction && !it.isLocal } ?: return null
|
val containingFunction = sourcePosition.elementAt.parents
|
||||||
|
.filterIsInstance<KtNamedFunction>()
|
||||||
|
.firstOrNull { !it.isLocal } ?: return null
|
||||||
|
|
||||||
val startLineNumber = containingFunction.getLineNumber(true)
|
val startLineNumber = containingFunction.getLineNumber(true) + 1
|
||||||
val endLineNumber = containingFunction.getLineNumber(false)
|
val endLineNumber = containingFunction.getLineNumber(false) + 1
|
||||||
if (startLineNumber > endLineNumber) return null
|
if (startLineNumber > endLineNumber) return null
|
||||||
|
|
||||||
val linesRange = startLineNumber + 1..endLineNumber + 1
|
val linesRange = startLineNumber..endLineNumber
|
||||||
|
|
||||||
val inlineArgumentsToSkip = getElementsToSkip(containingFunction as KtNamedFunction, sourcePosition) ?: return null
|
val inlineArgumentsToSkip = getElementsToSkip(containingFunction as KtNamedFunction, sourcePosition) ?: return null
|
||||||
|
|
||||||
@@ -87,122 +89,6 @@ class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
|
|||||||
return DebuggerSteppingHelper.createStepOverCommand(suspendContext, ignoreBreakpoints, file, linesRange, inlineArgumentsToSkip, additionalElementsToSkip)
|
return DebuggerSteppingHelper.createStepOverCommand(suspendContext, ignoreBreakpoints, file, linesRange, inlineArgumentsToSkip, additionalElementsToSkip)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getElementsToSkip(containingFunction: KtNamedFunction, sourcePosition: SourcePosition): List<KtFunction>? {
|
|
||||||
val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition)
|
|
||||||
if (!inlineFunctionCalls.isEmpty()) {
|
|
||||||
val inlineArguments = getInlineArgumentsIfAny(inlineFunctionCalls)
|
|
||||||
if (inlineArguments.isNotEmpty() && inlineArguments.all { !it.shouldNotUseStepOver(sourcePosition.elementAt) }) {
|
|
||||||
return inlineArguments
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inlineArguments.isEmpty() && inlineFunctionCalls.all { !it.shouldNotUseStepOver(sourcePosition.elementAt) }) {
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (InlineUtil.isInline(containingFunction.resolveToDescriptor())) {
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun PsiElement.getAdditionalElementsToSkip(): List<PsiElement> {
|
|
||||||
val result = arrayListOf<PsiElement>()
|
|
||||||
val ifParent = getParentOfType<KtIfExpression>(false)
|
|
||||||
if (ifParent != null) {
|
|
||||||
if (ifParent.then.contains(this)) {
|
|
||||||
ifParent.elseKeyword?.let { result.add(it) }
|
|
||||||
ifParent.`else`?.let { result.add(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val tryParent = getParentOfType<KtTryExpression>(false)
|
|
||||||
if (tryParent != null) {
|
|
||||||
val catchClause = getParentOfType<KtCatchClause>(false)
|
|
||||||
if (catchClause != null) {
|
|
||||||
result.addAll(tryParent.catchClauses.filter { it != catchClause })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val whenEntry = getParentOfType<KtWhenEntry>(false)
|
|
||||||
if (whenEntry != null) {
|
|
||||||
if (whenEntry.expression.contains(this)) {
|
|
||||||
val whenParent = whenEntry.getParentOfType<KtWhenExpression>(false)
|
|
||||||
if (whenParent != null) {
|
|
||||||
result.addAll(whenParent.entries.filter { it != whenEntry })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun PsiElement.shouldNotUseStepOver(elementAt: PsiElement): Boolean {
|
|
||||||
val ifParent = getParentOfType<KtIfExpression>(false)
|
|
||||||
if (ifParent != null) {
|
|
||||||
// if (inlineFunCall()) {...}
|
|
||||||
if (ifParent.condition.contains(this)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
<caret>if (...) inlineFunCall()
|
|
||||||
else inlineFunCall()
|
|
||||||
*/
|
|
||||||
val ifParentElementAt = elementAt.getParentOfType<KtIfExpression>(false)
|
|
||||||
if (ifParentElementAt == null) {
|
|
||||||
if (ifParent.then.contains(this)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (ifParent.`else`.contains(this)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val tryParent = getParentOfType<KtTryExpression>(false)
|
|
||||||
if (tryParent != null) {
|
|
||||||
/* try { inlineFunCall() }
|
|
||||||
catch()...
|
|
||||||
*/
|
|
||||||
if (tryParent.tryBlock.contains(this)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val whenEntry = getParentOfType<KtWhenEntry>(false)
|
|
||||||
if (whenEntry != null) {
|
|
||||||
// <caret>inlineFunCall -> ...
|
|
||||||
if (whenEntry.conditions.any { it.contains(this) } ) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// <caret>1 == 2 -> inlineFunCall()
|
|
||||||
if (whenEntry.expression.contains(this)) {
|
|
||||||
val parentEntryElementAt = elementAt.getParentOfType<KtWhenEntry>(false) ?: return true
|
|
||||||
return parentEntryElementAt == whenEntry &&
|
|
||||||
whenEntry.conditions.any { it.contains(elementAt) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val whileParent = getParentOfType<KtWhileExpression>(false)
|
|
||||||
if (whileParent != null) {
|
|
||||||
// while (inlineFunCall()) {...}
|
|
||||||
if (whileParent.condition.contains(this)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// last statement in while
|
|
||||||
return (whileParent.body as? KtBlockExpression)?.statements?.lastOrNull()?.getLineNumber() == elementAt.getLineNumber()
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun PsiElement?.contains(element: PsiElement): Boolean {
|
|
||||||
return this?.textRange?.contains(element.textRange) ?: false
|
|
||||||
}
|
|
||||||
|
|
||||||
@TestOnly
|
@TestOnly
|
||||||
fun getStepOutCommand(suspendContext: SuspendContextImpl, debugContext: DebuggerContextImpl): DebugProcessImpl.ResumeCommand? {
|
fun getStepOutCommand(suspendContext: SuspendContextImpl, debugContext: DebuggerContextImpl): DebugProcessImpl.ResumeCommand? {
|
||||||
return getStepOutCommand(suspendContext, debugContext.sourcePosition)
|
return getStepOutCommand(suspendContext, debugContext.sourcePosition)
|
||||||
@@ -228,79 +114,196 @@ class KotlinSteppingCommandProvider: JvmSteppingCommandProvider() {
|
|||||||
|
|
||||||
return DebuggerSteppingHelper.createStepOutCommand(suspendContext, true, inlineFunctions, inlinedArgument)
|
return DebuggerSteppingHelper.createStepOutCommand(suspendContext, true, inlineFunctions, inlinedArgument)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getInlineFunctionsIfAny(file: KtFile, offset: Int): List<KtNamedFunction> {
|
private fun getElementsToSkip(containingFunction: KtNamedFunction, sourcePosition: SourcePosition): List<KtFunction>? {
|
||||||
val elementAt = file.findElementAt(offset) ?: return emptyList()
|
val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition)
|
||||||
val containingFunction = elementAt.getParentOfType<KtNamedFunction>(false) ?: return emptyList()
|
if (!inlineFunctionCalls.isEmpty()) {
|
||||||
|
val inlineArguments = getInlineArgumentsIfAny(inlineFunctionCalls)
|
||||||
|
if (inlineArguments.isNotEmpty() && inlineArguments.all { !it.shouldNotUseStepOver(sourcePosition.elementAt) }) {
|
||||||
|
return inlineArguments
|
||||||
|
}
|
||||||
|
|
||||||
val descriptor = containingFunction.resolveToDescriptor()
|
if (inlineArguments.isEmpty() && inlineFunctionCalls.all { !it.shouldNotUseStepOver(sourcePosition.elementAt) }) {
|
||||||
if (!InlineUtil.isInline(descriptor)) return emptyList()
|
return emptyList()
|
||||||
|
|
||||||
val inlineFunctionsCalls = DebuggerUtils.analyzeElementWithInline(
|
|
||||||
containingFunction.getResolutionFacade(),
|
|
||||||
containingFunction.analyzeFully(),
|
|
||||||
containingFunction,
|
|
||||||
false
|
|
||||||
).filterIsInstance<KtNamedFunction>()
|
|
||||||
|
|
||||||
return inlineFunctionsCalls
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getInlineArgumentsIfAny(inlineFunctionCalls: List<KtCallExpression>): List<KtFunction> {
|
|
||||||
return inlineFunctionCalls.flatMap {
|
|
||||||
it.valueArguments
|
|
||||||
.map { getArgumentExpression(it) }
|
|
||||||
.filterIsInstance<KtFunction>()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
|
// Step over calls to lambda arguments in inline function while execution is already in that function
|
||||||
|
if (InlineUtil.isInline(containingFunction.resolveToDescriptor())) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
private fun getInlineFunctionCallsIfAny(sourcePosition: SourcePosition): List<KtCallExpression> {
|
return null
|
||||||
val file = sourcePosition.file as? KtFile ?: return emptyList()
|
}
|
||||||
val lineNumber = sourcePosition.line
|
|
||||||
var elementAt = sourcePosition.elementAt
|
|
||||||
|
|
||||||
var startOffset = file.getLineStartOffset(lineNumber) ?: elementAt.startOffset
|
private fun PsiElement.getAdditionalElementsToSkip(): List<PsiElement> {
|
||||||
val endOffset = file.getLineEndOffset(lineNumber) ?: elementAt.endOffset
|
val result = arrayListOf<PsiElement>()
|
||||||
|
val ifParent = getParentOfType<KtIfExpression>(false)
|
||||||
|
if (ifParent != null) {
|
||||||
|
if (ifParent.then.contains(this)) {
|
||||||
|
ifParent.elseKeyword?.let { result.add(it) }
|
||||||
|
ifParent.`else`?.let { result.add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val tryParent = getParentOfType<KtTryExpression>(false)
|
||||||
|
if (tryParent != null) {
|
||||||
|
val catchClause = getParentOfType<KtCatchClause>(false)
|
||||||
|
if (catchClause != null) {
|
||||||
|
result.addAll(tryParent.catchClauses.filter { it != catchClause })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var topMostElement: PsiElement? = null
|
val whenEntry = getParentOfType<KtWhenEntry>(false)
|
||||||
while (topMostElement !is KtElement && startOffset < endOffset) {
|
if (whenEntry != null) {
|
||||||
elementAt = file.findElementAt(startOffset)
|
if (whenEntry.expression.contains(this)) {
|
||||||
if (elementAt != null) {
|
val whenParent = whenEntry.getParentOfType<KtWhenExpression>(false)
|
||||||
topMostElement = CodeInsightUtils.getTopmostElementAtOffset(elementAt, startOffset)
|
if (whenParent != null) {
|
||||||
|
result.addAll(whenParent.entries.filter { it != whenEntry })
|
||||||
}
|
}
|
||||||
startOffset++
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PsiElement.shouldNotUseStepOver(elementAt: PsiElement): Boolean {
|
||||||
|
val ifParent = getParentOfType<KtIfExpression>(false)
|
||||||
|
if (ifParent != null) {
|
||||||
|
// if (inlineFunCall()) {...}
|
||||||
|
if (ifParent.condition.contains(this)) {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (topMostElement !is KtElement) return emptyList()
|
/*
|
||||||
|
<caret>if (...) inlineFunCall()
|
||||||
val start = topMostElement.startOffset
|
else inlineFunCall()
|
||||||
val end = topMostElement.endOffset
|
*/
|
||||||
|
val ifParentElementAt = elementAt.getParentOfType<KtIfExpression>(false)
|
||||||
fun isInlineCall(expr: KtExpression): Boolean {
|
if (ifParentElementAt == null) {
|
||||||
val context = expr.analyze(BodyResolveMode.PARTIAL)
|
if (ifParent.then.contains(this)) {
|
||||||
val resolvedCall = expr.getResolvedCall(context) ?: return false
|
return true
|
||||||
return InlineUtil.isInline(resolvedCall.resultingDescriptor)
|
}
|
||||||
}
|
if (ifParent.`else`.contains(this)) {
|
||||||
|
return true
|
||||||
val allInlineFunctionCalls = CodeInsightUtils.
|
|
||||||
findElementsOfClassInRange(file, start, end, KtExpression::class.java)
|
|
||||||
.map { KtPsiUtil.getParentCallIfPresent(it as KtExpression) }
|
|
||||||
.filterIsInstance<KtCallExpression>()
|
|
||||||
.filter { isInlineCall(it) }
|
|
||||||
.toSet()
|
|
||||||
|
|
||||||
// It is necessary to check range because of multiline assign
|
|
||||||
var linesRange = lineNumber..lineNumber
|
|
||||||
return allInlineFunctionCalls.filter {
|
|
||||||
val shouldInclude = it.getLineNumber() in linesRange
|
|
||||||
if (shouldInclude) {
|
|
||||||
linesRange = Math.min(linesRange.start, it.getLineNumber())..Math.max(linesRange.endInclusive, it.getLineNumber(false))
|
|
||||||
}
|
}
|
||||||
shouldInclude
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val tryParent = getParentOfType<KtTryExpression>(false)
|
||||||
|
if (tryParent != null) {
|
||||||
|
/* try { inlineFunCall() }
|
||||||
|
catch()...
|
||||||
|
*/
|
||||||
|
if (tryParent.tryBlock.contains(this)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val whenEntry = getParentOfType<KtWhenEntry>(false)
|
||||||
|
if (whenEntry != null) {
|
||||||
|
// <caret>inlineFunCall -> ...
|
||||||
|
if (whenEntry.conditions.any { it.contains(this) } ) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// <caret>1 == 2 -> inlineFunCall()
|
||||||
|
if (whenEntry.expression.contains(this)) {
|
||||||
|
val parentEntryElementAt = elementAt.getParentOfType<KtWhenEntry>(false) ?: return true
|
||||||
|
return parentEntryElementAt == whenEntry &&
|
||||||
|
whenEntry.conditions.any { it.contains(elementAt) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val whileParent = getParentOfType<KtWhileExpression>(false)
|
||||||
|
if (whileParent != null) {
|
||||||
|
// while (inlineFunCall()) {...}
|
||||||
|
if (whileParent.condition.contains(this)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// last statement in while
|
||||||
|
return (whileParent.body as? KtBlockExpression)?.statements?.lastOrNull()?.getLineNumber() == elementAt.getLineNumber()
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PsiElement?.contains(element: PsiElement): Boolean {
|
||||||
|
return this?.textRange?.contains(element.textRange) ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInlineFunctionsIfAny(file: KtFile, offset: Int): List<KtNamedFunction> {
|
||||||
|
val elementAt = file.findElementAt(offset) ?: return emptyList()
|
||||||
|
val containingFunction = elementAt.getParentOfType<KtNamedFunction>(false) ?: return emptyList()
|
||||||
|
|
||||||
|
val descriptor = containingFunction.resolveToDescriptor()
|
||||||
|
if (!InlineUtil.isInline(descriptor)) return emptyList()
|
||||||
|
|
||||||
|
val inlineFunctionsCalls = DebuggerUtils.analyzeElementWithInline(
|
||||||
|
containingFunction.getResolutionFacade(),
|
||||||
|
containingFunction.analyzeFully(),
|
||||||
|
containingFunction,
|
||||||
|
false
|
||||||
|
).filterIsInstance<KtNamedFunction>()
|
||||||
|
|
||||||
|
return inlineFunctionsCalls
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getInlineArgumentsIfAny(inlineFunctionCalls: List<KtCallExpression>): List<KtFunction> {
|
||||||
|
return inlineFunctionCalls.flatMap {
|
||||||
|
it.valueArguments
|
||||||
|
.map { getArgumentExpression(it) }
|
||||||
|
.filterIsInstance<KtFunction>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
|
||||||
|
|
||||||
|
private fun getInlineFunctionCallsIfAny(sourcePosition: SourcePosition): List<KtCallExpression> {
|
||||||
|
val file = sourcePosition.file as? KtFile ?: return emptyList()
|
||||||
|
val lineNumber = sourcePosition.line
|
||||||
|
var elementAt = sourcePosition.elementAt
|
||||||
|
|
||||||
|
var startOffset = file.getLineStartOffset(lineNumber) ?: elementAt.startOffset
|
||||||
|
val endOffset = file.getLineEndOffset(lineNumber) ?: elementAt.endOffset
|
||||||
|
|
||||||
|
var topMostElement: PsiElement? = null
|
||||||
|
while (topMostElement !is KtElement && startOffset < endOffset) {
|
||||||
|
elementAt = file.findElementAt(startOffset)
|
||||||
|
if (elementAt != null) {
|
||||||
|
topMostElement = CodeInsightUtils.getTopmostElementAtOffset(elementAt, startOffset)
|
||||||
|
}
|
||||||
|
startOffset++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topMostElement !is KtElement) return emptyList()
|
||||||
|
|
||||||
|
val start = topMostElement.startOffset
|
||||||
|
val end = topMostElement.endOffset
|
||||||
|
|
||||||
|
fun isInlineCall(expr: KtExpression): Boolean {
|
||||||
|
val context = expr.analyze(BodyResolveMode.PARTIAL)
|
||||||
|
val resolvedCall = expr.getResolvedCall(context) ?: return false
|
||||||
|
return InlineUtil.isInline(resolvedCall.resultingDescriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
val allInlineFunctionCalls = CodeInsightUtils.
|
||||||
|
findElementsOfClassInRange(file, start, end, KtExpression::class.java)
|
||||||
|
.map { KtPsiUtil.getParentCallIfPresent(it as KtExpression) }
|
||||||
|
.filterIsInstance<KtCallExpression>()
|
||||||
|
.filter { isInlineCall(it) }
|
||||||
|
.toSet()
|
||||||
|
|
||||||
|
// It is necessary to check range because of multiline assign
|
||||||
|
var linesRange = lineNumber..lineNumber
|
||||||
|
return allInlineFunctionCalls.filter {
|
||||||
|
val shouldInclude = it.getLineNumber() in linesRange
|
||||||
|
if (shouldInclude) {
|
||||||
|
linesRange = Math.min(linesRange.start, it.getLineNumber())..Math.max(linesRange.endInclusive, it.getLineNumber(false))
|
||||||
|
}
|
||||||
|
shouldInclude
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Action(val position: XSourcePositionImpl?) {
|
sealed class Action(val position: XSourcePositionImpl?) {
|
||||||
@@ -362,6 +365,8 @@ fun getStepOverPosition(
|
|||||||
return Action.STEP_OVER()
|
return Action.STEP_OVER()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Predict step over location by assuming that it will be next valid location in the range of current function.
|
||||||
|
// This prediction doesn't work in branching position when there're several valid location after current position.
|
||||||
val locations = computedReferenceType.allLineLocations()
|
val locations = computedReferenceType.allLineLocations()
|
||||||
.dropWhile { it != location }
|
.dropWhile { it != location }
|
||||||
.drop(1)
|
.drop(1)
|
||||||
|
|||||||
Reference in New Issue
Block a user