diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt index 86549103c5..152498d4ed 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/CodeWhispererPopupManager.kt @@ -41,9 +41,11 @@ import com.intellij.ui.popup.AbstractPopup import com.intellij.ui.popup.PopupFactoryImpl import com.intellij.util.messages.Topic import com.intellij.util.ui.UIUtil +import kotlinx.coroutines.sync.Semaphore import software.amazon.awssdk.services.codewhispererruntime.model.Import import software.amazon.awssdk.services.codewhispererruntime.model.Reference import software.aws.toolkits.core.utils.debug +import software.aws.toolkits.core.utils.error import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.jetbrains.services.codewhisperer.editor.CodeWhispererEditorManager import software.aws.toolkits.jetbrains.services.codewhisperer.layout.CodeWhispererLayoutConfig.addHorizontalGlue @@ -85,7 +87,8 @@ import javax.swing.JLabel class CodeWhispererPopupManager { val popupComponents = CodeWhispererPopupComponents() - var shouldListenerCancelPopup: Boolean = true + // Act like a semaphore: one increment only corresponds to one decrement + var allowEditsDuringSuggestionPreview = Semaphore(MAX_EDIT_SOURCE_DURING_SUGGESTION_PREVIEW) var sessionContext = SessionContext() private set @@ -245,12 +248,20 @@ class CodeWhispererPopupManager { } } + // Don't want to block or throw any kinds of exceptions here if it can continue to provide suggestions fun dontClosePopupAndRun(runnable: () -> Unit) { - try { - shouldListenerCancelPopup = false - runnable() - } finally { - shouldListenerCancelPopup = true + if (allowEditsDuringSuggestionPreview.tryAcquire()) { + try { + runnable() + } finally { + try { + allowEditsDuringSuggestionPreview.release() + } catch (e: Exception) { + LOG.error(e) { "Failed to release allowEditsDuringSuggestionPreview semaphore" } + } + } + } else { + LOG.error { "Failed to acquire allowEditsDuringSuggestionPreview semaphore" } } } @@ -496,7 +507,7 @@ class CodeWhispererPopupManager { val editor = states.requestContext.editor val codewhispererSelectionListener: SelectionListener = object : SelectionListener { override fun selectionChanged(event: SelectionEvent) { - if (shouldListenerCancelPopup) { + if (allowEditsDuringSuggestionPreview.availablePermits == MAX_EDIT_SOURCE_DURING_SUGGESTION_PREVIEW) { cancelPopup(states.popup) } super.selectionChanged(event) @@ -512,7 +523,7 @@ class CodeWhispererPopupManager { if (!delete) return if (editor.caretModel.offset == event.offset) { changeStates(states, 0) - } else if (shouldListenerCancelPopup) { + } else if (allowEditsDuringSuggestionPreview.availablePermits == MAX_EDIT_SOURCE_DURING_SUGGESTION_PREVIEW) { cancelPopup(states.popup) } } @@ -521,7 +532,7 @@ class CodeWhispererPopupManager { val codewhispererCaretListener: CaretListener = object : CaretListener { override fun caretPositionChanged(event: CaretEvent) { - if (shouldListenerCancelPopup) { + if (allowEditsDuringSuggestionPreview.availablePermits == MAX_EDIT_SOURCE_DURING_SUGGESTION_PREVIEW) { cancelPopup(states.popup) } super.caretPositionChanged(event) @@ -702,6 +713,7 @@ class CodeWhispererPopupManager { "CodeWhisperer user action performed", CodeWhispererUserActionListener::class.java ) + const val MAX_EDIT_SOURCE_DURING_SUGGESTION_PREVIEW = 2 } } diff --git a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPopupIntelliSenseAcceptListener.kt b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPopupIntelliSenseAcceptListener.kt index 6a6eb4b888..1d401f4fcd 100644 --- a/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPopupIntelliSenseAcceptListener.kt +++ b/plugins/amazonq/codewhisperer/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codewhisperer/popup/listeners/CodeWhispererPopupIntelliSenseAcceptListener.kt @@ -8,8 +8,11 @@ import com.intellij.codeInsight.lookup.LookupEvent import com.intellij.codeInsight.lookup.LookupListener import com.intellij.codeInsight.lookup.LookupManagerListener import com.intellij.codeInsight.lookup.impl.LookupImpl +import software.aws.toolkits.core.utils.error +import software.aws.toolkits.core.utils.getLogger import software.aws.toolkits.jetbrains.services.codewhisperer.model.InvocationContext import software.aws.toolkits.jetbrains.services.codewhisperer.popup.CodeWhispererPopupManager +import software.aws.toolkits.jetbrains.services.codewhisperer.popup.listeners.CodeWhispererPopupIntelliSenseAcceptListener.Companion.LOG import software.aws.toolkits.jetbrains.services.codewhisperer.service.CodeWhispererInvocationStatus class CodeWhispererPopupIntelliSenseAcceptListener(private val states: InvocationContext) : LookupManagerListener { @@ -18,14 +21,17 @@ class CodeWhispererPopupIntelliSenseAcceptListener(private val states: Invocatio addIntelliSenseAcceptListener(newLookup, states) } + + companion object { + val LOG = getLogger() + } } fun addIntelliSenseAcceptListener(lookup: Lookup, states: InvocationContext) { + if (!CodeWhispererPopupManager.getInstance().allowEditsDuringSuggestionPreview.tryAcquire()) { + LOG.error { "Failed to acquire allowEditsDuringSuggestionPreview semaphore" } + } lookup.addLookupListener(object : LookupListener { - override fun beforeItemSelected(event: LookupEvent): Boolean { - CodeWhispererPopupManager.getInstance().shouldListenerCancelPopup = false - return super.beforeItemSelected(event) - } override fun itemSelected(event: LookupEvent) { if (!CodeWhispererInvocationStatus.getInstance().isDisplaySessionActive() || !(event.lookup as LookupImpl).isShown @@ -46,7 +52,11 @@ fun addIntelliSenseAcceptListener(lookup: Lookup, states: InvocationContext) { private fun cleanup() { lookup.removeLookupListener(this) - CodeWhispererPopupManager.getInstance().shouldListenerCancelPopup = true + try { + CodeWhispererPopupManager.getInstance().allowEditsDuringSuggestionPreview.release() + } catch (e: Exception) { + LOG.error(e) { "Failed to release allowEditsDuringSuggestionPreview semaphore" } + } } }) }