mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-10-01 06:51:17 +07:00
[inline-completion] IJPL-161906: support 'inline shortcut hint' element
(cherry picked from commit 75e23acede488ac6cc44680699a30c04745da326) IJ-MR-147762 GitOrigin-RevId: d2214712c2fbe180498c4bba13d175dab1dd371d
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3dd22640ca
commit
2ae85bffdc
@@ -14,5 +14,7 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.core.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.editor" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.lang" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core.ui" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -4,4 +4,12 @@
|
||||
description="Use Rhizome DB for inline completion implementation in RemDev"
|
||||
defaultValue="false"/>
|
||||
</extensions>
|
||||
|
||||
<extensionPoints>
|
||||
<extensionPoint
|
||||
qualifiedName="com.intellij.inline.completion.shortcutHintListener"
|
||||
interface="com.intellij.codeInsight.inline.completion.shortcut.InlineCompletionShortcutHintListener"
|
||||
dynamic="true"
|
||||
/>
|
||||
</extensionPoints>
|
||||
</idea-plugin>
|
||||
@@ -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:
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+272
@@ -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<ShortcutRenderer>? = null
|
||||
private var textInlay: Inlay<TextRenderer>? = 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<InlineCompletionChooseShortcutElement>()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() }
|
||||
}
|
||||
}
|
||||
+32
@@ -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<String>, 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+186
@@ -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<InlineCompletionShortcutHintRendererBase>? = null
|
||||
private var suffixInlay: Inlay<InlineCompletionShortcutHintRendererBase>? = 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+47
@@ -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<InlineCompletionShortcutHintListener>(
|
||||
"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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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]"
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
@@ -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 }
|
||||
|
||||
Reference in New Issue
Block a user