IJPL-164043 promote suggested refactoring using editor line hint (disabled by default, use "promote.suggested.refactoring.in.editor" adv. setting)

GitOrigin-RevId: d067a744131450a2bb0945d1c847992ebb7da6d9
This commit is contained in:
Dmitry Batkovich
2024-10-13 13:56:28 +02:00
committed by intellij-monorepo-bot
parent 82fd7ae8d7
commit 36d0b02a0d
18 changed files with 427 additions and 99 deletions

View File

@@ -10,6 +10,8 @@ import com.intellij.util.asSafely
import com.intellij.util.keyFMap.KeyFMap
import org.jetbrains.annotations.Nls
import com.intellij.psi.createSmartPointer
import com.intellij.refactoring.RefactoringBundle
import org.jetbrains.annotations.ApiStatus
private var nextFeatureUsageId = 0
@@ -259,3 +261,19 @@ data class SuggestedChangeSignatureData private constructor(
}
}
}
@ApiStatus.Internal
fun SuggestedRefactoringData.getIntentionText(): @Nls String {
val text = when (this) {
is SuggestedRenameData -> RefactoringBundle.message(
"suggested.refactoring.rename.intention.text",
oldName,
newName
)
is SuggestedChangeSignatureData -> RefactoringBundle.message(
"suggested.refactoring.change.signature.intention.text",
nameOfStuffToUpdate
)
}
return text
}

View File

@@ -5,6 +5,7 @@ import com.intellij.openapi.ui.ValidationInfo
import com.intellij.psi.PsiCodeFragment
import com.intellij.refactoring.suggested.SuggestedRefactoringExecution.NewParameterValue
import com.intellij.refactoring.suggested.SuggestedRefactoringSupport.Signature
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import javax.swing.JComponent

View File

@@ -17892,74 +17892,6 @@ c:com.intellij.refactoring.safeDelete.usageInfo.SafeDeleteUsageInfo
- <init>(com.intellij.psi.PsiElement,com.intellij.psi.PsiElement):V
- <init>(com.intellij.psi.PsiElement,com.intellij.psi.PsiElement,I,I,Z):V
- getReferencedElement():com.intellij.psi.PsiElement
f:com.intellij.refactoring.suggested.NewIdentifierWatcher
- <init>(I):V
- f:documentChanged(com.intellij.openapi.editor.event.DocumentEvent,com.intellij.lang.Language):V
- f:getLastDocument():com.intellij.openapi.editor.Document
- f:lastNewIdentifierRanges():java.util.List
- f:reset():V
f:com.intellij.refactoring.suggested.RefactoringAvailableGutterIconRenderer
- com.intellij.openapi.editor.markup.GutterIconRenderer
- <init>(java.lang.String):V
- equals(java.lang.Object):Z
- getAlignment():com.intellij.openapi.editor.markup.GutterIconRenderer$Alignment
- getClickAction():com.intellij.openapi.actionSystem.AnAction
- getIcon():javax.swing.Icon
- getTooltipText():java.lang.String
- hashCode():I
- isNavigateAction():Z
f:com.intellij.refactoring.suggested.RefactoringDisabledGutterIconRenderer
- com.intellij.openapi.editor.markup.GutterIconRenderer
- <init>(java.lang.String):V
- equals(java.lang.Object):Z
- getAlignment():com.intellij.openapi.editor.markup.GutterIconRenderer$Alignment
- getIcon():javax.swing.Icon
- getTooltipText():java.lang.String
- hashCode():I
f:com.intellij.refactoring.suggested.SuggestedRefactoringAvailabilityIndicator
- sf:Companion:com.intellij.refactoring.suggested.SuggestedRefactoringAvailabilityIndicator$Companion
- <init>(com.intellij.openapi.project.Project):V
- f:clear():V
- f:disable():V
- f:getHasData():Z
- f:show(com.intellij.openapi.editor.Document,com.intellij.openapi.util.TextRange,com.intellij.openapi.util.TextRange,Z,java.lang.String):V
f:com.intellij.refactoring.suggested.SuggestedRefactoringAvailabilityIndicator$Companion
- f:getDisabledRefactoringTooltip():java.lang.String
f:com.intellij.refactoring.suggested.SuggestedRefactoringChangeCollector
- com.intellij.refactoring.suggested.SuggestedRefactoringSignatureWatcher
- <init>(com.intellij.refactoring.suggested.SuggestedRefactoringAvailabilityIndicator):V
- editingStarted(com.intellij.psi.PsiElement,com.intellij.refactoring.suggested.SuggestedRefactoringSupport):V
- f:getState():com.intellij.refactoring.suggested.SuggestedRefactoringState
- f:get_amendStateInBackgroundEnabled():Z
- inconsistentState():V
- nextSignature(com.intellij.psi.PsiElement,com.intellij.refactoring.suggested.SuggestedRefactoringSupport):V
- reset():V
- f:set_amendStateInBackgroundEnabled(Z):V
- f:undoToState(com.intellij.refactoring.suggested.SuggestedRefactoringState):V
f:com.intellij.refactoring.suggested.SuggestedRefactoringChangeListener
- <init>(com.intellij.openapi.project.Project,com.intellij.refactoring.suggested.SuggestedRefactoringSignatureWatcher,com.intellij.openapi.Disposable):V
- f:reset(Z):V
- bs:reset$default(com.intellij.refactoring.suggested.SuggestedRefactoringChangeListener,Z,I,java.lang.Object):V
- f:suppressForCurrentDeclaration():V
- f:undoToState(com.intellij.refactoring.suggested.SuggestedRefactoringState,com.intellij.openapi.util.TextRange):V
f:com.intellij.refactoring.suggested.SuggestedRefactoringProviderImpl
- com.intellij.refactoring.suggested.SuggestedRefactoringProvider
- sf:Companion:com.intellij.refactoring.suggested.SuggestedRefactoringProviderImpl$Companion
- <init>(com.intellij.openapi.project.Project):V
- f:getAvailabilityIndicator():com.intellij.refactoring.suggested.SuggestedRefactoringAvailabilityIndicator
- f:getState():com.intellij.refactoring.suggested.SuggestedRefactoringState
- f:get_amendStateInBackgroundEnabled():Z
- reset():V
- f:set_amendStateInBackgroundEnabled(Z):V
- f:suppressForCurrentDeclaration():V
- f:undoToState(com.intellij.refactoring.suggested.SuggestedRefactoringState,com.intellij.openapi.util.TextRange):V
f:com.intellij.refactoring.suggested.SuggestedRefactoringProviderImpl$Companion
- f:getInstance(com.intellij.openapi.project.Project):com.intellij.refactoring.suggested.SuggestedRefactoringProviderImpl
com.intellij.refactoring.suggested.SuggestedRefactoringSignatureWatcher
- a:editingStarted(com.intellij.psi.PsiElement,com.intellij.refactoring.suggested.SuggestedRefactoringSupport):V
- a:inconsistentState():V
- a:nextSignature(com.intellij.psi.PsiElement,com.intellij.refactoring.suggested.SuggestedRefactoringSupport):V
- a:reset():V
a:com.intellij.refactoring.ui.AbstractMemberSelectionPanel
- javax.swing.JPanel
- <init>():V

View File

@@ -2,6 +2,9 @@
package com.intellij.refactoring.suggested
import com.intellij.codeInsight.daemon.GutterMark
import com.intellij.codeInsight.editorLineStripeHint.EditorLineStripeButtonRenderer
import com.intellij.codeInsight.editorLineStripeHint.EditorLineStripeHintComponent
import com.intellij.codeInsight.editorLineStripeHint.EditorLineStripeTextRenderer
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.application.ApplicationManager
@@ -14,12 +17,15 @@ import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.markup.*
import com.intellij.openapi.keymap.KeymapUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.RefactoringCodeVisionSupport
import com.intellij.ui.JBColor
import com.intellij.util.concurrency.ThreadingAssertions
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
@@ -32,7 +38,8 @@ class SuggestedRefactoringAvailabilityIndicator(private val project: Project) {
val highlighterRangeMarker: RangeMarker,
val availabilityRangeMarker: RangeMarker,
val refactoringEnabled: Boolean,
@NlsContexts.Tooltip val tooltip: String
@NlsContexts.Tooltip val tooltip: String,
@Nls val intentionText: String?,
) {
override fun equals(other: Any?): Boolean {
return other is Data
@@ -41,6 +48,7 @@ class SuggestedRefactoringAvailabilityIndicator(private val project: Project) {
&& other.availabilityRangeMarker.asTextRange == availabilityRangeMarker.asTextRange
&& other.refactoringEnabled == refactoringEnabled
&& other.tooltip == tooltip
&& other.intentionText == intentionText
}
override fun hashCode() = tooltip.hashCode()
@@ -48,7 +56,7 @@ class SuggestedRefactoringAvailabilityIndicator(private val project: Project) {
private var data: Data? = null
private val editorsAndHighlighters = mutableMapOf<Editor, RangeHighlighter?>()
private val editorsAndHighlighters = mutableMapOf<Editor, Pair<RangeHighlighter, EditorLineStripeHintComponent?>>()
private val caretListener = object : CaretListener {
override fun caretPositionChanged(event: CaretEvent) {
@@ -73,7 +81,8 @@ class SuggestedRefactoringAvailabilityIndicator(private val project: Project) {
markerRange: TextRange,
availabilityRange: TextRange,
refactoringEnabled: Boolean,
@NlsContexts.Tooltip tooltip: String
@NlsContexts.Tooltip tooltip: String,
@Nls intentionText: String?,
) {
ThreadingAssertions.assertEventDispatchThread()
@@ -82,7 +91,8 @@ class SuggestedRefactoringAvailabilityIndicator(private val project: Project) {
document.createRangeMarker(markerRange),
document.createRangeMarker(availabilityRange).apply { isGreedyToLeft = true; isGreedyToRight = true },
refactoringEnabled,
tooltip
tooltip,
intentionText
)
if (newData == data) return
@@ -98,13 +108,21 @@ class SuggestedRefactoringAvailabilityIndicator(private val project: Project) {
if (data == null) return
data = null
for ((editor, highlighter) in editorsAndHighlighters) {
for ((editor, highlighterAndHint) in editorsAndHighlighters) {
editor.caretModel.removeCaretListener(caretListener)
highlighter?.let { editor.markupModel.removeHighlighter(it) }
editor.markupModel.removeHighlighter(highlighterAndHint.first)
highlighterAndHint.second?.let { hint ->
hint.uninstall()
Disposer.dispose(hint)
}
}
editorsAndHighlighters.clear()
}
internal fun isHintShown(editor: Editor): Boolean {
return editorsAndHighlighters[editor]?.second != null
}
fun disable() {
ThreadingAssertions.assertEventDispatchThread()
val data = data ?: return
@@ -114,7 +132,8 @@ class SuggestedRefactoringAvailabilityIndicator(private val project: Project) {
data.highlighterRangeMarker.asTextRange ?: return,
data.availabilityRangeMarker.asTextRange ?: return,
false,
data.tooltip
data.tooltip,
data.intentionText,
)
}
}
@@ -133,15 +152,20 @@ class SuggestedRefactoringAvailabilityIndicator(private val project: Project) {
private fun updateHighlighter(editor: Editor) {
ThreadingAssertions.assertEventDispatchThread()
val prevHighlighter = editorsAndHighlighters[editor]
if (prevHighlighter != null) {
editor.markupModel.removeHighlighter(prevHighlighter)
val prevHighlighterAndHint = editorsAndHighlighters[editor]
if (prevHighlighterAndHint != null) {
editor.markupModel.removeHighlighter(prevHighlighterAndHint.first)
editorsAndHighlighters.remove(editor)
prevHighlighterAndHint.second?.let { hint ->
hint.uninstall()
Disposer.dispose(hint)
}
}
val data = data
val range = data?.availabilityRangeMarker?.asTextRange ?: return
if (!range.containsOffset(editor.caretModel.offset)) return
val highlighterRange = data!!.highlighterRangeMarker.asTextRange ?: return
val highlighterRange = data.highlighterRangeMarker.asTextRange ?: return
val highlighter = editor.markupModel.addRangeHighlighter(
highlighterRange.startOffset,
@@ -150,11 +174,22 @@ class SuggestedRefactoringAvailabilityIndicator(private val project: Project) {
TextAttributes(),
HighlighterTargetArea.EXACT_RANGE
)
highlighter.gutterIconRenderer = if (data!!.refactoringEnabled)
RefactoringAvailableGutterIconRenderer(data!!.tooltip)
else
RefactoringDisabledGutterIconRenderer(data!!.tooltip)
editorsAndHighlighters[editor] = highlighter
var hint: EditorLineStripeHintComponent? = null
if (data.refactoringEnabled) {
highlighter.gutterIconRenderer = RefactoringAvailableGutterIconRenderer(data.tooltip)
if (data.intentionText != null && isSuggestedRefactoringEditorHintEnabled()) {
hint = EditorLineStripeHintComponent(editor, {
listOf(listOf(EditorLineStripeTextRenderer(data.intentionText), EditorLineStripeButtonRenderer("Enter")))
}, JBColor.GREEN)
hint.redraw()
}
}
else {
highlighter.gutterIconRenderer = RefactoringDisabledGutterIconRenderer(data.tooltip)
}
editorsAndHighlighters[editor] = highlighter to hint
}
companion object {
@@ -261,7 +296,7 @@ internal fun SuggestedRefactoringAvailabilityIndicator.update(
fun doUpdate() {
if (availabilityRange != null) {
show(document, markerRange, availabilityRange, refactoringAvailable, tooltip)
show(document, markerRange, availabilityRange, refactoringAvailable, tooltip, refactoringData?.getIntentionText())
}
else {
clear()

View File

@@ -0,0 +1,35 @@
// 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.refactoring.suggested
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
import com.intellij.openapi.project.Project
private class SuggestedRefactoringEditorEnterHandler(private val originalHandler: EditorActionHandler) : EditorActionHandler() {
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
val project = editor.project
return isSuggestedRefactoringHintShown(project, editor) || originalHandler.isEnabled(editor, caret, dataContext)
}
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext) {
val project = editor.project
if (isSuggestedRefactoringHintShown(project, editor)) {
performSuggestedRefactoring(project!!,
editor,
null,
null,
showReviewBalloon = true,
ActionPlaces.KEYBOARD_SHORTCUT)
}
else {
originalHandler.execute(editor, caret, dataContext)
}
}
}
internal fun isSuggestedRefactoringHintShown(project: Project?, editor: Editor): Boolean = project != null &&
isSuggestedRefactoringEditorHintEnabled() &&
SuggestedRefactoringProviderImpl.getInstance(project).availabilityIndicator.isHintShown(editor)

View File

@@ -0,0 +1,22 @@
// 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.refactoring.suggested
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
private class SuggestedRefactoringEditorEscapeHandler(private val originalHandler: EditorActionHandler) : EditorActionHandler() {
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
val project = editor.project
return isSuggestedRefactoringHintShown(project, editor) || originalHandler.isEnabled(editor, caret, dataContext)
}
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext) {
val project = editor.project
if (isSuggestedRefactoringHintShown(project, editor)) {
SuggestedRefactoringProviderImpl.getInstance(project!!).suppressForCurrentDeclaration()
}
originalHandler.execute(editor, caret, dataContext)
}
}

View File

@@ -0,0 +1,30 @@
// 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.refactoring.suggested
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
import com.intellij.openapi.options.advanced.AdvancedSettings
import com.intellij.openapi.options.advanced.AdvancedSettingsChangeListener
import com.intellij.openapi.project.ProjectManager
private const val ADV_SETTING_NAME = "promote.suggested.refactoring.in.editor"
internal fun isSuggestedRefactoringEditorHintEnabled(): Boolean {
return AdvancedSettings.getBoolean(ADV_SETTING_NAME)
}
private class SuggestedRefactoringEditorHintAdvSettingListener: AdvancedSettingsChangeListener {
override fun advancedSettingChanged(id: String, oldValue: Any, newValue: Any) {
if (ADV_SETTING_NAME == id) {
if (newValue == true) {
ProjectManager.getInstance().openProjects.forEach {
DaemonCodeAnalyzer.getInstance(it).restart()
}
}
else {
ProjectManager.getInstance().openProjects.asSequence().forEach {
SuggestedRefactoringProviderImpl.getInstance(it).suppressForCurrentDeclaration()
}
}
}
}
}

View File

@@ -20,6 +20,7 @@ import com.intellij.psi.PsiFile
import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.suggested.SuggestedRefactoringState.ErrorLevel
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.NonNls
internal val REFACTORING_DATA_KEY: Key<SuggestedRefactoringData> = Key.create<SuggestedRefactoringData>("suggested.refactoring.data")
@@ -100,19 +101,7 @@ class SuggestedRefactoringIntentionContributor : IntentionMenuContributor {
if (!range.containsOffset(offset)) return null
SuggestedRefactoringFeatureUsage.refactoringSuggested(refactoringData, state)
val text = when (refactoringData) {
is SuggestedRenameData -> RefactoringBundle.message(
"suggested.refactoring.rename.intention.text",
refactoringData.oldName,
refactoringData.newName
)
is SuggestedChangeSignatureData -> RefactoringBundle.message(
"suggested.refactoring.change.signature.intention.text",
refactoringData.nameOfStuffToUpdate
)
}
val text = refactoringData.getIntentionText()
return MyIntention(text, showReviewBalloon = refactoringData is SuggestedChangeSignatureData)
}

View File

@@ -0,0 +1,5 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@ApiStatus.Internal
package com.intellij.refactoring.suggested;
import org.jetbrains.annotations.ApiStatus;

View File

@@ -0,0 +1,179 @@
// 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.editorLineStripeHint
import com.intellij.codeInsight.daemon.impl.HintRenderer
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.DefaultLanguageHighlighterColors
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.event.VisibleAreaEvent
import com.intellij.openapi.editor.event.VisibleAreaListener
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.openapi.observable.util.whenDisposed
import com.intellij.ui.JBColor
import com.intellij.ui.components.JBPanel
import java.awt.Color
import java.awt.Component
import java.awt.Dimension
import java.awt.GradientPaint
import java.awt.Graphics
import java.awt.Graphics2D
import java.awt.Rectangle
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import javax.swing.BoxLayout
import javax.swing.JPanel
class EditorLineStripeHintComponent(
val editor: Editor,
panelRenderer: () -> List<List<EditorLineStripeInlayRenderer>>,
val stripeColor: JBColor,
) : JBPanel<JBPanel<*>>(), Disposable {
@Suppress("UseJBColor")
val gradientStartColor: Color = run {
// transparent
Color(0, 0, 0, 0)
}
private val isInstalled = AtomicBoolean(false)
private val lifetime: AtomicInteger = AtomicInteger(4)
private val batches: List<List<Component>>
init {
isOpaque = false
layout = BoxLayout(this, BoxLayout.X_AXIS)
batches = panelRenderer().map { renderers ->
renderers.map { renderer -> PhantomInlayComponent(editor, renderer) }
}
for (batch in batches) {
batch.forEach { add(it) }
}
val editorListener = EditorListener(this)
editor.contentComponent.addComponentListener(editorListener)
editor.scrollingModel.addVisibleAreaListener(editorListener)
whenDisposed {
editor.contentComponent.removeComponentListener(editorListener)
editor.scrollingModel.removeVisibleAreaListener(editorListener)
}
}
fun reposition() {
val width = editor.scrollingModel.visibleArea.width
setSize(width / 2, editor.lineHeight)
ApplicationManager.getApplication().runReadAction {
val caretPoint = editor.visualPositionToXY(editor.caretModel.visualPosition)
val startPositionOfEolStripe = (width / 3) * 2
setBounds(editor.contentComponent.visibleRect.x + (width / 2), caretPoint.y, width / 2, editor.lineHeight)
for (comp in components) {
if (comp is PhantomInlayComponent) {
comp.reposition()
}
}
val lineEndOffset = editor.document.getLineEndOffset(editor.caretModel.logicalPosition.line)
val caretOffset = editor.caretModel.offset
val inlays = editor.inlayModel.getAfterLineEndElementsInRange(caretOffset, lineEndOffset)
val meaningfulTextEnd = editor.offsetToXY(lineEndOffset).x + inlays.sumOf { it.widthInPixels }
var remainingSpace = if (meaningfulTextEnd > startPositionOfEolStripe) {
-1
}
else {
editor.component.size.width - meaningfulTextEnd
//size.width
}
for (batch in batches) {
val compoundSize = batch.sumOf { it.maximumSize.width }
if (compoundSize < remainingSpace) {
batch.forEach { it.isVisible = true }
remainingSpace -= compoundSize
}
else {
batch.forEach { it.isVisible = false }
remainingSpace -= compoundSize // yes, can be negative. In this case everything to the right is hidden
}
}
}
}
fun isShown(): Boolean = isInstalled.get()
fun redraw() {
uninstall()
val currentLifetime = lifetime.getAndDecrement()
if (currentLifetime <= 0) {
lifetime.set(0)
return
}
reposition()
install()
}
private fun install() {
if (isInstalled.compareAndSet(false, true)) {
editor.contentComponent.add(this)
}
}
fun uninstall() {
if (isInstalled.compareAndSet(true, false)) {
editor.contentComponent.remove(this)
}
}
override fun paintComponent(g: Graphics?) {
super.paintComponent(g)
val g2d = g as Graphics2D
val width = width
val height = height
val gradient = GradientPaint((width.toFloat() / 3) * 2, height / 2f, gradientStartColor, width.toFloat(), height.toFloat() / 2f, stripeColor)
g2d.paint = gradient
g2d.fillRect(0, 0, width, height)
}
override fun dispose(): Unit = Unit
class PhantomInlayComponent(val editor: Editor, val renderer: EditorLineStripeInlayRenderer) : JPanel() {
var pixelWidth = HintRenderer.Companion.calcWidthInPixels(editor, renderer.text, renderer.widthAdjustment)
init {
reposition()
}
fun reposition() {
pixelWidth = HintRenderer.Companion.calcWidthInPixels(editor, renderer.text, renderer.widthAdjustment)
val delta = if (renderer is EditorLineStripeButtonRenderer) 3 else -5
maximumSize = Dimension(pixelWidth - delta, editor.lineHeight)
// delta is needed so that text is close to the button, but far from other text
}
override fun paint(g: Graphics) {
val attrs = renderer.internalGetTextAttributes(editor)
?: editor.colorsScheme.getAttributes(DefaultLanguageHighlighterColors.INLINE_PARAMETER_HINT)
HintRenderer.Companion.paintHint(
g,
editor as EditorImpl,
// todo: figure out why 0 is here
Rectangle(0, y, pixelWidth, height),
renderer.text,
attrs,
attrs,
renderer.widthAdjustment, false)
}
}
}
private class EditorListener(val panel: EditorLineStripeHintComponent) : ComponentAdapter(), VisibleAreaListener {
override fun componentResized(e: ComponentEvent) {
panel.reposition()
}
override fun visibleAreaChanged(e: VisibleAreaEvent) {
panel.reposition()
}
}

View File

@@ -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.editorLineStripeHint
import com.intellij.codeInsight.daemon.impl.HintRenderer
import com.intellij.codeInsight.hints.presentation.InputHandler
import com.intellij.codeInsight.inline.completion.InlineCompletionFontUtils
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.Inlay
import com.intellij.openapi.editor.markup.TextAttributes
import org.jetbrains.annotations.Nls
import java.awt.Graphics
import java.awt.Rectangle
sealed class EditorLineStripeInlayRenderer(text: String) : HintRenderer(text) {
// exposes protected method in HintRenderer to the current package
internal fun internalGetTextAttributes(editor: Editor): TextAttributes? = getTextAttributes(editor)
}
class EditorLineStripeButtonRenderer(text: String) : EditorLineStripeInlayRenderer(text) {
override fun toString(): String {
return "<|$text|>"
}
}
class EditorLineStripeTextRenderer(text: @Nls String) : EditorLineStripeInlayRenderer(text), InputHandler {
override fun getTextAttributes(editor: Editor): TextAttributes? {
return super.getTextAttributes(editor)?.clearEffects(editor)
}
private fun TextAttributes.clearEffects(editor: Editor): TextAttributes = clone().apply {
effectType = null
effectColor = null
backgroundColor = null
foregroundColor = InlineCompletionFontUtils.color(editor)
setAdditionalEffects(emptyMap())
}
override fun paint(inlay: Inlay<*>, g: Graphics, r: Rectangle, textAttributes: TextAttributes) {
// shift because we want two independent inlays to looks related
val shiftedRectangle = Rectangle(r.x - 5, r.y, r.width, r.height)
super.paint(inlay, g, shiftedRectangle, textAttributes)
}
override fun toString(): String {
return "<#$text#>"
}
}

View File

@@ -0,0 +1,18 @@
// 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.editorLineStripeHint
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.PlatformCoreDataKeys
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.ui.JBColor
private class PaintEditorLineStripeHintComponentAction : DumbAwareAction() {
@Suppress("HardCodedStringLiteral")
override fun actionPerformed(e: AnActionEvent) {
val editor = e.dataContext.getData(PlatformCoreDataKeys.EDITOR) ?: return
EditorLineStripeHintComponent(editor, {
listOf(listOf(EditorLineStripeTextRenderer("Component_1"), EditorLineStripeButtonRenderer("Button_1")),
listOf(EditorLineStripeTextRenderer("Component_2"), EditorLineStripeButtonRenderer("Button_2")))
}, JBColor.BLUE).redraw()
}
}

View File

@@ -0,0 +1,5 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@ApiStatus.Internal
package com.intellij.codeInsight.editorLineStripeHint;
import org.jetbrains.annotations.ApiStatus;

View File

@@ -2928,4 +2928,5 @@ action.ToolWindowShowNamesAction.text=Show Tool Window Names
action.TogglePresentationAssistantAction.text=Presentation Assistant
action.separator=Separator
action.ConfigureInspectionsAction.text=Configure Inspections\u2026
action.ConfigureInspectionsAction.text=Configure Inspections\u2026
action.PaintEditorLineStripeComponentAction.text=Paint Editor Caret Stripe

View File

@@ -253,6 +253,9 @@
topic="com.intellij.ide.AppLifecycleListener"
activeInHeadlessMode="true"
activeInTestMode="false" />
<listener class="com.intellij.refactoring.suggested.SuggestedRefactoringEditorHintAdvSettingListener"
topic="com.intellij.openapi.options.advanced.AdvancedSettingsChangeListener"/>
</applicationListeners>
<projectListeners>
<listener class="com.intellij.openapi.command.impl.UndoManagerImpl$MyCommandListener"

View File

@@ -135,6 +135,11 @@
<projectService serviceInterface="com.intellij.refactoring.suggested.SuggestedRefactoringProvider"
serviceImplementation="com.intellij.refactoring.suggested.SuggestedRefactoringProviderImpl"/>
<postStartupActivity implementation="com.intellij.refactoring.suggested.SuggestedRefactoringProviderImpl$Startup"/>
<editorActionHandler action="EditorEscape" implementationClass="com.intellij.refactoring.suggested.SuggestedRefactoringEditorEscapeHandler" order="last"/>
<editorActionHandler action="EditorEnter" implementationClass="com.intellij.refactoring.suggested.SuggestedRefactoringEditorEnterHandler" order="last"/>
<advancedSetting id="promote.suggested.refactoring.in.editor" default="false"
groupKey="suggested.refactoring.group.key"
bundle="messages.RefactoringBundle"/>
<applicationService serviceInterface="com.intellij.openapi.roots.impl.PushedFilePropertiesRetriever"
serviceImplementation="com.intellij.openapi.roots.impl.PushedFilePropertiesRetrieverImpl"/>

View File

@@ -1755,5 +1755,6 @@
<!-- copy of CheckVfsSanityInMenu: !internal so could be invoked via 'search everywhere' -->
<action id="CheckVfsSanity" class="com.intellij.openapi.vfs.newvfs.persistent.CheckVFSHealthAction"/>
<action id="PaintEditorLineStripeComponentAction" class="com.intellij.codeInsight.editorLineStripeHint.PaintEditorLineStripeHintComponentAction" internal="true"/>
</actions>
</idea-plugin>

View File

@@ -515,3 +515,5 @@ progress.title.collecting.suggested.names=Collecting suggested names
dialog.message.selected.element.used.from.non.project.files=Selected element is used from non-project files. These usages won't be renamed. Proceed anyway?
progress.title.prepare.to.delete=Prepare to delete
progress.title.prepare.additional.searcher=Prepare additional searcher
advanced.setting.promote.suggested.refactoring.in.editor=Show a hint for suggested refactoring in the editor
suggested.refactoring.group.key=Suggested refactoring