mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[debugger] inline breakpoints, fix races during inlay drawing, IDEA-337960
Two breakpoints were removed one after another and triggered inlay redraw. First one collected one inlay, second collected no inlays. Second erased all inlays in editor, first brought them back. ^IDEA-337960 fixed ^IDEA-324621 GitOrigin-RevId: 7809133a12242132ed1b6d34e3f5ffefcb073ed5
This commit is contained in:
committed by
intellij-monorepo-bot
parent
ebc37f0177
commit
41e5ca1754
+89
-84
@@ -2,7 +2,7 @@
|
||||
package com.intellij.xdebugger.impl.breakpoints
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.application.readAction
|
||||
import com.intellij.openapi.application.readAndWriteAction
|
||||
import com.intellij.openapi.application.writeAction
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
@@ -21,6 +21,7 @@ import com.intellij.openapi.util.registry.RegistryValue
|
||||
import com.intellij.openapi.util.registry.RegistryValueListener
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.util.DocumentUtil
|
||||
import com.intellij.util.concurrency.annotations.RequiresReadLock
|
||||
import com.intellij.util.concurrency.annotations.RequiresWriteLock
|
||||
import com.intellij.util.containers.toMutableSmartList
|
||||
import com.intellij.util.ui.update.MergingUpdateQueue
|
||||
@@ -151,48 +152,53 @@ internal class InlineBreakpointInlayManager(private val project: Project, privat
|
||||
private suspend fun redraw(document: Document, onlyLine: Int?, onlyEditor: Editor?) {
|
||||
val startStamp = document.modificationStamp
|
||||
|
||||
fun retryLater() {
|
||||
redrawQueue.queue(Update.create(Pair(document, onlyLine)) {
|
||||
scope.launch {
|
||||
redraw(document, onlyLine, onlyEditor)
|
||||
fun postponeOnChanged(): Boolean {
|
||||
val documentAndPsiAreOutOfSync = !PsiDocumentManager.getInstance(project).isCommitted(document)
|
||||
val documentIsOutdated = document.modificationStamp != startStamp
|
||||
return if (documentAndPsiAreOutOfSync || documentIsOutdated) {
|
||||
redrawQueue.queue(Update.create(Pair(document, onlyLine)) {
|
||||
scope.launch {
|
||||
redraw(document, onlyLine, onlyEditor)
|
||||
}
|
||||
})
|
||||
true
|
||||
}
|
||||
else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
if (postponeOnChanged()) return
|
||||
// Double-checked now.
|
||||
|
||||
readAndWriteAction {
|
||||
if (postponeOnChanged()) return@readAndWriteAction value(Unit)
|
||||
|
||||
val allBreakpoints = allBreakpointsIn(document)
|
||||
|
||||
val inlays = mutableListOf<SingleInlayDatum>()
|
||||
if (onlyLine != null) {
|
||||
if (!DocumentUtil.isValidLine(onlyLine, document)) return@readAndWriteAction value(Unit)
|
||||
|
||||
val breakpoints = allBreakpoints.filter { it.line == onlyLine }
|
||||
if (!breakpoints.isEmpty()) {
|
||||
inlays += collectInlays(document, onlyLine, breakpoints)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// We need Document and PSI to be in sync. Also ensure that document was not changed.
|
||||
fun shouldRetry() =
|
||||
!PsiDocumentManager.getInstance(project).isCommitted(document) || document.modificationStamp != startStamp
|
||||
|
||||
if (shouldRetry()) {
|
||||
retryLater()
|
||||
return
|
||||
}
|
||||
|
||||
val allBreakpoints = allBreakpointsIn(document)
|
||||
|
||||
val inlays = mutableListOf<SingleInlayDatum>()
|
||||
if (onlyLine != null) {
|
||||
if (!DocumentUtil.isValidLine(onlyLine, document)) return
|
||||
|
||||
val breakpoints = allBreakpoints.filter { it.line == onlyLine }
|
||||
if (!breakpoints.isEmpty()) {
|
||||
inlays += collectInlays(document, onlyLine, breakpoints)
|
||||
}
|
||||
}
|
||||
else {
|
||||
for ((line, breakpoints) in allBreakpoints.groupBy { it.line }) {
|
||||
// We could process lines concurrently, but it doesn't seem to be really required.
|
||||
inlays += collectInlays(document, line, breakpoints)
|
||||
}
|
||||
}
|
||||
|
||||
writeAction {
|
||||
if (shouldRetry()) {
|
||||
retryLater()
|
||||
return@writeAction
|
||||
else {
|
||||
for ((line, breakpoints) in allBreakpoints.groupBy { it.line }) {
|
||||
// We could process lines concurrently, but it doesn't seem to be really required.
|
||||
inlays += collectInlays(document, line, breakpoints)
|
||||
}
|
||||
}
|
||||
|
||||
insertInlays(document, onlyEditor, onlyLine, inlays)
|
||||
if (postponeOnChanged()) return@readAndWriteAction value(Unit)
|
||||
|
||||
writeAction {
|
||||
if (postponeOnChanged()) return@writeAction
|
||||
|
||||
insertInlays(document, onlyEditor, onlyLine, inlays)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,62 +213,61 @@ internal class InlineBreakpointInlayManager(private val project: Project, privat
|
||||
val offset: Int,
|
||||
)
|
||||
|
||||
private suspend fun collectInlays(document: Document,
|
||||
@RequiresReadLock
|
||||
private fun collectInlays(document: Document,
|
||||
line: Int,
|
||||
breakpoints: List<XLineBreakpointImpl<*>>): List<SingleInlayDatum> {
|
||||
return readAction {
|
||||
if (!DocumentUtil.isValidLine(line, document)) return@readAction emptyList()
|
||||
if (!DocumentUtil.isValidLine(line, document)) return emptyList()
|
||||
|
||||
val file = FileDocumentManager.getInstance().getFile(document) ?: return@readAction emptyList()
|
||||
val linePosition = XSourcePositionImpl.create(file, line)
|
||||
val breakpointTypes = XBreakpointUtil.getAvailableLineBreakpointTypes(project, linePosition, null)
|
||||
val file = FileDocumentManager.getInstance().getFile(document) ?: return emptyList()
|
||||
val linePosition = XSourcePositionImpl.create(file, line)
|
||||
val breakpointTypes = XBreakpointUtil.getAvailableLineBreakpointTypes(project, linePosition, null)
|
||||
|
||||
val variants =
|
||||
if (breakpointTypes.isNotEmpty()) {
|
||||
XDebuggerUtilImpl.getLineBreakpointVariantsSync(project, breakpointTypes, linePosition)
|
||||
// No need to show "all" variant in case of the inline breakpoints approach, it's useful only for the popup based one.
|
||||
.filter { !isAllVariant(it) }
|
||||
}
|
||||
else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
val codeStartOffset = DocumentUtil.getLineStartIndentedOffset(document, line)
|
||||
|
||||
if (!shouldAlwaysShowAllInlays() &&
|
||||
breakpoints.size == 1 &&
|
||||
(variants.isEmpty() ||
|
||||
variants.size == 1 && areMatching(variants[0], breakpoints[0], codeStartOffset))) {
|
||||
// No need to show inline variants when there is only one breakpoint and one matching variant (or no variants at all).
|
||||
return@readAction emptyList()
|
||||
val variants =
|
||||
if (breakpointTypes.isNotEmpty()) {
|
||||
XDebuggerUtilImpl.getLineBreakpointVariantsSync(project, breakpointTypes, linePosition)
|
||||
// No need to show "all" variant in case of the inline breakpoints approach, it's useful only for the popup based one.
|
||||
.filter { !isAllVariant(it) }
|
||||
}
|
||||
else {
|
||||
emptyList()
|
||||
}
|
||||
|
||||
buildList {
|
||||
val remainingBreakpoints = breakpoints.toMutableSmartList()
|
||||
for (variant in variants) {
|
||||
val breakpointsHere = remainingBreakpoints.filter { areMatching(variant, it, codeStartOffset) }
|
||||
if (!breakpointsHere.isEmpty()) {
|
||||
for (breakpointHere in breakpointsHere) {
|
||||
remainingBreakpoints.remove(breakpointHere)
|
||||
val offset = getBreakpointRangeStartOffset(breakpointHere, codeStartOffset)
|
||||
// TODO[inline-bp]: introduce better way to check that it's simple line breakpoint,
|
||||
// it should be possible when we are able to better match variants and breakpoints
|
||||
val singleLineBreakpoint = breakpoints.size == 1 && offset == codeStartOffset
|
||||
if (!singleLineBreakpoint || shouldAlwaysShowAllInlays()) {
|
||||
add(SingleInlayDatum(breakpointHere, variant, offset))
|
||||
}
|
||||
val codeStartOffset = DocumentUtil.getLineStartIndentedOffset(document, line)
|
||||
|
||||
if (!shouldAlwaysShowAllInlays() &&
|
||||
breakpoints.size == 1 &&
|
||||
(variants.isEmpty() ||
|
||||
variants.size == 1 && areMatching(variants[0], breakpoints[0], codeStartOffset))) {
|
||||
// No need to show inline variants when there is only one breakpoint and one matching variant (or no variants at all).
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
return buildList {
|
||||
val remainingBreakpoints = breakpoints.toMutableSmartList()
|
||||
for (variant in variants) {
|
||||
val breakpointsHere = remainingBreakpoints.filter { areMatching(variant, it, codeStartOffset) }
|
||||
if (!breakpointsHere.isEmpty()) {
|
||||
for (breakpointHere in breakpointsHere) {
|
||||
remainingBreakpoints.remove(breakpointHere)
|
||||
val offset = getBreakpointRangeStartOffset(breakpointHere, codeStartOffset)
|
||||
// TODO[inline-bp]: introduce better way to check that it's simple line breakpoint,
|
||||
// it should be possible when we are able to better match variants and breakpoints
|
||||
val singleLineBreakpoint = breakpoints.size == 1 && offset == codeStartOffset
|
||||
if (!singleLineBreakpoint || shouldAlwaysShowAllInlays()) {
|
||||
add(SingleInlayDatum(breakpointHere, variant, offset))
|
||||
}
|
||||
}
|
||||
else {
|
||||
val offset = getBreakpointVariantRangeStartOffset(variant, codeStartOffset)
|
||||
add(SingleInlayDatum(null, variant, offset))
|
||||
}
|
||||
}
|
||||
for (remainingBreakpoint in remainingBreakpoints) {
|
||||
val offset = getBreakpointRangeStartOffset(remainingBreakpoint, codeStartOffset)
|
||||
add(SingleInlayDatum(remainingBreakpoint, null, offset))
|
||||
else {
|
||||
val offset = getBreakpointVariantRangeStartOffset(variant, codeStartOffset)
|
||||
add(SingleInlayDatum(null, variant, offset))
|
||||
}
|
||||
}
|
||||
for (remainingBreakpoint in remainingBreakpoints) {
|
||||
val offset = getBreakpointRangeStartOffset(remainingBreakpoint, codeStartOffset)
|
||||
add(SingleInlayDatum(remainingBreakpoint, null, offset))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user