mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
LLM-18992 [ai-next-edit] request NES when inline completion is triggered by shortcut
(cherry picked from commit 165ad21d571bb6a479179222fd8cebd85f18a0ee) IJ-CR-183016 GitOrigin-RevId: 61d3bd8d01ed3cbedb7adb4ceb32731d01a73e50
This commit is contained in:
committed by
intellij-monorepo-bot
parent
538c752411
commit
3140d68eb6
@@ -20,7 +20,6 @@ internal class InlineCompletionHandlerImpl(
|
||||
) : InlineCompletionHandler(scope, editor, parentDisposable) {
|
||||
|
||||
init {
|
||||
addEventListener(InlineCompletionNoSuggestionsListener(editor))
|
||||
InlineCompletionOnboardingListener.createIfOnboarding(editor)?.let(::addEventListener)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright 2000-2024 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 com.intellij.codeInsight.hint.HintManager
|
||||
import com.intellij.codeInsight.inline.completion.logs.InlineCompletionUsageTracker
|
||||
import com.intellij.codeInsight.inline.completion.session.InlineCompletionSession
|
||||
import com.intellij.lang.LangBundle
|
||||
import com.intellij.openapi.editor.Editor
|
||||
|
||||
internal class InlineCompletionNoSuggestionsListener(private val editor: Editor) : InlineCompletionEventAdapter {
|
||||
|
||||
override fun onHide(event: InlineCompletionEventType.Hide) {
|
||||
if (event.finishType == InlineCompletionUsageTracker.ShownEvents.FinishType.EMPTY) {
|
||||
val session = InlineCompletionSession.getOrNull(editor)
|
||||
if (session != null && session.request.event is InlineCompletionEvent.DirectCall) {
|
||||
HintManager.getInstance().showInformationHint(editor, LangBundle.message("completion.no.suggestions"), HintManager.ABOVE)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-13
@@ -126,19 +126,6 @@ class BackspaceDeleteInlineCompletionHandler(private val originalHandler: Editor
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
class CallInlineCompletionAction : EditorAction(CallInlineCompletionHandler()), HintManagerImpl.ActionToIgnore {
|
||||
|
||||
class CallInlineCompletionHandler : EditorWriteActionHandler() {
|
||||
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
||||
val curCaret = caret ?: editor.caretModel.currentCaret
|
||||
|
||||
val listener = InlineCompletion.getHandlerOrNull(editor) ?: return
|
||||
listener.invoke(InlineCompletionEvent.DirectCall(editor, curCaret, dataContext))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
@ApiStatus.Internal
|
||||
class InsertInlineCompletionWordAction : EditorAction(Handler()), HintManagerImpl.ActionToIgnore {
|
||||
|
||||
+1
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2024 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 com.intellij.codeInsight.inline.completion.action.CallInlineCompletionAction
|
||||
import com.intellij.codeInsight.inline.completion.session.InlineCompletionContext
|
||||
import com.intellij.openapi.actionSystem.ActionPromoter
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
|
||||
-2
@@ -590,9 +590,7 @@ abstract class InlineCompletionHandler @ApiStatus.Internal constructor(
|
||||
|
||||
// -----------------------------------
|
||||
|
||||
@TestOnly
|
||||
suspend fun awaitExecution() {
|
||||
ThreadingAssertions.assertEventDispatchThread()
|
||||
executor.awaitActiveRequest()
|
||||
}
|
||||
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
// 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.inline.completion.action
|
||||
|
||||
import com.intellij.codeInsight.hint.HintManagerImpl
|
||||
import com.intellij.codeInsight.inline.completion.InlineCompletion
|
||||
import com.intellij.codeInsight.inline.completion.InlineCompletionEvent
|
||||
import com.intellij.codeInsight.inline.edit.NoInlineEditShownNotifier
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.editor.Caret
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.actionSystem.EditorAction
|
||||
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
|
||||
import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
class CallInlineCompletionAction : EditorAction(CallInlineCompletionHandler()), HintManagerImpl.ActionToIgnore {
|
||||
|
||||
class CallInlineCompletionHandler : EditorWriteActionHandler() {
|
||||
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
||||
val curCaret = caret ?: editor.caretModel.currentCaret
|
||||
|
||||
val handler = InlineCompletion.getHandlerOrNull(editor) ?: return
|
||||
handler.invokeEvent(InlineCompletionEvent.DirectCall(editor, curCaret, dataContext))
|
||||
}
|
||||
|
||||
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
|
||||
return InlineCompletion.getHandlerOrNull(editor) != null
|
||||
}
|
||||
}
|
||||
|
||||
internal class ShowNoSuggestionsHintIfNeededHandler(private val originalHandler: EditorActionHandler) : EditorWriteActionHandler() {
|
||||
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
||||
if (originalHandler.isEnabled(editor, caret, dataContext)) {
|
||||
originalHandler.execute(editor, caret, dataContext)
|
||||
}
|
||||
|
||||
if (InlineCompletion.getHandlerOrNull(editor) != null) {
|
||||
val project = dataContext?.getData(CommonDataKeys.PROJECT) ?: editor.project
|
||||
project?.let { project ->
|
||||
NoInlineEditShownNotifier.getInstance(project).notifyNoSuggestionIfNothingIsShown(editor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
|
||||
return InlineCompletion.getHandlerOrNull(editor) != null || originalHandler.isEnabled(editor, caret, dataContext)
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
// 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.inline.edit
|
||||
|
||||
import com.intellij.codeInsight.inline.completion.InlineCompletion
|
||||
import com.intellij.codeInsight.inline.completion.session.InlineCompletionSession
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.project.Project
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
/**
|
||||
* Provides a way to wait for all possible inline edit providers: inline completion and Next Edit.
|
||||
*
|
||||
* The method returns whether a particular inline edit source shows something or no edits were proposed.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
interface InlineEditAwaiter {
|
||||
|
||||
suspend fun awaitInlineEdit(project: Project, editor: Editor): Result
|
||||
|
||||
enum class Result {
|
||||
SuggestionProvided,
|
||||
NothingProvided,
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val EP_NAME = ExtensionPointName.create<InlineEditAwaiter>("com.intellij.inline.edit.awaiter")
|
||||
|
||||
suspend fun awaitAllInlineEdits(project: Project, editor: Editor): Result {
|
||||
val isAnythingProvided = EP_NAME.extensionList.any { it.awaitInlineEdit(project, editor) == Result.SuggestionProvided }
|
||||
return if (isAnythingProvided) Result.SuggestionProvided else Result.NothingProvided
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class InlineCompletionAwaiter : InlineEditAwaiter {
|
||||
override suspend fun awaitInlineEdit(project: Project, editor: Editor): InlineEditAwaiter.Result {
|
||||
InlineCompletion.getHandlerOrNull(editor)?.awaitExecution()
|
||||
return withContext(Dispatchers.EDT) {
|
||||
val isShowing = InlineCompletionSession.getOrNull(editor)?.context?.isCurrentlyDisplaying() == true
|
||||
if (isShowing) InlineEditAwaiter.Result.SuggestionProvided else InlineEditAwaiter.Result.NothingProvided
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-10
@@ -11,8 +11,9 @@ import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
@@ -34,8 +35,6 @@ sealed interface InlineEditRequestExecutor : Disposable {
|
||||
@RequiresEdt
|
||||
fun cancelActiveRequest()
|
||||
|
||||
@TestOnly
|
||||
@RequiresEdt
|
||||
suspend fun awaitActiveRequest()
|
||||
|
||||
companion object {
|
||||
@@ -51,7 +50,7 @@ private class InlineEditRequestExecutorImpl(parentScope: CoroutineScope) : Inlin
|
||||
|
||||
// Timestamps of jobs are required to understand whether we waited for all requests by some moment
|
||||
private val lastRequestedTimestamp = AtomicLong(0)
|
||||
private val lastExecutedTimestamp = AtomicLong(0)
|
||||
private val lastExecutedTimestamp = MutableStateFlow(0L)
|
||||
|
||||
private val nextTask = Channel<Request>(capacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
|
||||
@@ -115,13 +114,9 @@ private class InlineEditRequestExecutorImpl(parentScope: CoroutineScope) : Inlin
|
||||
nextTask.trySend(Request.Cancel(lastRequestedTimestamp.incrementAndGet()))
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
override suspend fun awaitActiveRequest() {
|
||||
ThreadingAssertions.assertEventDispatchThread()
|
||||
val currentTimestamp = lastRequestedTimestamp.get()
|
||||
while (lastExecutedTimestamp.get() < currentTimestamp) {
|
||||
yield()
|
||||
}
|
||||
lastExecutedTimestamp.first { it >= currentTimestamp }
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
@@ -134,7 +129,7 @@ private class InlineEditRequestExecutorImpl(parentScope: CoroutineScope) : Inlin
|
||||
|
||||
private fun setLastExecutedToAtLeast(atLeastTimestamp: Long) {
|
||||
while (true) {
|
||||
val currentTimestamp = lastExecutedTimestamp.get()
|
||||
val currentTimestamp = lastExecutedTimestamp.value
|
||||
if (currentTimestamp >= atLeastTimestamp) {
|
||||
return
|
||||
}
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// 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.inline.edit
|
||||
|
||||
import com.intellij.codeInsight.hint.HintManager
|
||||
import com.intellij.lang.LangBundle
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.application.runReadAction
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.progress.coroutineToIndicator
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Awaits all possible inline edit providers (completion and Next Edit). If nothing is shown after they finish,
|
||||
* the 'No suggestions' hint is shown.
|
||||
*
|
||||
* This service is used only after a user requests inline completion explicitly by shortcut.
|
||||
*/
|
||||
@Service(Service.Level.PROJECT)
|
||||
internal class NoInlineEditShownNotifier(private val project: Project, scope: CoroutineScope) : Disposable {
|
||||
|
||||
private val hintRequestExecutor = InlineEditRequestExecutor.create(scope)
|
||||
|
||||
@RequiresEdt
|
||||
fun notifyNoSuggestionIfNothingIsShown(editor: Editor) {
|
||||
val initialEditorState = runReadAction { editor.getState() }
|
||||
hintRequestExecutor.switchRequest(onJobCreated = {}) {
|
||||
val result = InlineEditAwaiter.awaitAllInlineEdits(project, editor)
|
||||
when (result) {
|
||||
InlineEditAwaiter.Result.SuggestionProvided -> Unit
|
||||
InlineEditAwaiter.Result.NothingProvided -> {
|
||||
withContext(Dispatchers.EDT) {
|
||||
val finalEditorState = runReadAction { editor.getState() }
|
||||
if (initialEditorState == finalEditorState) {
|
||||
coroutineToIndicator {
|
||||
HintManager.getInstance().showInformationHint(editor, LangBundle.message("completion.no.suggestions"), HintManager.ABOVE)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
Disposer.dispose(hintRequestExecutor)
|
||||
}
|
||||
|
||||
private fun Editor.getState(): EditorState {
|
||||
return EditorState(document.modificationStamp, caretModel.offset)
|
||||
}
|
||||
|
||||
private data class EditorState(val modificationStamp: Long, val caretOffset: Int)
|
||||
|
||||
companion object {
|
||||
fun getInstance(project: Project): NoInlineEditShownNotifier = project.service()
|
||||
}
|
||||
}
|
||||
@@ -112,5 +112,9 @@
|
||||
name="inline.completion.editorTypeResolver"
|
||||
interface="com.intellij.codeInsight.inline.completion.editor.InlineCompletionEditorTypeResolver"
|
||||
dynamic="true"/>
|
||||
<extensionPoint
|
||||
name="inline.edit.awaiter"
|
||||
interface="com.intellij.codeInsight.inline.edit.InlineEditAwaiter"
|
||||
dynamic="true"/>
|
||||
</extensionPoints>
|
||||
</idea-plugin>
|
||||
|
||||
@@ -911,6 +911,12 @@
|
||||
/>
|
||||
|
||||
<typedHandler implementation="com.intellij.codeInsight.inline.completion.listeners.typing.InlineCompletionTypedHandlerDelegate"/>
|
||||
<inline.edit.awaiter implementation="com.intellij.codeInsight.inline.edit.InlineCompletionAwaiter"/>
|
||||
<editorActionHandler
|
||||
action="CallInlineCompletionAction"
|
||||
id="inline.edit.no.suggestions.hint"
|
||||
order="last"
|
||||
implementationClass="com.intellij.codeInsight.inline.completion.action.CallInlineCompletionAction$ShowNoSuggestionsHintIfNeededHandler"/>
|
||||
|
||||
<registryKey key="editor.action.caretMovement.UpDownIgnoreSelectionBoundaries" defaultValue="false"
|
||||
description="Allows to restore the legacy behavior of Up/Down w.r.t. selection boundaries.\n\
|
||||
|
||||
@@ -302,7 +302,7 @@
|
||||
<action id="Floating.CodeToolbar.Disable" class="com.intellij.ui.codeFloatingToolbar.DisableCodeFloatingToolbarAction" />
|
||||
|
||||
<group id="InlineCompletion" popup="true">
|
||||
<action id="CallInlineCompletionAction" class="com.intellij.codeInsight.inline.completion.CallInlineCompletionAction">
|
||||
<action id="CallInlineCompletionAction" class="com.intellij.codeInsight.inline.completion.action.CallInlineCompletionAction">
|
||||
<keyboard-shortcut first-keystroke="shift alt BACK_SLASH" keymap="$default"/>
|
||||
</action>
|
||||
<action id="InsertInlineCompletionAction" class="com.intellij.codeInsight.inline.completion.InsertInlineCompletionAction">
|
||||
|
||||
Reference in New Issue
Block a user