mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-13 15:52:01 +07:00
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:
committed by
intellij-monorepo-bot
parent
82fd7ae8d7
commit
36d0b02a0d
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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#>"
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user