Debugger: Fix breakpoint applicability (KT-10984)
Ensure that breakpoints of each type can be placed only on lines where it makes sense to place a breakpoint. Here is a quick summary of the rules: 1. Method breakpoints are available for functions, property accessors, constructors; 2. Line breakpoints are available on any line with an expression, excluding some cases like 'const' property initializers or annotations; 3. Line breakpoints should be available on a '}' in functions and lambdas; 4. Line breakpoints are not suggested for one-liners; 5. Lambda breakpoints should be shown for single-line lambdas.
This commit is contained in:
@@ -243,6 +243,7 @@ debugger.filter.ignore.internal.classes=Do not step into Kotlin runtime library
|
|||||||
debugger.data.view.delegated.properties=Calculate values of delegated properties (may affect program execution)
|
debugger.data.view.delegated.properties=Calculate values of delegated properties (may affect program execution)
|
||||||
debugger.field.watchpoints.tab.title=Kotlin Field Watchpoints
|
debugger.field.watchpoints.tab.title=Kotlin Field Watchpoints
|
||||||
debugger.function.breakpoints.tab.title=Kotlin Function Breakpoints
|
debugger.function.breakpoints.tab.title=Kotlin Function Breakpoints
|
||||||
|
debugger.line.breakpoints.tab.title=Kotlin Line Breakpoints
|
||||||
debugger.field.watchpoints.properties.panel.field.access.label=Field &access
|
debugger.field.watchpoints.properties.panel.field.access.label=Field &access
|
||||||
debugger.field.watchpoints.properties.panel.field.modification.label=Field &modification
|
debugger.field.watchpoints.properties.panel.field.modification.label=Field &modification
|
||||||
debugger.field.watchpoints.properties.panel.field.initialization.label=Field &initialization
|
debugger.field.watchpoints.properties.panel.field.initialization.label=Field &initialization
|
||||||
|
|||||||
+8
-1
@@ -41,6 +41,7 @@ import org.jetbrains.kotlin.idea.debugger.breakpoints.dialog.AddFieldBreakpointD
|
|||||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
import org.jetbrains.kotlin.psi.KtDeclarationContainer
|
import org.jetbrains.kotlin.psi.KtDeclarationContainer
|
||||||
import org.jetbrains.kotlin.psi.KtFile
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.psi.KtParameter
|
||||||
import org.jetbrains.kotlin.psi.KtProperty
|
import org.jetbrains.kotlin.psi.KtProperty
|
||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
|
|
||||||
@@ -52,7 +53,13 @@ class KotlinFieldBreakpointType : JavaBreakpointType<KotlinPropertyBreakpointPro
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun canPutAt(file: VirtualFile, line: Int, project: Project): Boolean {
|
override fun canPutAt(file: VirtualFile, line: Int, project: Project): Boolean {
|
||||||
return canPutAt(file, line, project, this::class.java)
|
return isBreakpointApplicable(file, line, project) { element ->
|
||||||
|
when (element) {
|
||||||
|
is KtProperty -> ApplicabilityResult.definitely(!element.isLocal)
|
||||||
|
is KtParameter -> ApplicabilityResult.definitely(element.hasValOrVar())
|
||||||
|
else -> ApplicabilityResult.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getPriority() = 120
|
override fun getPriority() = 120
|
||||||
|
|||||||
+74
-5
@@ -29,6 +29,7 @@ import com.intellij.openapi.util.Comparing;
|
|||||||
import com.intellij.openapi.util.TextRange;
|
import com.intellij.openapi.util.TextRange;
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
|
import com.intellij.psi.impl.source.tree.LeafPsiElement;
|
||||||
import com.intellij.psi.PsiFile;
|
import com.intellij.psi.PsiFile;
|
||||||
import com.intellij.psi.PsiManager;
|
import com.intellij.psi.PsiManager;
|
||||||
import com.intellij.psi.util.PsiTreeUtil;
|
import com.intellij.psi.util.PsiTreeUtil;
|
||||||
@@ -40,16 +41,19 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;
|
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;
|
||||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties;
|
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties;
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinBundle;
|
||||||
import org.jetbrains.kotlin.idea.debugger.KotlinPositionManager;
|
import org.jetbrains.kotlin.idea.debugger.KotlinPositionManager;
|
||||||
import org.jetbrains.kotlin.psi.KtClassInitializer;
|
import org.jetbrains.kotlin.lexer.KtTokens;
|
||||||
import org.jetbrains.kotlin.psi.KtElement;
|
import org.jetbrains.kotlin.psi.KtElement;
|
||||||
import org.jetbrains.kotlin.psi.KtFunction;
|
import org.jetbrains.kotlin.psi.*;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.jetbrains.kotlin.idea.debugger.breakpoints.BreakpointTypeUtilsKt.isBreakpointApplicable;
|
||||||
|
|
||||||
public class KotlinLineBreakpointType extends JavaLineBreakpointType {
|
public class KotlinLineBreakpointType extends JavaLineBreakpointType {
|
||||||
public KotlinLineBreakpointType() {
|
public KotlinLineBreakpointType() {
|
||||||
super("kotlin-line", "Kotlin Line Breakpoints");
|
super("kotlin-line", KotlinBundle.message("debugger.line.breakpoints.tab.title"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -126,13 +130,50 @@ public class KotlinLineBreakpointType extends JavaLineBreakpointType {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canPutAt(@NotNull VirtualFile file, int line, @NotNull Project project) {
|
public boolean canPutAt(@NotNull VirtualFile file, int line, @NotNull Project project) {
|
||||||
return BreakpointTypeUtilsKt.canPutAt(file, line, project, getClass());
|
return isBreakpointApplicable(file, line, project, element -> {
|
||||||
|
if (element instanceof KtDestructuringDeclaration) {
|
||||||
|
return ApplicabilityResult.MAYBE_YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isClosingBraceInMethod(element)) {
|
||||||
|
return ApplicabilityResult.MAYBE_YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element instanceof KtElement) {
|
||||||
|
LineBreakpointExpressionVisitor visitor = LineBreakpointExpressionVisitor.of(file, line);
|
||||||
|
if (visitor != null) {
|
||||||
|
ApplicabilityResult result = ((KtElement) element).accept(visitor, null);
|
||||||
|
if (result == null) {
|
||||||
|
return ApplicabilityResult.UNKNOWN;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApplicabilityResult.UNKNOWN;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isClosingBraceInMethod(PsiElement element) {
|
||||||
|
if (element instanceof LeafPsiElement && element.getNode().getElementType() == KtTokens.RBRACE) {
|
||||||
|
PsiElement blockExpression = element.getParent();
|
||||||
|
if (blockExpression instanceof KtFunctionLiteral) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (blockExpression instanceof KtBlockExpression) {
|
||||||
|
PsiElement owner = blockExpression.getParent();
|
||||||
|
if (owner instanceof KtFunction || owner instanceof KtClassInitializer) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public List<JavaBreakpointVariant> computeVariants(@NotNull Project project, @NotNull XSourcePosition position) {
|
public List<JavaBreakpointVariant> computeVariants(@NotNull Project project, @NotNull XSourcePosition position) {
|
||||||
return BreakpointTypeUtilsKt.computeVariants(project, position, this);
|
return BreakpointTypeUtilsKt.computeLineBreakpointVariants(project, position, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@@ -195,4 +236,32 @@ public class KotlinLineBreakpointType extends JavaLineBreakpointType {
|
|||||||
|
|
||||||
return super.getSourcePosition(breakpoint);
|
return super.getSourcePosition(breakpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class LineKotlinBreakpointVariant extends LineJavaBreakpointVariant {
|
||||||
|
public LineKotlinBreakpointVariant(@NotNull XSourcePosition position, @Nullable PsiElement element, Integer lambdaOrdinal) {
|
||||||
|
super(position, element, lambdaOrdinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public String getText() {
|
||||||
|
return "Line Breakpoint";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class KotlinBreakpointVariant extends JavaBreakpointVariant {
|
||||||
|
private final int lambdaCount;
|
||||||
|
|
||||||
|
public KotlinBreakpointVariant(@NotNull XSourcePosition position, int lambdaCount) {
|
||||||
|
super(position);
|
||||||
|
this.lambdaCount = lambdaCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public String getText() {
|
||||||
|
String lambdas = lambdaCount > 1 ? "Lambdas" : "Lambda";
|
||||||
|
return "Line and " + lambdas + " Breakpoints";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+217
@@ -0,0 +1,217 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.debugger.breakpoints
|
||||||
|
|
||||||
|
import com.intellij.openapi.editor.Document
|
||||||
|
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||||
|
|
||||||
|
class LineBreakpointExpressionVisitor private constructor(
|
||||||
|
private val document: Document,
|
||||||
|
private val mainLine: Int
|
||||||
|
) : KtVisitor<ApplicabilityResult, Unit?>() {
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun of(file: VirtualFile, line: Int): LineBreakpointExpressionVisitor? {
|
||||||
|
val document = FileDocumentManager.getInstance().getDocument(file) ?: return null
|
||||||
|
return LineBreakpointExpressionVisitor(document, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitKtElement(element: KtElement, data: Unit?): ApplicabilityResult {
|
||||||
|
return visitChildren(element.children.asList(), data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun visitChildren(children: List<PsiElement>, data: Unit?): ApplicabilityResult {
|
||||||
|
if (children.isEmpty() || !children.first().isSameLine()) {
|
||||||
|
return ApplicabilityResult.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
var isApplicable = false
|
||||||
|
|
||||||
|
for (child in children) {
|
||||||
|
val ktElement = child as? KtElement ?: continue
|
||||||
|
val result = ktElement.accept(this, data)
|
||||||
|
if (result.shouldStop) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
isApplicable = isApplicable or result.isApplicable
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApplicabilityResult.maybe(isApplicable)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitExpression(expression: KtExpression, data: Unit?): ApplicabilityResult {
|
||||||
|
if (expression.isSameLine()) {
|
||||||
|
val superResult = super.visitExpression(expression, data)
|
||||||
|
if (superResult.shouldStop) {
|
||||||
|
return superResult
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApplicabilityResult.MAYBE_YES
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApplicabilityResult.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitClass(klass: KtClass, data: Unit?): ApplicabilityResult {
|
||||||
|
visitChildren(klass.primaryConstructorParameters, data).handle()?.let { return it }
|
||||||
|
return klass.body?.accept(this, data) ?: ApplicabilityResult.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitObjectDeclaration(declaration: KtObjectDeclaration, data: Unit?): ApplicabilityResult {
|
||||||
|
return declaration.body?.accept(this, data) ?: ApplicabilityResult.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitSecondaryConstructor(constructor: KtSecondaryConstructor, data: Unit?): ApplicabilityResult {
|
||||||
|
return constructor.bodyExpression?.accept(this, data)
|
||||||
|
?.acceptIfMultiLineParent(constructor)
|
||||||
|
?: ApplicabilityResult.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitParameter(parameter: KtParameter, data: Unit?): ApplicabilityResult {
|
||||||
|
val defaultValue = parameter.defaultValue
|
||||||
|
if (defaultValue.isSameLine()) {
|
||||||
|
return ApplicabilityResult.DEFINITELY_YES
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApplicabilityResult.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitCatchSection(catchClause: KtCatchClause, data: Unit?): ApplicabilityResult {
|
||||||
|
return ApplicabilityResult.MAYBE_YES
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitAnnotationEntry(annotationEntry: KtAnnotationEntry, data: Unit?): ApplicabilityResult {
|
||||||
|
return ApplicabilityResult.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitProperty(property: KtProperty, data: Unit?): ApplicabilityResult {
|
||||||
|
if (property.hasModifier(KtTokens.CONST_KEYWORD)) {
|
||||||
|
return ApplicabilityResult.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.visitProperty(property, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitNamedFunction(function: KtNamedFunction, data: Unit?): ApplicabilityResult {
|
||||||
|
if (function.isSameLine() && KtPsiUtil.isLocal(function)) {
|
||||||
|
return ApplicabilityResult.MAYBE_YES
|
||||||
|
}
|
||||||
|
|
||||||
|
visitChildren(function.valueParameters, data).handle()?.let { return it }
|
||||||
|
|
||||||
|
return function.bodyExpression?.accept(this, data)
|
||||||
|
?.acceptIfMultiLineParent(function)
|
||||||
|
?: ApplicabilityResult.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitPropertyAccessor(accessor: KtPropertyAccessor, data: Unit?): ApplicabilityResult {
|
||||||
|
return accessor.bodyExpression?.accept(this, data)
|
||||||
|
?.acceptIfMultiLineParent(accessor)
|
||||||
|
?: ApplicabilityResult.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitPropertyDelegate(delegate: KtPropertyDelegate, data: Unit?): ApplicabilityResult {
|
||||||
|
return ApplicabilityResult.maybe(delegate.isSameLine())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitLambdaExpression(expression: KtLambdaExpression, data: Unit?): ApplicabilityResult {
|
||||||
|
if (expression.isSameLine()) {
|
||||||
|
return ApplicabilityResult.DEFINITELY_YES
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApplicabilityResult.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitWhenEntry(jetWhenEntry: KtWhenEntry, data: Unit?): ApplicabilityResult {
|
||||||
|
jetWhenEntry.expression?.accept(this, data)?.handle()?.let { return it }
|
||||||
|
return ApplicabilityResult.maybe(jetWhenEntry.conditions.isNotEmpty() || jetWhenEntry.isElse)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitDestructuringDeclaration(multiDeclaration: KtDestructuringDeclaration, data: Unit?): ApplicabilityResult {
|
||||||
|
return ApplicabilityResult.maybe(multiDeclaration.isSameLine())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitBlockExpression(expression: KtBlockExpression, data: Unit?): ApplicabilityResult {
|
||||||
|
return visitChildren(expression.statements, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun visitParenthesizedExpression(expression: KtParenthesizedExpression, data: Unit?): ApplicabilityResult {
|
||||||
|
val parenthesized = expression.expression ?: return ApplicabilityResult.UNKNOWN
|
||||||
|
val lines = parenthesized.getLines()
|
||||||
|
|
||||||
|
if (lines.start > mainLine) {
|
||||||
|
return ApplicabilityResult.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lines.isMultiLine) {
|
||||||
|
return parenthesized.accept(this, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApplicabilityResult.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PsiElement?.isSameLine(): Boolean {
|
||||||
|
val lines = getLines()
|
||||||
|
return lines.start == mainLine
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PsiElement?.getLines(): Lines {
|
||||||
|
if (this == null) {
|
||||||
|
return Lines.EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
|
val startOffset = this.startOffset
|
||||||
|
val endOffset = this.endOffset
|
||||||
|
|
||||||
|
if (startOffset < 0 || endOffset < 0) {
|
||||||
|
return Lines.EMPTY
|
||||||
|
}
|
||||||
|
|
||||||
|
val startLine = document.getLineNumber(startOffset)
|
||||||
|
val endLine = document.getLineNumber(endOffset)
|
||||||
|
return Lines(startLine, endLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ApplicabilityResult.acceptIfMultiLineParent(parent: KtExpression): ApplicabilityResult? {
|
||||||
|
if (this.shouldStop) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent.getLines().isSingleLine) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ApplicabilityResult.handle(): ApplicabilityResult? {
|
||||||
|
if (this.shouldStop || this.isApplicable) {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Lines(val start: Int, val end: Int) {
|
||||||
|
companion object {
|
||||||
|
val EMPTY = Lines(-1, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
val isSingleLine: Boolean
|
||||||
|
get() = start == end && start >= 0
|
||||||
|
|
||||||
|
val isMultiLine: Boolean
|
||||||
|
get() = start != end && start >= 0 && end >= 0
|
||||||
|
}
|
||||||
+37
-74
@@ -39,51 +39,6 @@ import org.jetbrains.kotlin.psi.psiUtil.*
|
|||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
fun canPutAt(file: VirtualFile, line: Int, project: Project, breakpointTypeClass: Class<*>): Boolean {
|
|
||||||
val psiFile = PsiManager.getInstance(project).findFile(file)
|
|
||||||
|
|
||||||
if (psiFile == null || psiFile.virtualFile?.fileType != KotlinFileType.INSTANCE) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
val document = FileDocumentManager.getInstance().getDocument(file) ?: return false
|
|
||||||
|
|
||||||
var result: Class<*>? = null
|
|
||||||
XDebuggerUtil.getInstance().iterateLine(project, document, line, fun (el: PsiElement): Boolean {
|
|
||||||
// avoid comments
|
|
||||||
if (el is PsiWhiteSpace || PsiTreeUtil.getParentOfType(el, PsiComment::class.java, false) != null) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
var element = el
|
|
||||||
var parent = element.parent
|
|
||||||
while (parent != null) {
|
|
||||||
val offset = parent.textOffset
|
|
||||||
if (offset >= 0 && document.getLineNumber(offset) != line) break
|
|
||||||
|
|
||||||
element = parent
|
|
||||||
parent = element.parent
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element is KtProperty || element is KtParameter) {
|
|
||||||
result = if ((element is KtParameter && element.hasValOrVar()) || (element is KtProperty && !element.isLocal)) {
|
|
||||||
KotlinFieldBreakpointType::class.java
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
KotlinLineBreakpointType::class.java
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
result = KotlinLineBreakpointType::class.java
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
return result == breakpointTypeClass
|
|
||||||
}
|
|
||||||
|
|
||||||
class ApplicabilityResult(val isApplicable: Boolean, val shouldStop: Boolean) {
|
class ApplicabilityResult(val isApplicable: Boolean, val shouldStop: Boolean) {
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@@ -111,32 +66,34 @@ fun isBreakpointApplicable(file: VirtualFile, line: Int, project: Project, check
|
|||||||
}
|
}
|
||||||
|
|
||||||
val document = FileDocumentManager.getInstance().getDocument(file) ?: return false
|
val document = FileDocumentManager.getInstance().getDocument(file) ?: return false
|
||||||
var isApplicable = false
|
|
||||||
|
|
||||||
val checked = HashSet<PsiElement>()
|
return runReadAction {
|
||||||
|
var isApplicable = false
|
||||||
|
val checked = HashSet<PsiElement>()
|
||||||
|
|
||||||
XDebuggerUtil.getInstance().iterateLine(project, document, line, fun(element: PsiElement): Boolean {
|
XDebuggerUtil.getInstance().iterateLine(project, document, line, fun(element: PsiElement): Boolean {
|
||||||
if (element is PsiWhiteSpace || element.getParentOfType<PsiComment>(false) != null) {
|
if (element is PsiWhiteSpace || element.getParentOfType<PsiComment>(false) != null) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
val parent = getTopmostParentOnLineOrSelf(element, document, line)
|
val parent = getTopmostParentOnLineOrSelf(element, document, line)
|
||||||
if (!checked.add(parent)) {
|
if (!checked.add(parent)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = checker(parent)
|
val result = checker(parent)
|
||||||
|
|
||||||
if (result.shouldStop && !result.isApplicable) {
|
if (result.shouldStop && !result.isApplicable) {
|
||||||
isApplicable = false
|
isApplicable = false
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
isApplicable = isApplicable or result.isApplicable
|
isApplicable = isApplicable or result.isApplicable
|
||||||
return !result.shouldStop
|
return !result.shouldStop
|
||||||
})
|
})
|
||||||
|
|
||||||
return isApplicable
|
return@runReadAction isApplicable
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTopmostParentOnLineOrSelf(element: PsiElement, document: Document, line: Int): PsiElement {
|
private fun getTopmostParentOnLineOrSelf(element: PsiElement, document: Document, line: Int): PsiElement {
|
||||||
@@ -153,7 +110,7 @@ private fun getTopmostParentOnLineOrSelf(element: PsiElement, document: Document
|
|||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
|
|
||||||
fun computeVariants(
|
fun computeLineBreakpointVariants(
|
||||||
project: Project,
|
project: Project,
|
||||||
position: XSourcePosition,
|
position: XSourcePosition,
|
||||||
kotlinBreakpointType: KotlinLineBreakpointType
|
kotlinBreakpointType: KotlinLineBreakpointType
|
||||||
@@ -167,15 +124,19 @@ fun computeVariants(
|
|||||||
val result = LinkedList<JavaLineBreakpointType.JavaBreakpointVariant>()
|
val result = LinkedList<JavaLineBreakpointType.JavaBreakpointVariant>()
|
||||||
|
|
||||||
val elementAt = pos.elementAt.parentsWithSelf.firstIsInstance<KtElement>()
|
val elementAt = pos.elementAt.parentsWithSelf.firstIsInstance<KtElement>()
|
||||||
val mainMethod = KotlinLineBreakpointType.getContainingMethod(elementAt)
|
val mainMethod = PsiTreeUtil.getParentOfType(elementAt, KtFunction::class.java, false)
|
||||||
|
|
||||||
|
var mainMethodAdded = false
|
||||||
|
|
||||||
if (mainMethod != null) {
|
if (mainMethod != null) {
|
||||||
result.add(
|
val bodyExpression = mainMethod.bodyExpression
|
||||||
kotlinBreakpointType.LineJavaBreakpointVariant(
|
val isLambdaResult = bodyExpression is KtLambdaExpression && bodyExpression.functionLiteral in lambdas
|
||||||
position,
|
|
||||||
CodeInsightUtils.getTopmostElementAtOffset(elementAt, pos.offset),
|
if (!isLambdaResult) {
|
||||||
-1
|
val variantElement = CodeInsightUtils.getTopmostElementAtOffset(elementAt, pos.offset)
|
||||||
)
|
result.add(kotlinBreakpointType.LineKotlinBreakpointVariant(position, variantElement, -1))
|
||||||
)
|
mainMethodAdded = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lambdas.forEachIndexed { ordinal, lambda ->
|
lambdas.forEachIndexed { ordinal, lambda ->
|
||||||
@@ -186,7 +147,9 @@ fun computeVariants(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.add(kotlinBreakpointType.JavaBreakpointVariant(position))
|
if (mainMethodAdded && result.size > 1) {
|
||||||
|
result.add(kotlinBreakpointType.KotlinBreakpointVariant(position, lambdas.size))
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-8
@@ -5,7 +5,7 @@ class Derived2(): Base(1) {
|
|||||||
// constructor with body
|
// constructor with body
|
||||||
// EXPRESSION: p
|
// EXPRESSION: p
|
||||||
// RESULT: 1: I
|
// RESULT: 1: I
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
constructor(p: Int): this() {
|
constructor(p: Int): this() {
|
||||||
// EXPRESSION: p + 1
|
// EXPRESSION: p + 1
|
||||||
// RESULT: 2: I
|
// RESULT: 2: I
|
||||||
@@ -16,7 +16,7 @@ class Derived2(): Base(1) {
|
|||||||
// constructor without body
|
// constructor without body
|
||||||
// EXPRESSION: p1 + p2
|
// EXPRESSION: p1 + p2
|
||||||
// RESULT: 2: I
|
// RESULT: 2: I
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
constructor(p1: Int, p2: Int): this()
|
constructor(p1: Int, p2: Int): this()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +36,7 @@ fun main(args: Array<String>) {
|
|||||||
Derived1(1)
|
Derived1(1)
|
||||||
|
|
||||||
A()
|
A()
|
||||||
|
AA()
|
||||||
B()
|
B()
|
||||||
C(1)
|
C(1)
|
||||||
D()
|
D()
|
||||||
@@ -45,33 +46,40 @@ fun main(args: Array<String>) {
|
|||||||
|
|
||||||
// EXPRESSION: 1 + 1
|
// EXPRESSION: 1 + 1
|
||||||
// RESULT: 2: I
|
// RESULT: 2: I
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
class A
|
class A
|
||||||
|
|
||||||
|
// EXPRESSION: 1 + 3
|
||||||
|
// RESULT: 4: I
|
||||||
|
//FunctionBreakpoint!
|
||||||
|
class AA {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// EXPRESSION: 1 + 2
|
// EXPRESSION: 1 + 2
|
||||||
// RESULT: 3: I
|
// RESULT: 3: I
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
class B()
|
class B()
|
||||||
|
|
||||||
// EXPRESSION: a
|
// EXPRESSION: a
|
||||||
// RESULT: 1: I
|
// RESULT: 1: I
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
class C(val a: Int)
|
class C(val a: Int)
|
||||||
|
|
||||||
class D {
|
class D {
|
||||||
// EXPRESSION: 1 + 3
|
// EXPRESSION: 1 + 3
|
||||||
// RESULT: 4: I
|
// RESULT: 4: I
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
constructor()
|
constructor()
|
||||||
}
|
}
|
||||||
class E {
|
class E {
|
||||||
// EXPRESSION: i
|
// EXPRESSION: i
|
||||||
// RESULT: 1: I
|
// RESULT: 1: I
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
constructor(i: Int)
|
constructor(i: Int)
|
||||||
}
|
}
|
||||||
|
|
||||||
// EXPRESSION: a
|
// EXPRESSION: a
|
||||||
// RESULT: "foo": Ljava/lang/String;
|
// RESULT: "foo": Ljava/lang/String;
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
class F(val a: String)
|
class F(val a: String)
|
||||||
+19
-16
@@ -1,13 +1,14 @@
|
|||||||
LineBreakpoint created at constructors.kt:9
|
FunctionBreakpoint created at constructors.kt:9
|
||||||
LineBreakpoint created at constructors.kt:13
|
LineBreakpoint created at constructors.kt:13
|
||||||
LineBreakpoint created at constructors.kt:20
|
FunctionBreakpoint created at constructors.kt:20
|
||||||
LineBreakpoint created at constructors.kt:28
|
LineBreakpoint created at constructors.kt:28
|
||||||
LineBreakpoint created at constructors.kt:49
|
FunctionBreakpoint created at constructors.kt:50
|
||||||
LineBreakpoint created at constructors.kt:54
|
FunctionBreakpoint created at constructors.kt:55
|
||||||
LineBreakpoint created at constructors.kt:59
|
FunctionBreakpoint created at constructors.kt:62
|
||||||
LineBreakpoint created at constructors.kt:65
|
FunctionBreakpoint created at constructors.kt:67
|
||||||
LineBreakpoint created at constructors.kt:71
|
FunctionBreakpoint created at constructors.kt:73
|
||||||
LineBreakpoint created at constructors.kt:77
|
FunctionBreakpoint created at constructors.kt:79
|
||||||
|
FunctionBreakpoint created at constructors.kt:85
|
||||||
Run Java
|
Run Java
|
||||||
Connected to the target VM
|
Connected to the target VM
|
||||||
constructors.kt:9
|
constructors.kt:9
|
||||||
@@ -18,17 +19,19 @@ constructors.kt:20
|
|||||||
Compile bytecode for p1 + p2
|
Compile bytecode for p1 + p2
|
||||||
constructors.kt:28
|
constructors.kt:28
|
||||||
Compile bytecode for i1
|
Compile bytecode for i1
|
||||||
constructors.kt:49
|
constructors.kt:50
|
||||||
Compile bytecode for 1 + 1
|
Compile bytecode for 1 + 1
|
||||||
constructors.kt:54
|
constructors.kt:55
|
||||||
Compile bytecode for 1 + 2
|
|
||||||
constructors.kt:59
|
|
||||||
Compile bytecode for a
|
|
||||||
constructors.kt:65
|
|
||||||
Compile bytecode for 1 + 3
|
Compile bytecode for 1 + 3
|
||||||
constructors.kt:71
|
constructors.kt:62
|
||||||
|
Compile bytecode for 1 + 2
|
||||||
|
constructors.kt:67
|
||||||
|
Compile bytecode for a
|
||||||
|
constructors.kt:73
|
||||||
|
Compile bytecode for 1 + 3
|
||||||
|
constructors.kt:79
|
||||||
Compile bytecode for i
|
Compile bytecode for i
|
||||||
constructors.kt:77
|
constructors.kt:85
|
||||||
Compile bytecode for a
|
Compile bytecode for a
|
||||||
Disconnected from the target VM
|
Disconnected from the target VM
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -5,13 +5,13 @@ import kotlin.properties.Delegates
|
|||||||
// EXPRESSION: 1 + 1
|
// EXPRESSION: 1 + 1
|
||||||
// RESULT: 2: I
|
// RESULT: 2: I
|
||||||
val aGet: Int
|
val aGet: Int
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
get() = 1
|
get() = 1
|
||||||
|
|
||||||
// EXPRESSION: 1 + 2
|
// EXPRESSION: 1 + 2
|
||||||
// RESULT: 3: I
|
// RESULT: 3: I
|
||||||
val aGet2: Int
|
val aGet2: Int
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
get() { return 1 }
|
get() { return 1 }
|
||||||
|
|
||||||
fun fooWithBody(i: Int): Int {
|
fun fooWithBody(i: Int): Int {
|
||||||
@@ -23,23 +23,23 @@ fun fooWithBody(i: Int): Int {
|
|||||||
|
|
||||||
// EXPRESSION: i
|
// EXPRESSION: i
|
||||||
// RESULT: 2: I
|
// RESULT: 2: I
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
fun foo(i: Int) = i
|
fun foo(i: Int) = i
|
||||||
|
|
||||||
// EXPRESSION: i
|
// EXPRESSION: i
|
||||||
// RESULT: 2: I
|
// RESULT: 2: I
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
fun fooOneLine(i: Int): Int { return 1 }
|
fun fooOneLine(i: Int): Int { return 1 }
|
||||||
|
|
||||||
// EXPRESSION: i
|
// EXPRESSION: i
|
||||||
// RESULT: 2: I
|
// RESULT: 2: I
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
fun fooEmpty(i: Int) {}
|
fun fooEmpty(i: Int) {}
|
||||||
|
|
||||||
object A {
|
object A {
|
||||||
// EXPRESSION: test2()
|
// EXPRESSION: test2()
|
||||||
// RESULT: 2: I
|
// RESULT: 2: I
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
@JvmStatic fun fooWithoutBodyInsideObject() = test2()
|
@JvmStatic fun fooWithoutBodyInsideObject() = test2()
|
||||||
fun test2() = 2
|
fun test2() = 2
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-6
@@ -1,10 +1,10 @@
|
|||||||
LineBreakpoint created at withoutBodyFunctions.kt:9
|
FunctionBreakpoint created at withoutBodyFunctions.kt:9
|
||||||
LineBreakpoint created at withoutBodyFunctions.kt:15
|
FunctionBreakpoint created at withoutBodyFunctions.kt:15
|
||||||
LineBreakpoint created at withoutBodyFunctions.kt:21
|
LineBreakpoint created at withoutBodyFunctions.kt:21
|
||||||
LineBreakpoint created at withoutBodyFunctions.kt:27
|
FunctionBreakpoint created at withoutBodyFunctions.kt:27
|
||||||
LineBreakpoint created at withoutBodyFunctions.kt:32
|
FunctionBreakpoint created at withoutBodyFunctions.kt:32
|
||||||
LineBreakpoint created at withoutBodyFunctions.kt:37
|
FunctionBreakpoint created at withoutBodyFunctions.kt:37
|
||||||
LineBreakpoint created at withoutBodyFunctions.kt:43
|
FunctionBreakpoint created at withoutBodyFunctions.kt:43
|
||||||
Run Java
|
Run Java
|
||||||
Connected to the target VM
|
Connected to the target VM
|
||||||
withoutBodyFunctions.kt:9
|
withoutBodyFunctions.kt:9
|
||||||
|
|||||||
Vendored
+1
-1
@@ -4,7 +4,7 @@ import kotlin.properties.Delegates
|
|||||||
|
|
||||||
// EXPRESSION: i
|
// EXPRESSION: i
|
||||||
// RESULT: instance of java.lang.Integer(id=ID): Ljava/lang/Integer;
|
// RESULT: instance of java.lang.Integer(id=ID): Ljava/lang/Integer;
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
fun <T> foo(i: T) = i
|
fun <T> foo(i: T) = i
|
||||||
|
|
||||||
fun run(i: () -> Int) = 1
|
fun run(i: () -> Int) = 1
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
LineBreakpoint created at withoutBodyTypeParameters.kt:8
|
FunctionBreakpoint created at withoutBodyTypeParameters.kt:8
|
||||||
Run Java
|
Run Java
|
||||||
Connected to the target VM
|
Connected to the target VM
|
||||||
withoutBodyTypeParameters.kt:8
|
withoutBodyTypeParameters.kt:8
|
||||||
|
|||||||
idea/testData/debugger/tinyApp/src/evaluate/singleBreakpoint/extraVariables/evFunctionDeclaration.kt
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
package evFunctionDeclaration
|
package evFunctionDeclaration
|
||||||
|
|
||||||
class A(val a: Int) {
|
class A(val a: Int) {
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
fun foo() = a
|
fun foo() = a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
LineBreakpoint created at evFunctionDeclaration.kt:5
|
FunctionBreakpoint created at evFunctionDeclaration.kt:5
|
||||||
Run Java
|
Run Java
|
||||||
Connected to the target VM
|
Connected to the target VM
|
||||||
evFunctionDeclaration.kt:5
|
evFunctionDeclaration.kt:5
|
||||||
|
|||||||
@@ -3,12 +3,15 @@ package kt15259
|
|||||||
interface ObjectFace
|
interface ObjectFace
|
||||||
|
|
||||||
private fun makeFace() = object : ObjectFace {
|
private fun makeFace() = object : ObjectFace {
|
||||||
//Breakpoint!
|
//Breakpoint!
|
||||||
|
init { 5 }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
makeFace()
|
makeFace()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// STEP_OVER: 1
|
||||||
|
|
||||||
// EXPRESSION: this
|
// EXPRESSION: this
|
||||||
// RESULT: 'this' is not defined in this context
|
// RESULT: 'this' is not defined in this context
|
||||||
@@ -2,6 +2,7 @@ LineBreakpoint created at kt15259.kt:7
|
|||||||
Run Java
|
Run Java
|
||||||
Connected to the target VM
|
Connected to the target VM
|
||||||
kt15259.kt:7
|
kt15259.kt:7
|
||||||
|
kt15259.kt:8
|
||||||
Disconnected from the target VM
|
Disconnected from the target VM
|
||||||
|
|
||||||
Process finished with exit code 0
|
Process finished with exit code 0
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ fun main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val x: String = "x"
|
val x: String = "x"
|
||||||
//Breakpoint!
|
//FunctionBreakpoint!
|
||||||
get() = "foo" + field
|
get() = "foo" + field
|
||||||
|
|
||||||
// EXPRESSION: field
|
// EXPRESSION: field
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
LineBreakpoint created at staticField.kt:9
|
FunctionBreakpoint created at staticField.kt:9
|
||||||
Run Java
|
Run Java
|
||||||
Connected to the target VM
|
Connected to the target VM
|
||||||
staticField.kt:9
|
staticField.kt:9
|
||||||
|
|||||||
@@ -42,9 +42,7 @@ import com.intellij.xdebugger.breakpoints.XLineBreakpointType
|
|||||||
import com.sun.jdi.request.StepRequest
|
import com.sun.jdi.request.StepRequest
|
||||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties
|
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties
|
||||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties
|
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties
|
||||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpoint
|
import org.jetbrains.kotlin.idea.debugger.breakpoints.*
|
||||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpointType
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType
|
|
||||||
import org.jetbrains.kotlin.idea.debugger.stepping.*
|
import org.jetbrains.kotlin.idea.debugger.stepping.*
|
||||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
@@ -326,6 +324,9 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
|
|||||||
val condition = getPropertyFromComment(comment, "condition")
|
val condition = getPropertyFromComment(comment, "condition")
|
||||||
createLineBreakpoint(breakpointManager, file, lineIndex, ordinal, condition)
|
createLineBreakpoint(breakpointManager, file, lineIndex, ordinal, condition)
|
||||||
}
|
}
|
||||||
|
else if (comment.startsWith("//FunctionBreakpoint!")) {
|
||||||
|
createFunctionBreakpoint(breakpointManager, file, lineIndex)
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
throw AssertionError("Cannot create breakpoint at line ${lineIndex + 1}")
|
throw AssertionError("Cannot create breakpoint at line ${lineIndex + 1}")
|
||||||
}
|
}
|
||||||
@@ -353,6 +354,14 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createFunctionBreakpoint(breakpointManager: XBreakpointManager, file: PsiFile, lineIndex: Int) {
|
||||||
|
val breakpointType = findBreakpointType(KotlinFunctionBreakpointType::class.java)
|
||||||
|
val breakpoint = createBreakpointOfType(breakpointManager, breakpointType, lineIndex, file.virtualFile)
|
||||||
|
if (breakpoint is KotlinFunctionBreakpoint) {
|
||||||
|
println("FunctionBreakpoint created at ${file.virtualFile.name}:${lineIndex + 1}", ProcessOutputTypes.SYSTEM)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun createLineBreakpoint(
|
private fun createLineBreakpoint(
|
||||||
breakpointManager: XBreakpointManager,
|
breakpointManager: XBreakpointManager,
|
||||||
file: PsiFile,
|
file: PsiFile,
|
||||||
|
|||||||
Reference in New Issue
Block a user