diff --git a/platform/inline-completion/shared/intellij.platform.inline.completion.iml b/platform/inline-completion/shared/intellij.platform.inline.completion.iml index 807a701820d8..35f4c084abed 100644 --- a/platform/inline-completion/shared/intellij.platform.inline.completion.iml +++ b/platform/inline-completion/shared/intellij.platform.inline.completion.iml @@ -14,5 +14,7 @@ + + \ No newline at end of file diff --git a/platform/inline-completion/shared/resources/intellij.platform.inline.completion.xml b/platform/inline-completion/shared/resources/intellij.platform.inline.completion.xml index fbbfdd5df949..a424cd4af7b9 100644 --- a/platform/inline-completion/shared/resources/intellij.platform.inline.completion.xml +++ b/platform/inline-completion/shared/resources/intellij.platform.inline.completion.xml @@ -4,4 +4,12 @@ description="Use Rhizome DB for inline completion implementation in RemDev" defaultValue="false"/> + + + + \ No newline at end of file diff --git a/platform/inline-completion/shared/resources/messages/InlineCompletionBundle.properties b/platform/inline-completion/shared/resources/messages/InlineCompletionBundle.properties new file mode 100644 index 000000000000..c3ca983f4706 --- /dev/null +++ b/platform/inline-completion/shared/resources/messages/InlineCompletionBundle.properties @@ -0,0 +1,4 @@ +inline.completion.shortcut.hint.insert.text=to complete +inline.completion.shortcut.hint.insert.word.text=to complete word +inline.completion.shortcut.hint.insert.line.text=to complete line +inline.completion.shortcut.choose.shortcut.hint.text=Choose key for completion: \ No newline at end of file diff --git a/platform/inline-completion/shared/src/MessageBundle.kt b/platform/inline-completion/shared/src/MessageBundle.kt new file mode 100644 index 000000000000..71db389b4764 --- /dev/null +++ b/platform/inline-completion/shared/src/MessageBundle.kt @@ -0,0 +1,17 @@ +// 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.DynamicBundle +import org.jetbrains.annotations.Nls +import org.jetbrains.annotations.NotNull +import org.jetbrains.annotations.PropertyKey + +internal object MessageBundle { + private const val BUNDLE = "messages.InlineCompletionBundle" + private val INSTANCE = DynamicBundle(MessageBundle::class.java, BUNDLE) + + @JvmStatic + fun message(@NotNull @PropertyKey(resourceBundle = BUNDLE) key: String, @NotNull vararg params: Any): @Nls String { + return INSTANCE.getMessage(key, *params) + } +} \ No newline at end of file diff --git a/platform/inline-completion/shared/src/shortcut/InlineCompletionChooseShortcutElement.kt b/platform/inline-completion/shared/src/shortcut/InlineCompletionChooseShortcutElement.kt new file mode 100644 index 000000000000..43b53c44cec4 --- /dev/null +++ b/platform/inline-completion/shared/src/shortcut/InlineCompletionChooseShortcutElement.kt @@ -0,0 +1,272 @@ +// 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.shortcut + +import com.intellij.codeInsight.hints.presentation.InputHandler +import com.intellij.codeInsight.inline.completion.MessageBundle +import com.intellij.codeInsight.inline.completion.elements.InlineCompletionElement +import com.intellij.codeInsight.inline.completion.tooltip.ChangeToCustomInlineCompletionAction +import com.intellij.codeInsight.inline.completion.tooltip.InplaceChangeInlineCompletionShortcutAction +import com.intellij.codeInsight.inline.hint.InlineShortcutHintRendererBase +import com.intellij.ide.IdeBundle +import com.intellij.openapi.actionSystem.* +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.Inlay +import com.intellij.openapi.editor.impl.EditorImpl +import com.intellij.openapi.editor.markup.TextAttributes +import com.intellij.openapi.observable.util.whenDisposed +import com.intellij.openapi.ui.popup.JBPopupFactory +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.NlsActions +import com.intellij.ui.awt.RelativePoint +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.UIUtil +import org.jetbrains.annotations.ApiStatus +import java.awt.Cursor +import java.awt.Graphics +import java.awt.Point +import java.awt.Rectangle +import java.awt.event.MouseEvent + +@ApiStatus.Internal +class InlineCompletionChooseShortcutElement(val lineNumber: Int) : InlineCompletionElement { + override val text: String = "" + + init { + InlineCompletionShortcutHintListener.choiceShown() + } + + override fun toPresentable(): InlineCompletionElement.Presentable { + return Presentable(this) + } + + class Presentable(override val element: InlineCompletionChooseShortcutElement) : InlineCompletionElement.Presentable { + private var shortcutInlay: Inlay? = null + private var textInlay: Inlay? = null + private var offset: Int? = null + + override fun isVisible(): Boolean { + return getBounds() != null + } + + override fun getBounds(): Rectangle? { + if (!choiceShouldRender()) { + return null + } + val shortcutBounds = shortcutInlay?.bounds ?: return null + val suffixBounds = textInlay?.bounds ?: return null + return shortcutBounds.union(suffixBounds) + } + + override fun startOffset(): Int? = offset + + override fun endOffset(): Int? = offset + + override fun dispose() { + shortcutInlay?.let(Disposer::dispose) + textInlay?.let(Disposer::dispose) + shortcutInlay = null + textInlay = null + offset = null + } + + override fun render(editor: Editor, offset: Int) { + InlineCompletionShortcutChangeListener.whenInsertShortcutChanged(disposable = this) { + shortcutInlay?.update() + textInlay?.update() + } + + if (editor !is EditorImpl || !InlineShortcutHintRendererBase.isAvailableForLine(editor, element.lineNumber)) { + return + } + try { + val caretOffset = editor.caretModel.offset + val hintState = HintState(false) + val textBefore = MessageBundle.message("inline.completion.shortcut.choose.shortcut.hint.text") + val textRenderer = TextRenderer(textBefore, hintState, element.lineNumber) + val shortcutRenderer = ShortcutRenderer("Tab " + '\u25BE', hintState, element.lineNumber) + textInlay = editor.inlayModel.addAfterLineEndElement(caretOffset, true, textRenderer).apply { + renderer.initialize(this) + } + shortcutInlay = editor.inlayModel.addAfterLineEndElement(caretOffset, true, shortcutRenderer).apply { + renderer.initialize(this) + } + this.offset = offset + } + catch (e: Exception) { + LOG.error("Could not render ML completion in-editor choice hint.", e) + } + } + + companion object { + private val LOG = logger() + } + } +} + +private class HintState(var isHovered: Boolean) { + private val updates = mutableListOf<() -> Unit>() + + fun whenUpdated(block: () -> Unit) { + updates += block + } + + fun update() { + updates.forEach { it() } + } +} + +private abstract class RendererBase( + text: String?, + val hintState: HintState, + private val lineNumber: Int +) : InlineCompletionShortcutHintRendererBase(text), InputHandler { + private lateinit var myInlay: Inlay<*> + + override fun getTextAttributes(editor: Editor): TextAttributes? { + return super.getTextAttributes(editor)?.updateIfHovering() + } + + override fun calcWidthInPixels(inlay: Inlay<*>): Int { + return if (!choiceShouldRender()) 1 else super.calcWidthInPixels(inlay) + } + + fun initialize(inlay: Inlay<*>) { + check(!::myInlay.isInitialized) + myInlay = inlay + inlay.whenDisposed { + updateState(false) + } + hintState.whenUpdated { + inlay.update() + } + } + + fun TextAttributes.updateIfHovering(): TextAttributes { + return if (hintState.isHovered) { + clone().apply { + foregroundColor = JBUI.CurrentTheme.Link.Foreground.ENABLED + } + } + else this + } + + override fun isEnabledAdditional(editor: Editor): Boolean = choiceShouldRender() + + override fun mousePressed(event: MouseEvent, translated: Point) { + if (choiceShouldRender()) { + event.consume() + } + } + + override fun mouseReleased(event: MouseEvent, translated: Point) { + if (choiceShouldRender()) { + showChooseShortcutPopup(myInlay.editor, guessPopupPoint(event), lineNumber) + } + } + + override fun mouseMoved(event: MouseEvent, translated: Point) { + if (choiceShouldRender()) { + updateState(true) + } + } + + override fun mouseExited() { + if (choiceShouldRender()) { + updateState(false) + } + } + + private fun updateState(isHovered: Boolean) { + hintState.isHovered = isHovered + if (myInlay.isValid) { + updateCursor(isHovered) + hintState.update() + } + } + + private fun updateCursor(isHovered: Boolean) { + val cursor = when (isHovered) { + true -> Cursor.getPredefinedCursor(Cursor.HAND_CURSOR) + false -> Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR) + } + val contentComponent = myInlay.editor.contentComponent + if (contentComponent.cursor != cursor) { + UIUtil.setCursor(contentComponent, cursor) + } + } + + private fun guessPopupPoint(event: MouseEvent): RelativePoint { + val shortcutInlay = myInlay.editor.inlayModel.getAfterLineEndElementsForLogicalLine(lineNumber).firstOrNull { + it.renderer is ShortcutRenderer + } + val point = shortcutInlay?.bounds?.let { Point(it.x, it.y) } ?: event.point + return RelativePoint(event.component, Point(point.x, point.y + myInlay.editor.lineHeight)) + } +} + +private class ShortcutRenderer( + text: String?, + hoveringState: HintState, + lineNumber: Int +) : RendererBase(text, hoveringState, lineNumber), InputHandler { + + override fun paintIfEnabled(inlay: Inlay<*>, g: Graphics, r: Rectangle, textAttributes: TextAttributes) { + paintHint(inlay, g, r, textAttributes.updateIfHovering().clearEffects()) + } +} + +private class TextRenderer( + text: String?, + hoveringState: HintState, + lineNumber: Int +) : RendererBase(text, hoveringState, lineNumber), InputHandler { + + override fun paintIfEnabled(inlay: Inlay<*>, g: Graphics, r: Rectangle, textAttributes: TextAttributes) { + paintLabel(g, inlay.editor as EditorImpl, r, text, textAttributes) { it.updateIfHovering() } + } +} + +private fun choiceShouldRender(): Boolean { + return InlineCompletionShortcutHintState.getState() == InlineCompletionShortcutHintState.SHOW_CHOICE +} + +private fun showChooseShortcutPopup(editor: Editor, relativePoint: RelativePoint, lineNumber: Int) { + val predefinedShortcuts = listOf( + "Tab" to KeyboardShortcut.fromString("TAB"), + "→" to KeyboardShortcut.fromString("RIGHT"), + "Enter" to KeyboardShortcut.fromString("ENTER"), + "Shift →" to KeyboardShortcut.fromString("shift pressed RIGHT") + ).map { (name, shortcut) -> + ChangeShortcutAction(name, shortcut, editor, lineNumber) + } + val defaultGroup = DefaultActionGroup().apply { + addAll(predefinedShortcuts) + add(ChangeToCustomInlineCompletionAction()) + } + val popup = JBPopupFactory.getInstance().createActionGroupPopup( + IdeBundle.message("inline.completion.tooltip.shortcuts.header"), + defaultGroup, + DataContext.EMPTY_CONTEXT, + true, + null, + 10 + ) + popup.show(relativePoint) +} + +private class ChangeShortcutAction( + @NlsActions.ActionText text: String, + shortcut: Shortcut, + private val editor: Editor, + private val lineNumber: Int +) : InplaceChangeInlineCompletionShortcutAction(text, shortcut) { + + override fun actionPerformed(e: AnActionEvent) { + super.actionPerformed(e) + InlineCompletionShortcutHintListener.choiceMade() + val inlaysAfterLine = editor.inlayModel.getAfterLineEndElementsForLogicalLine(lineNumber) + val relatedInlays = inlaysAfterLine.filter { it.renderer is InlineCompletionShortcutHintRendererBase } + relatedInlays.forEach { it.update() } + } +} diff --git a/platform/inline-completion/shared/src/shortcut/InlineCompletionShortcutChangeListener.kt b/platform/inline-completion/shared/src/shortcut/InlineCompletionShortcutChangeListener.kt new file mode 100644 index 000000000000..651095fa1e6e --- /dev/null +++ b/platform/inline-completion/shared/src/shortcut/InlineCompletionShortcutChangeListener.kt @@ -0,0 +1,32 @@ +// 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.shortcut + +import com.intellij.openapi.Disposable +import com.intellij.openapi.actionSystem.IdeActions +import com.intellij.openapi.keymap.Keymap +import com.intellij.openapi.keymap.KeymapManagerListener +import com.intellij.util.application +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.NonNls + +@ApiStatus.Internal +abstract class InlineCompletionShortcutChangeListener : KeymapManagerListener { + + protected abstract fun insertShortcutChanged() + + final override fun shortcutsChanged(keymap: Keymap, actionIds: @NonNls MutableCollection, fromSettings: Boolean) { + val expectedActionId = IdeActions.ACTION_INSERT_INLINE_COMPLETION + if (actionIds.contains(expectedActionId) && keymap.getShortcuts(expectedActionId).isNotEmpty()) { + insertShortcutChanged() + } + } + + companion object { + fun whenInsertShortcutChanged(disposable: Disposable, handler: () -> Unit) { + val listener = object : InlineCompletionShortcutChangeListener() { + override fun insertShortcutChanged() = handler() + } + application.messageBus.connect(disposable).subscribe(KeymapManagerListener.TOPIC, listener) + } + } +} diff --git a/platform/inline-completion/shared/src/shortcut/InlineCompletionShortcutHintElement.kt b/platform/inline-completion/shared/src/shortcut/InlineCompletionShortcutHintElement.kt new file mode 100644 index 000000000000..2dec7e64af0a --- /dev/null +++ b/platform/inline-completion/shared/src/shortcut/InlineCompletionShortcutHintElement.kt @@ -0,0 +1,186 @@ +// 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.shortcut + +import com.intellij.codeInsight.inline.completion.MessageBundle +import com.intellij.codeInsight.inline.completion.elements.InlineCompletionElement +import com.intellij.codeInsight.inline.completion.shortcut.InlineCompletionShortcutHint.entries +import com.intellij.codeInsight.inline.hint.InlineShortcutHintRendererBase +import com.intellij.openapi.actionSystem.IdeActions +import com.intellij.openapi.actionSystem.Shortcut +import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.Inlay +import com.intellij.openapi.editor.impl.EditorImpl +import com.intellij.openapi.editor.markup.TextAttributes +import com.intellij.openapi.keymap.KeymapUtil +import com.intellij.openapi.util.Disposer +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.Nls +import java.awt.Graphics +import java.awt.Rectangle +import kotlin.random.Random + +@ApiStatus.Internal +class InlineCompletionShortcutHintElement(val lineNumber: Int, val isMultiline: Boolean) : InlineCompletionElement { + override val text: String = "" + + private val hint = InlineCompletionShortcutHint.random(isMultiline) + + override fun toPresentable(): InlineCompletionElement.Presentable { + return Presentable(this, hint) + } + + class Presentable( + override val element: InlineCompletionShortcutHintElement, + private val hint: InlineCompletionShortcutHint + ) : InlineCompletionElement.Presentable { + private var shortcutInlay: Inlay? = null + private var suffixInlay: Inlay? = null + private var offset: Int? = null + private var currentShortcut = getShortcutRepresentation() + + init { + InlineCompletionShortcutChangeListener.whenInsertShortcutChanged(disposable = this) { + rerender() + } + } + + override fun isVisible(): Boolean { + return getBounds() != null + } + + override fun getBounds(): Rectangle? { + if (!hintShouldRender()) { + return null + } + val shortcutBounds = shortcutInlay?.bounds ?: return null + val suffixBounds = suffixInlay?.bounds ?: return null + return shortcutBounds.union(suffixBounds) + } + + override fun startOffset(): Int? = offset + + override fun endOffset(): Int? = offset + + override fun dispose() { + shortcutInlay?.let(Disposer::dispose) + suffixInlay?.let(Disposer::dispose) + shortcutInlay = null + suffixInlay = null + offset = null + } + + override fun render(editor: Editor, offset: Int) { + if (editor !is EditorImpl || currentShortcut == null || !InlineShortcutHintRendererBase.isAvailableForLine(editor, element.lineNumber)) { + return + } + try { + val caretOffset = editor.caretModel.offset + shortcutInlay = editor.inlayModel.addAfterLineEndElement(caretOffset, true, getShortcutRenderer()) + suffixInlay = editor.inlayModel.addAfterLineEndElement(caretOffset, true, getSuffixRenderer(editor)) + this.offset = offset + } + catch (e: Exception) { + LOG.error("Could not render Full Line in-editor hint.", e) + } + } + + private fun getSuffixRenderer(editor: EditorImpl): InlineCompletionShortcutHintRendererBase { + return object : InlineCompletionShortcutHintRendererBase(hint.suffixText) { + + // We need to check it here to be able to re-draw if a user changes the setting during a session + override fun isEnabledAdditional(editor: Editor): Boolean = hintShouldRender() + + override fun paintIfEnabled(inlay: Inlay<*>, g: Graphics, r: Rectangle, textAttributes: TextAttributes) { + paintLabel(g, editor, r, text, textAttributes.clearEffects()) + } + } + } + + private fun getShortcutRenderer(): InlineCompletionShortcutHintRendererBase { + return object : InlineCompletionShortcutHintRendererBase(currentShortcut) { + + // We need to check it here to be able to re-draw if a user changes the setting during a session + override fun isEnabledAdditional(editor: Editor): Boolean = hintShouldRender() + + override fun paintIfEnabled(inlay: Inlay<*>, g: Graphics, r: Rectangle, textAttributes: TextAttributes) { + paintHint(inlay, g, r, textAttributes.clearEffects()) + } + } + } + + private fun rerender() { + currentShortcut = getShortcutRepresentation() + shortcutInlay?.renderer?.text = currentShortcut + shortcutInlay?.update() + suffixInlay?.update() + } + + private fun getShortcutRepresentation(): String? { + val shortcut = KeymapUtil.getPrimaryShortcut(hint.actionId) + return shortcut?.asString() + } + + private fun Shortcut.asString(): String { + return when (toString()) { + "[pressed TAB]" -> "Tab" + "[pressed ENTER]" -> "Enter" + "[shift pressed RIGHT]" -> "Shift →" + else -> KeymapUtil.getShortcutText(this) + } + } + + private fun hintShouldRender(): Boolean { + return currentShortcut != null && InlineCompletionShortcutHintState.getState() == InlineCompletionShortcutHintState.SHOW_HINT + } + + companion object { + private val LOG = thisLogger() + } + } +} + +@ApiStatus.Internal +enum class InlineCompletionShortcutHint { + INSERT { + override val actionId: String + get() = IdeActions.ACTION_INSERT_INLINE_COMPLETION + override val suffixText: String + get() = MessageBundle.message("inline.completion.shortcut.hint.insert.text") + override val priority: Int + get() = 4 + }, + INSERT_WORD { + override val actionId: String + get() = IdeActions.ACTION_INSERT_INLINE_COMPLETION_WORD + override val suffixText: String + get() = MessageBundle.message("inline.completion.shortcut.hint.insert.word.text") + override val priority: Int + get() = 1 + }, + INSERT_LINE { + override val actionId: String + get() = IdeActions.ACTION_INSERT_INLINE_COMPLETION_LINE + override val suffixText: String + get() = MessageBundle.message("inline.completion.shortcut.hint.insert.line.text") + override val priority: Int + get() = 3 + }; + + abstract val actionId: String + + abstract val suffixText: @Nls String + + protected abstract val priority: Int + + companion object { + fun random(isMultiline: Boolean): InlineCompletionShortcutHint { + val entries = if (isMultiline) entries else entries.filter { it !== INSERT_LINE } + var randomPoint = Random.nextInt(entries.sumOf { it.priority }) + 1 + return entries.firstOrNull { + randomPoint -= it.priority + randomPoint <= 0 + } ?: entries.last() + } + } +} diff --git a/platform/inline-completion/shared/src/shortcut/InlineCompletionShortcutHintListener.kt b/platform/inline-completion/shared/src/shortcut/InlineCompletionShortcutHintListener.kt new file mode 100644 index 000000000000..868bbf4793ac --- /dev/null +++ b/platform/inline-completion/shared/src/shortcut/InlineCompletionShortcutHintListener.kt @@ -0,0 +1,47 @@ +// 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.shortcut + +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.extensions.ExtensionPointName +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Internal +@ApiStatus.NonExtendable +interface InlineCompletionShortcutHintListener { + + fun choiceShown() + + fun choiceMade() + + fun shouldShowChoice(): Boolean + + fun shouldHideHint(editor: Editor): Boolean + + fun isHintDisabled(): Boolean + + companion object { + private val EP_NAME = ExtensionPointName.create( + "com.intellij.inline.completion.shortcutHintListener" + ) + + fun choiceShown() { + EP_NAME.forEachExtensionSafe { it.choiceShown() } + } + + fun choiceMade() { + EP_NAME.forEachExtensionSafe { it.choiceMade() } + } + + fun shouldShowChoice(): Boolean { + return EP_NAME.extensionList.any { it.shouldShowChoice() } + } + + fun shouldHideHint(editor: Editor): Boolean { + return EP_NAME.extensionList.any { it.shouldHideHint(editor) } + } + + fun isHintDisabled(): Boolean { + return EP_NAME.extensionList.any { it.isHintDisabled() } + } + } +} diff --git a/platform/inline-completion/shared/src/shortcut/InlineCompletionShortcutHintRendererBase.kt b/platform/inline-completion/shared/src/shortcut/InlineCompletionShortcutHintRendererBase.kt new file mode 100644 index 000000000000..dc2e6e8fa307 --- /dev/null +++ b/platform/inline-completion/shared/src/shortcut/InlineCompletionShortcutHintRendererBase.kt @@ -0,0 +1,16 @@ +// 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.shortcut + +import com.intellij.codeInsight.inline.hint.InlineShortcutHintRendererBase +import com.intellij.openapi.editor.Editor +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Internal +abstract class InlineCompletionShortcutHintRendererBase(text: String?) : InlineShortcutHintRendererBase(text) { + + protected abstract fun isEnabledAdditional(editor: Editor): Boolean + + final override fun isEnabled(editor: Editor): Boolean { + return isEnabledAdditional(editor) && !InlineCompletionShortcutHintListener.shouldHideHint(editor) + } +} diff --git a/platform/inline-completion/shared/src/shortcut/InlineCompletionShortcutHintState.kt b/platform/inline-completion/shared/src/shortcut/InlineCompletionShortcutHintState.kt new file mode 100644 index 000000000000..51d28f508e69 --- /dev/null +++ b/platform/inline-completion/shared/src/shortcut/InlineCompletionShortcutHintState.kt @@ -0,0 +1,33 @@ +// 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.shortcut + +import com.intellij.openapi.actionSystem.IdeActions +import com.intellij.openapi.actionSystem.Shortcut +import com.intellij.openapi.keymap.KeymapUtil +import com.intellij.util.application +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Internal +enum class InlineCompletionShortcutHintState { + SHOW_HINT, + SHOW_CHOICE, + SHOW_NOTHING; + + companion object { + + fun getState(): InlineCompletionShortcutHintState { + val primaryShortcut = KeymapUtil.getPrimaryShortcut(IdeActions.ACTION_INSERT_INLINE_COMPLETION) + if (primaryShortcut?.isTab() == false) { + InlineCompletionShortcutHintListener.choiceMade() + } + if (primaryShortcut == null || InlineCompletionShortcutHintListener.isHintDisabled()) { + return SHOW_NOTHING + } + return if (InlineCompletionShortcutHintListener.shouldShowChoice() && !application.isUnitTestMode) SHOW_CHOICE else SHOW_HINT + } + + private fun Shortcut.isTab(): Boolean { + return toString().lowercase() == "[pressed tab]" + } + } +} diff --git a/platform/platform-impl/src/com/intellij/codeInsight/inline/hint/InlineShortcutHintRendererBase.kt b/platform/platform-impl/src/com/intellij/codeInsight/inline/hint/InlineShortcutHintRendererBase.kt index 578311035fe7..91d334f7d657 100644 --- a/platform/platform-impl/src/com/intellij/codeInsight/inline/hint/InlineShortcutHintRendererBase.kt +++ b/platform/platform-impl/src/com/intellij/codeInsight/inline/hint/InlineShortcutHintRendererBase.kt @@ -3,6 +3,9 @@ package com.intellij.codeInsight.inline.hint import com.intellij.codeInsight.daemon.impl.HintRenderer import com.intellij.codeInsight.inline.completion.InlineCompletionFontUtils +import com.intellij.codeWithMe.ClientId +import com.intellij.codeWithMe.ClientId.Companion.isLocal +import com.intellij.idea.AppMode import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Inlay import com.intellij.openapi.editor.impl.EditorImpl @@ -72,6 +75,9 @@ abstract class InlineShortcutHintRendererBase(text: String?) : HintRenderer(text companion object { @JvmStatic fun isAvailableForLine(editor: Editor, lineNumber: Int): Boolean { + if (!ClientId.currentOrNull.isLocal || AppMode.isRemoteDevHost()) { + return false + } // Debugger may suggest something on the right, so inlays will mix up val inlaysAfterLine = editor.inlayModel.getAfterLineEndElementsForLogicalLine(lineNumber) return inlaysAfterLine.all { it.renderer is InlineShortcutHintRendererBase }