Commit 6ecc71f2 authored by Sergey Stoyanovsky's avatar Sergey Stoyanovsky Committed by intellij-monorepo-bot
Browse files

ML-1067 Handle multiple inline completion providers

GitOrigin-RevId: 13a234e79689b8704b956645fb945f352555ec08
parent 77e900ba
Showing with 113 additions and 14 deletions
+113 -14
......@@ -30,7 +30,7 @@ class EscapeInlineCompletionHandler(val originalHandler: EditorActionHandler) :
}
return
}
InlineCompletion.getHandlerOrNull(editor)?.hide(true, context)
InlineCompletion.getHandlerOrNull(editor)?.hide(true, context, true)
if (originalHandler.isEnabled(editor, caret, dataContext)) {
originalHandler.execute(editor, caret, dataContext)
......@@ -56,3 +56,18 @@ class CallInlineCompletionAction : EditorAction(CallInlineCompletionHandler()),
}
}
}
private class InlineNavigationHandler(val event: InlineCompletionEvent.InlineNavigationEvent) : EditorWriteActionHandler() {
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
editor.getUserData(InlineCompletionHandler.KEY)?.invoke(event)
}
}
@ApiStatus.Experimental
class ShowNextInlineCompletionAction : EditorAction(InlineNavigationHandler(InlineCompletionEvent.ShowNext)),
HintManagerImpl.ActionToIgnore
@ApiStatus.Experimental
class ShowPreviousInlineCompletionAction : EditorAction(InlineNavigationHandler(InlineCompletionEvent.ShowPrevious)),
HintManagerImpl.ActionToIgnore
......@@ -106,6 +106,17 @@ interface InlineCompletionEvent {
return InlineCompletionRequest(this, file, editor, editor.document, offset, offset, event.item)
}
}
@ApiStatus.Experimental
sealed interface InlineNavigationEvent : InlineCompletionEvent {
override fun toRequest(): InlineCompletionRequest? = null
}
@ApiStatus.Experimental
data object ShowNext : InlineNavigationEvent
@ApiStatus.Experimental
data object ShowPrevious : InlineNavigationEvent
}
@RequiresBlockingContext
......
......@@ -16,6 +16,7 @@ import com.intellij.openapi.Disposable
import com.intellij.openapi.application.EDT
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.observable.util.whenDisposed
import com.intellij.openapi.progress.coroutineToIndicator
import com.intellij.openapi.util.Disposer
......@@ -45,11 +46,22 @@ class InlineCompletionHandler internal constructor(
private val eventListeners = EventDispatcher.create(InlineCompletionEventListener::class.java)
private val sessionManager = createSessionManager()
private val requestManager = InlineCompletionRequestManager(sessionManager::invalidate)
private val proposalsManager = InlineProposalsManager()
init {
addEventListener(InlineCompletionUsageTracker.Listener())
}
private fun getProvider(): InlineCompletionProvider? {
if (application.isUnitTestMode && testProvider != null) {
return testProvider
}
return proposalsManager.getProvider()?.also {
LOG.trace("Selected inline provider: $it")
}
}
fun addEventListener(listener: InlineCompletionEventListener) {
eventListeners.addListener(listener)
}
......@@ -67,20 +79,17 @@ class InlineCompletionHandler internal constructor(
fun invoke(event: InlineCompletionEvent.LookupChange) = invokeEvent(event)
fun invoke(event: InlineCompletionEvent.LookupCancelled) = invokeEvent(event)
fun invoke(event: InlineCompletionEvent.DirectCall) = invokeEvent(event)
fun invoke(event: InlineCompletionEvent.InlineNavigationEvent) = invokeEvent(event)
@RequiresEdt
fun invokeEvent(event: InlineCompletionEvent) {
ThreadingAssertions.assertEventDispatchThread()
LOG.trace("Start processing inline event $event")
val request = requestManager.getRequest(event) ?: return
if (editor != request.editor) {
LOG.warn("Request has an inappropriate editor. Another editor was expected. Will not be invoked.")
return
}
val provider = getProvider(event)
if (sessionManager.updateSession(request, provider) || provider == null) {
proposalsManager.processEvent(event)
val provider = getProvider()
val request = requestManager.getRequest(event)
if (request == null || sessionManager.updateSession(request, provider) || provider == null) {
return
}
......@@ -89,6 +98,9 @@ class InlineCompletionHandler internal constructor(
sessionManager.sessionCreated(this)
guardCaretModifications(request)
}
proposalsManager.getCachedProposal()?.let { return newSession.context.renderElement(it, request.endOffset) }
executor.switchJobSafely(newSession::assignJob) {
invokeRequest(request, newSession)
}
......@@ -121,8 +133,11 @@ class InlineCompletionHandler internal constructor(
@RequiresEdt
@RequiresBlockingContext
fun hide(explicit: Boolean, context: InlineCompletionContext) {
fun hide(explicit: Boolean, context: InlineCompletionContext, shouldClearCache: Boolean = false) {
LOG.assertTrue(!context.isDisposed)
if (shouldClearCache) {
proposalsManager.clear()
}
if (context.isCurrentlyDisplaying()) {
trace(InlineCompletionEventType.Hide(explicit))
}
......@@ -135,7 +150,7 @@ class InlineCompletionHandler internal constructor(
executor.cancel()
application.invokeAndWait {
InlineCompletionContext.getOrNull(editor)?.let {
hide(false, it)
hide(false, it, true)
}
}
}
......@@ -144,6 +159,7 @@ class InlineCompletionHandler internal constructor(
@RequiresBlockingContext
fun complete(isActive: Boolean, cause: Throwable?, context: InlineCompletionContext) {
trace(InlineCompletionEventType.Completion(cause, isActive))
proposalsManager.cacheProposal(context.lineToInsert)
if (cause != null && !context.isDisposed) {
hide(false, context)
}
......@@ -263,7 +279,7 @@ class InlineCompletionHandler internal constructor(
if (!context.isDisposed) context.startOffset() ?: request.endOffset else -1
}
val cancel = {
if (!context.isDisposed) hide(false, context)
if (!context.isDisposed) hide(false, context, true)
}
val listener = InlineSessionWiseCaretListener(expectedOffset, cancel)
editor.caretModel.addCaretListener(listener)
......
......@@ -10,13 +10,16 @@ internal data class SimpleTypingEvent(val typed: String, val caretMoves: Boolean
internal class InlineCompletionRequestManager(@RequiresEdt private val invalidate: () -> Unit) {
private var lastSimpleEvent: SimpleTypingEvent? = null
private var lastRequest: InlineCompletionRequest? = null
@RequiresEdt
fun getRequest(event: InlineCompletionEvent): InlineCompletionRequest? {
return when (event) {
lastRequest = when (event) {
is InlineCompletionEvent.DocumentChange -> onDocumentChange(event)
is InlineCompletionEvent.InlineNavigationEvent -> lastRequest
else -> event.toRequest()
}
return lastRequest
}
@RequiresEdt
......
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.inline.completion
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Experimental
internal class InlineProposalsManager : ArrayList<CacheableInlineProposal>() {
private var current: Int = 0
override fun clear() {
super.clear()
current = 0
}
internal fun processEvent(event: InlineCompletionEvent) {
if (event is InlineCompletionEvent.ShowNext) {
if (current + 1 < size) {
++current
}
return
}
if (event is InlineCompletionEvent.ShowPrevious) {
if (current > 0) {
--current
}
return
}
clear()
addAll(InlineCompletionProvider.extensions().filter { it.isEnabled(event) }.map(::CacheableInlineProposal))
}
internal fun getProvider(): InlineCompletionProvider? = getOrNull(current)?.provider
internal fun cacheProposal(proposal: String) {
getOrNull(current)?.apply { cachedProposal = InlineCompletionElement(proposal) }
}
internal fun getCachedProposal(): InlineCompletionElement? = getOrNull(current)?.cachedProposal
}
@ApiStatus.Experimental
internal data class CacheableInlineProposal(val provider: InlineCompletionProvider) {
var cachedProposal: InlineCompletionElement? = null
}
\ No newline at end of file
......@@ -160,7 +160,7 @@ class InlineCompletionAnActionListener : AnActionListener {
private fun hideInlineCompletion(editor: Editor) {
val context = InlineCompletionContext.getOrNull(editor) ?: return
InlineCompletion.getHandlerOrNull(editor)?.hide(false, context)
InlineCompletion.getHandlerOrNull(editor)?.hide(false, context, true)
}
private fun hideInlineCompletion(editor: Editor, handler: InlineCompletionHandler?) {
......
......@@ -866,6 +866,10 @@ group.InlineCompletion.text=Inline Completion
group.InlineCompletion.description=Inline Completion
action.CallInlineCompletionAction.text=Call Inline Completion
action.CallInlineCompletionAction.description=Generate a suggestion for current cursor position and show as inline
action.ShowNextInlineCompletionAction.text=Show Next Inline Completion
action.ShowNextInlineCompletionAction.description=Show next inline completion
action.ShowPreviousInlineCompletionAction.text=Show Previous Inline Completion
action.ShowPreviousInlineCompletionAction.description=Show previous inline completion
action.ResetICOnboardingAtFirstShowAction.text=Reset inline completion onboarding (1)
action.ResetICOnboardingAtFirstShowAction.description=Reset onboarding for inline completion
action.ResetICOnboardingAtFirstAcceptAction.text=Reset inline completion onboarding (2)
......
......@@ -504,6 +504,12 @@
<group id="InlineCompletion" popup="true">
<action id="CallInlineCompletionAction" class="com.intellij.codeInsight.inline.completion.CallInlineCompletionAction"/>
<action id="ShowNextInlineCompletionAction" class="com.intellij.codeInsight.inline.completion.ShowNextInlineCompletionAction">
<keyboard-shortcut first-keystroke="shift alt CLOSE_BRACKET" keymap="$default"/>
</action>
<action id="ShowPreviousInlineCompletionAction" class="com.intellij.codeInsight.inline.completion.ShowPreviousInlineCompletionAction">
<keyboard-shortcut first-keystroke="shift alt OPEN_BRACKET" keymap="$default"/>
</action>
<action id="InsertInlineCompletionAction" class="com.intellij.codeInsight.inline.completion.InsertInlineCompletionAction">
<keyboard-shortcut first-keystroke="TAB" keymap="$default"/>
</action>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment