mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-199180 add proper state for CommittingDocuments phase
Several typed handlers can trigger auto-popup and we need to process them independently because each request has its own condition, and we need to start auto-popup if any of them succeeds. GitOrigin-RevId: c54148d8de7ff2e384cc37071294535baa6a9e50
This commit is contained in:
committed by
intellij-monorepo-bot
parent
9239a0120f
commit
33d20484c4
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.completion
|
||||
|
||||
import com.intellij.codeInsight.completion.CompletionPhase.CommittingDocuments.CommittingState.*
|
||||
import com.intellij.codeInsight.completion.CompletionPhase.Companion.NoCompletion
|
||||
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
|
||||
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl.Companion.assertPhase
|
||||
@@ -76,32 +77,69 @@ sealed class CompletionPhase @ApiStatus.Internal constructor(
|
||||
internal var replaced: Boolean = false
|
||||
|
||||
private val myTracker: ActionTracker = ActionTracker(editor, this)
|
||||
private var myRequestCount = 1
|
||||
private var myState: CommittingState = InProgress(1)
|
||||
|
||||
fun ignoreCurrentDocumentChange() {
|
||||
myTracker.ignoreCurrentDocumentChange()
|
||||
}
|
||||
|
||||
/**
|
||||
* @return `true` if the phase won't trigger the completion process.
|
||||
*/
|
||||
@get:ApiStatus.Internal
|
||||
val isExpired: Boolean
|
||||
get() = myTracker.hasAnythingHappened() || myRequestCount <= 0
|
||||
get() = myTracker.hasAnythingHappened() || myState !is InProgress
|
||||
|
||||
private val expirationReason: String
|
||||
get() = myTracker.describeChangeEvent() + "; myRequestCount = $myRequestCount"
|
||||
|
||||
internal fun incrementRequestCount() {
|
||||
myRequestCount++
|
||||
LOG.trace { "Increment request count :: new myRequestCount=$myRequestCount" }
|
||||
/**
|
||||
* Several typedHandlers can request auto-popup completion during processing of a single event.
|
||||
* We need to trigger read-action for them independently because they can have different conditions for starting completion.
|
||||
*/
|
||||
private fun addRequest() {
|
||||
when (val cur = myState) {
|
||||
Cancelled, Disposed, Success -> {
|
||||
LOG.error("Cannot add request for a finished phase: $cur")
|
||||
return
|
||||
}
|
||||
is InProgress -> {
|
||||
myState = InProgress(cur.requests + 1)
|
||||
}
|
||||
}
|
||||
LOG.trace { "Increment request count :: new myState=$myState" }
|
||||
}
|
||||
|
||||
private fun decrementRequestCount() {
|
||||
myRequestCount--
|
||||
LOG.trace { "Decrement request count :: new myRequestCount=$myRequestCount" }
|
||||
/**
|
||||
* the current request was not successful (most likely because of the failed condition), but other requests still have a chance to succeed.
|
||||
*/
|
||||
private fun cancelThisRequest() {
|
||||
when (val cur = myState) {
|
||||
Disposed, Cancelled, Success -> {
|
||||
/* do nothing */
|
||||
}
|
||||
|
||||
is InProgress -> {
|
||||
val requests = cur.requests
|
||||
if (requests > 1) {
|
||||
myState = InProgress(cur.requests - 1)
|
||||
}
|
||||
else {
|
||||
if (requests < 1) {
|
||||
LOG.error("Invalid request count: $requests")
|
||||
}
|
||||
myState = Cancelled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG.trace { "Cancel request :: new myState=$myState" }
|
||||
}
|
||||
|
||||
/**
|
||||
* At least one of the requests succeeded, the completion process has been started, no more requests are necessary.
|
||||
*/
|
||||
private fun requestCompleted() {
|
||||
LOG.trace { "Request completed" }
|
||||
myRequestCount = 0
|
||||
LOG.assertTrue(myState is InProgress, "myState=$myState")
|
||||
myState = Success
|
||||
}
|
||||
|
||||
override fun newCompletionStarted(time: Int, repeated: Boolean): Int {
|
||||
@@ -110,7 +148,7 @@ sealed class CompletionPhase @ApiStatus.Internal constructor(
|
||||
|
||||
override fun dispose() {
|
||||
LOG.trace { "Dispose completion phase: $this" }
|
||||
myRequestCount = 0
|
||||
myState = Disposed
|
||||
if (!replaced && indicator != null) {
|
||||
indicator.closeAndFinish(true)
|
||||
}
|
||||
@@ -120,6 +158,18 @@ sealed class CompletionPhase @ApiStatus.Internal constructor(
|
||||
return "CommittingDocuments{hasIndicator=${indicator != null}}"
|
||||
}
|
||||
|
||||
/**
|
||||
* InProgress(1) -> InProgress(2...Xxx) -> InProgress(1) -> Cancelled -> Disposed
|
||||
* |--------------|------------------------|---------> Success-------^
|
||||
*
|
||||
*/
|
||||
private sealed interface CommittingState {
|
||||
object Success : CommittingState
|
||||
object Cancelled : CommittingState
|
||||
object Disposed : CommittingState
|
||||
data class InProgress(val requests: Int) : CommittingState
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
companion object {
|
||||
private val ourExecutor = AppExecutorUtil.createBoundedApplicationPoolExecutor("Completion Preparation", 1)
|
||||
@@ -178,8 +228,8 @@ sealed class CompletionPhase @ApiStatus.Internal constructor(
|
||||
autopopup: Boolean,
|
||||
project: Project,
|
||||
): Editor? {
|
||||
if (phase.isExpired) {
|
||||
LOG.trace("Phase is expired")
|
||||
if (phase.myState !is InProgress) {
|
||||
LOG.trace { "Phase is expired ${phase.myState}" }
|
||||
return null
|
||||
}
|
||||
LOG.trace { "Start non-blocking read action :: phase=${phase.replaced}" }
|
||||
@@ -206,17 +256,36 @@ sealed class CompletionPhase @ApiStatus.Internal constructor(
|
||||
) {
|
||||
LOG.trace { "Finish on UI thread :: completionEditor=$completionEditor" }
|
||||
|
||||
if (completionEditor == null || phase.isExpired) {
|
||||
if (phase.myState !is InProgress) {
|
||||
LOG.trace { "Phase is expired :: myState=${phase.myState}" }
|
||||
return
|
||||
}
|
||||
|
||||
if (completionEditor == null) {
|
||||
// preparation has failed for this specific request. We must cancel only this request.
|
||||
// If no other requests are pending, we can cancel the phase altogether.
|
||||
|
||||
if (phase == CompletionServiceImpl.completionPhase) {
|
||||
LOG.trace { "Setting NoCompletion phase :: completionEditor=$completionEditor, expirationReason=${phase.expirationReason}" }
|
||||
phase.decrementRequestCount()
|
||||
if (phase.isExpired) {
|
||||
LOG.trace { "Setting NoCompletion phase :: completionEditor=$completionEditor, expirationReason=editor is null" }
|
||||
phase.cancelThisRequest()
|
||||
if (phase.myState == Cancelled) {
|
||||
CompletionServiceImpl.setCompletionPhase(NoCompletion)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (phase.myTracker.hasAnythingHappened()) {
|
||||
// activity has happened in the editor. We must cancel all the requests altogether
|
||||
|
||||
if (phase == CompletionServiceImpl.completionPhase) {
|
||||
LOG.trace { "Setting NoCompletion phase :: completionEditor=$completionEditor, expirationReason=${phase.myTracker.describeChangeEvent()}" }
|
||||
phase.cancelPhase()
|
||||
CompletionServiceImpl.setCompletionPhase(NoCompletion)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
LOG.trace { "Starting completion phase :: completionEditor=$completionEditor" }
|
||||
|
||||
phase.requestCompleted()
|
||||
@@ -237,7 +306,7 @@ sealed class CompletionPhase @ApiStatus.Internal constructor(
|
||||
if (currentPhase is CommittingDocuments && !currentPhase.isExpired && event == currentPhase.event) {
|
||||
LOG.assertTrue(prevIndicator == currentPhase.indicator, "Indicators must match. prevIndicator=$prevIndicator, currentPhase=$currentPhase, currentPhase.indicator=${currentPhase.indicator}")
|
||||
|
||||
currentPhase.incrementRequestCount()
|
||||
currentPhase.addRequest()
|
||||
return currentPhase
|
||||
}
|
||||
}
|
||||
@@ -280,6 +349,13 @@ sealed class CompletionPhase @ApiStatus.Internal constructor(
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelPhase() {
|
||||
when (myState) {
|
||||
Cancelled, Disposed, Success -> {}
|
||||
is InProgress -> myState = Cancelled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Synchronous internal constructor(indicator: CompletionProgressIndicator) : CompletionPhase(indicator) {
|
||||
|
||||
Reference in New Issue
Block a user