InlayHints: migrate annotation hints to provider model

GitOrigin-RevId: 8a0de6f22597fa336c92590ba94199293a341e8a
This commit is contained in:
Roman.Ivanov
2019-07-02 07:30:13 +03:00
committed by intellij-monorepo-bot
parent 075cb7338a
commit b524745152
7 changed files with 189 additions and 145 deletions
+4 -2
View File
@@ -60,6 +60,9 @@
<component>
<implementation-class>com.intellij.codeInsight.hints.AnnotationHintsPassFactory</implementation-class>
</component>
<component>
<implementation-class>com.intellij.lang.java.JavaModuleRenameListener</implementation-class>
</component>
<component>
<implementation-class>com.intellij.openapi.vcs.impl.ModuleVcsDetector</implementation-class>
</component>
@@ -299,7 +302,6 @@
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<highlightingPassFactory implementation="com.intellij.codeInsight.hints.AnnotationHintsPassFactory"/>
<highlightingPassFactory implementation="com.intellij.framework.detection.impl.FrameworkDetectionManager$FrameworkDetectionHighlightingPassFactory"/>
<virtualFileSystem implementationClass="com.intellij.openapi.vfs.impl.jrt.JrtFileSystemImpl" key="jrt" physical="true"/>
@@ -1109,7 +1111,6 @@
<joinLinesHandler implementation="com.intellij.codeInsight.editorActions.LiteralJoinLinesHandler"/>
<editorSmartKeysConfigurable instance="com.intellij.application.options.JavadocOptionsProvider" id="editor.preferences.javadocOptions"
key="javadoc.generate.message.title" bundle="messages.JavadocBundle"/>
<editorAppearanceConfigurable instance="com.intellij.codeInsight.hints.AnnotationHintsConfigurable"/>
<wordBoundaryFilter language="JAVA" implementationClass="com.intellij.codeInsight.editorActions.JavaWordBoundaryFilter"/>
<editorActionHandler action="PrevParameter" implementationClass="com.intellij.codeInsight.editorActions.JavaPrevParameterHandler"/>
<editorActionHandler action="NextParameter" implementationClass="com.intellij.codeInsight.editorActions.JavaNextParameterHandler"/>
@@ -1155,6 +1156,7 @@
<codeInsight.parameterInfo language="JAVA" implementationClass="com.intellij.codeInsight.hint.api.impls.MethodParameterInfoHandler"/>
<codeInsight.parameterNameHints language="JAVA" implementationClass="com.intellij.codeInsight.hints.JavaInlayParameterHintsProvider"/>
<codeInsight.inlayProvider language="JAVA" implementationClass="com.intellij.codeInsight.hints.MethodChainsInlayProvider"/>
<codeInsight.inlayProvider language="JAVA" implementationClass="com.intellij.codeInsight.hints.AnnotationInlayProvider"/>
<lang.foldingBuilder language="JAVA" implementationClass="com.intellij.codeInsight.folding.impl.JavaFoldingBuilder"/>
<lang.braceMatcher language="JAVA" implementationClass="com.intellij.codeInsight.highlighting.JavaPairedBraceMatcher"/>
<anonymousElementProvider implementation="com.intellij.lang.java.JavaAnonymousClassesProvider" />
@@ -1,24 +0,0 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.hints
import com.intellij.application.options.editor.CodeFoldingOptionsProvider
import com.intellij.codeInsight.CodeInsightSettings
import com.intellij.openapi.application.ApplicationBundle
import com.intellij.openapi.options.BeanConfigurable
/**
* @author egor
*/
class AnnotationHintsConfigurable : BeanConfigurable<CodeInsightSettings>(), CodeFoldingOptionsProvider {
init {
val settings = CodeInsightSettings.getInstance()
checkBox(ApplicationBundle.message("editor.appearance.show.external.annotations"), settings::SHOW_EXTERNAL_ANNOTATIONS_INLINE)
checkBox(ApplicationBundle.message("editor.appearance.show.inferred.annotations"), settings::SHOW_INFERRED_ANNOTATIONS_INLINE)
}
override fun apply() {
super.apply()
AnnotationHintsPassFactory.modificationStampHolder.forceHintsUpdateOnNextPass()
}
}
@@ -1,79 +0,0 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.hints
import com.intellij.codeInsight.CodeInsightSettings
import com.intellij.codeInsight.ExternalAnnotationsManager
import com.intellij.codeInsight.InferredAnnotationsManager
import com.intellij.codeInsight.daemon.impl.HintRenderer
import com.intellij.codeInsight.javadoc.JavaDocInfoGenerator
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.ToggleAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.Inlay
import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiAnnotation
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiModifierListOwner
class AnnotationHintsPass(
rootElement: PsiElement, editor: Editor,
modificationStampHolder: ModificationStampHolder
) : ElementProcessingHintPass(rootElement, editor, modificationStampHolder) {
override fun isAvailable(virtualFile: VirtualFile): Boolean =
(CodeInsightSettings.getInstance().SHOW_EXTERNAL_ANNOTATIONS_INLINE
&& ExternalAnnotationsManager.getInstance(myProject).hasAnnotationRootsForFile(virtualFile))
|| CodeInsightSettings.getInstance().SHOW_INFERRED_ANNOTATIONS_INLINE
override fun collectElementHints(element: PsiElement, collector: (offset: Int, hint: String) -> Unit) {
if (element is PsiModifierListOwner) {
var annotations = emptySequence<PsiAnnotation>()
if (CodeInsightSettings.getInstance().SHOW_EXTERNAL_ANNOTATIONS_INLINE) {
annotations += ExternalAnnotationsManager.getInstance(myProject).findExternalAnnotations(element).orEmpty()
}
if (CodeInsightSettings.getInstance().SHOW_INFERRED_ANNOTATIONS_INLINE) {
annotations += InferredAnnotationsManager.getInstance(myProject).findInferredAnnotations(element)
}
val shownAnnotations = mutableSetOf<String>()
annotations.forEach {
val nameReferenceElement = it.nameReferenceElement
if (nameReferenceElement != null && element.modifierList != null &&
(shownAnnotations.add(nameReferenceElement.qualifiedName) || JavaDocInfoGenerator.isRepeatableAnnotationType(it))) {
val offset = element.modifierList!!.textRange.startOffset
collector.invoke(offset, "@" + nameReferenceElement.referenceName + it.parameterList.text)
}
}
}
}
override fun getHintKey(): Key<Boolean> = ANNOTATION_INLAY_KEY
override fun createRenderer(text: String): HintRenderer = AnnotationHintRenderer(text)
companion object {
private val ANNOTATION_INLAY_KEY = Key.create<Boolean>("ANNOTATION_INLAY_KEY")
}
private class AnnotationHintRenderer(text: String) : HintRenderer(text) {
override fun getContextMenuGroupId(inlay: Inlay<*>) = "AnnotationHintsContextMenu"
}
class ToggleExternalAnnotationsHintsAction : ToggleAction() {
override fun isSelected(e: AnActionEvent): Boolean = CodeInsightSettings.getInstance().SHOW_EXTERNAL_ANNOTATIONS_INLINE
override fun setSelected(e: AnActionEvent, state: Boolean) {
CodeInsightSettings.getInstance().SHOW_EXTERNAL_ANNOTATIONS_INLINE = state
AnnotationHintsPassFactory.modificationStampHolder.forceHintsUpdateOnNextPass()
}
}
class ToggleInferredAnnotationsHintsAction : ToggleAction() {
override fun isSelected(e: AnActionEvent): Boolean = CodeInsightSettings.getInstance().SHOW_INFERRED_ANNOTATIONS_INLINE
override fun setSelected(e: AnActionEvent, state: Boolean) {
CodeInsightSettings.getInstance().SHOW_INFERRED_ANNOTATIONS_INLINE = state
AnnotationHintsPassFactory.modificationStampHolder.forceHintsUpdateOnNextPass()
}
}
}
@@ -1,29 +0,0 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.hints
import com.intellij.codeHighlighting.TextEditorHighlightingPass
import com.intellij.codeHighlighting.TextEditorHighlightingPassFactory
import com.intellij.codeHighlighting.TextEditorHighlightingPassFactoryRegistrar
import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiJavaFile
internal class AnnotationHintsPassFactory : TextEditorHighlightingPassFactory, TextEditorHighlightingPassFactoryRegistrar {
override fun registerHighlightingPassFactory(registrar: TextEditorHighlightingPassRegistrar, project: Project) {
registrar.registerTextEditorHighlightingPass(this, null, null, false, -1)
}
override fun createHighlightingPass(file: PsiFile, editor: Editor): TextEditorHighlightingPass? {
if (editor.isOneLineMode ||
file !is PsiJavaFile ||
modificationStampHolder.isNotChanged(editor, file)) return null
return AnnotationHintsPass(file, editor, modificationStampHolder)
}
companion object {
val modificationStampHolder: ModificationStampHolder = ModificationStampHolder(Key.create<Long>("LAST_PASS_MODIFICATION_TIMESTAMP"))
}
}
@@ -0,0 +1,167 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.codeInsight.hints
import com.intellij.codeInsight.ExternalAnnotationsManager
import com.intellij.codeInsight.InferredAnnotationsManager
import com.intellij.codeInsight.MakeInferredAnnotationExplicit
import com.intellij.codeInsight.hints.presentation.InsetPresentation
import com.intellij.codeInsight.hints.presentation.MenuOnClickPresentation
import com.intellij.codeInsight.javadoc.JavaDocInfoGenerator
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.application.ApplicationBundle
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.psi.*
import com.intellij.ui.layout.*
import javax.swing.JComponent
import kotlin.reflect.KMutableProperty0
class AnnotationInlayProvider : InlayHintsProvider<AnnotationInlayProvider.Settings> {
override fun getCollectorFor(file: PsiFile,
editor: Editor,
settings: Settings,
sink: InlayHintsSink): InlayHintsCollector? {
val project = file.project
return object : FactoryInlayHintsCollector(editor) {
override fun collect(element: PsiElement, editor: Editor, sink: InlayHintsSink): Boolean {
if (element is PsiModifierListOwner) {
var annotations = emptySequence<PsiAnnotation>()
if (settings.showExternal) {
annotations += ExternalAnnotationsManager.getInstance(project).findExternalAnnotations(element).orEmpty()
}
if (settings.showInferred) {
annotations += InferredAnnotationsManager.getInstance(project).findInferredAnnotations(element)
}
val shownAnnotations = mutableSetOf<String>()
annotations.forEach {
val nameReferenceElement = it.nameReferenceElement
if (nameReferenceElement != null && element.modifierList != null &&
(shownAnnotations.add(nameReferenceElement.qualifiedName) || JavaDocInfoGenerator.isRepeatableAnnotationType(it))) {
val offset = element.modifierList!!.textRange.startOffset
val presentation = annotationPresentation(it)
val menuOnClick = MenuOnClickPresentation(presentation, project) {
val makeExplicit = InsertAnnotationAction(project, file, element)
listOf(
makeExplicit,
ToggleSettingsAction("Turn off external annotations", settings::showExternal),
ToggleSettingsAction("Turn off inferred annotations", settings::showInferred)
)
}
sink.addInlineElement(offset, true, menuOnClick)
}
}
}
return true
}
private fun annotationPresentation(annotation: PsiAnnotation): InsetPresentation = with(factory) {
val nameReferenceElement = annotation.nameReferenceElement
val parameterList = annotation.parameterList
inset(
roundWithBackground(seq(
smallText("@"),
psiSingleReference(smallText(nameReferenceElement?.referenceName ?: "")) { nameReferenceElement?.resolve() },
parametersPresentation(parameterList)
)),
left = 1,
right = 1
)
}
private fun parametersPresentation(parameterList: PsiAnnotationParameterList) = with(factory) {
val attributes = parameterList.attributes
when {
attributes.isEmpty() -> smallText("()")
else -> insideParametersPresentation(attributes, collapsed = parameterList.textLength > 30)
}
}
private fun insideParametersPresentation(attributes: Array<PsiNameValuePair>, collapsed: Boolean) = with(factory) {
collapsible(
smallText("("),
smallText("..."),
{
join(
presentations = attributes.map { pairPresentation(it) },
separator = { smallText(", ") }
)
},
smallText(")"),
collapsed
)
}
private fun pairPresentation(attribute: PsiNameValuePair) = with(factory) {
seq(
psiSingleReference(smallText(attribute.name ?: ""), resolve = { attribute.reference?.resolve() }),
smallText(" = "),
smallText(attribute.value?.text ?: "") // TODO values references, at least annotations and enum names
)
}
}
}
override fun createSettings(): Settings = Settings(showInferred = true, showExternal = true)
override val name: String
get() = "Annotations"
override val key: SettingsKey<Settings>
get() = ourKey
override val previewText: String?
get() = "" // TODO
override fun createConfigurable(settings: Settings): ImmediateConfigurable {
return object : ImmediateConfigurable {
override fun createComponent(listener: ChangeListener): JComponent {
return panel {
row {
checkBox(ApplicationBundle.message("editor.appearance.show.external.annotations"), settings::showExternal)
}
row {
checkBox(ApplicationBundle.message("editor.appearance.show.external.annotations"), settings::showExternal)
}
}
}
}
}
companion object {
val ourKey: SettingsKey<Settings> = SettingsKey("annotation.hints")
}
data class Settings(var showInferred: Boolean = true, var showExternal: Boolean = true)
class ToggleSettingsAction(val text: String, val prop: KMutableProperty0<Boolean>) : AnAction() {
override fun update(e: AnActionEvent) {
val presentation = e.presentation
presentation.text = text
}
override fun actionPerformed(e: AnActionEvent) {
prop.set(!prop.get())
}
}
}
class InsertAnnotationAction(
private val project: Project,
private val file: PsiFile,
private val element: PsiModifierListOwner
) : AnAction() {
override fun update(e: AnActionEvent) {
e.presentation.text = "Insert annotation"
}
override fun actionPerformed(e: AnActionEvent) {
val intention = MakeInferredAnnotationExplicit()
if (intention.isAvailable(project, file, element)) {
intention.makeAnnotationsExplicit(project, file, element)
}
}
}
@@ -117,6 +117,11 @@ class PresentationFactory(private val editor: EditorImpl) {
}), onClick = unwrapAction)
}
@Contract(pure = true)
fun inset(base: InlayPresentation, left: Int = 0, right: Int = 0, top: Int = 0, down: Int = 0): InsetPresentation {
return InsetPresentation(base, left, right, top, down)
}
/**
* Creates node, that can be collapsed/expanded by clicking on prefix/suffix.
* If presentation is collapsed, clicking to content will expand it.
@@ -270,6 +275,19 @@ class PresentationFactory(private val editor: EditorImpl) {
}
}
fun join(presentations: List<InlayPresentation>, separator: () -> InlayPresentation) : InlayPresentation {
val seq = mutableListOf<InlayPresentation>()
var first = true
for (presentation in presentations) {
if (!first) {
seq.add(separator())
}
seq.add(presentation)
first = false
}
return SequencePresentation(seq)
}
@Contract(pure = true)
fun rounding(arcWidth: Int, arcHeight: Int, presentation: InlayPresentation): InlayPresentation =
RoundPresentation(presentation, arcWidth, arcHeight)
-11
View File
@@ -775,17 +775,6 @@
<action class="com.intellij.compiler.backwardRefs.view.TestCompilerRefFunctionalExpressionSearchAction" id="TestCompilerReferenceServiceFunctionalExpressionSearch" internal="true"/>
<action class="com.intellij.compiler.backwardRefs.view.TestCompilerRefInheritanceAction" id="TestCompilerReferenceServiceInheritance" internal="true"/>
<group id="AnnotationHintsContextMenu" popup="true">
<action id="ToggleExternalAnnotationsHintsAction"
class="com.intellij.codeInsight.hints.AnnotationHintsPass$ToggleExternalAnnotationsHintsAction"
text="Show External Annotations"/>
<action id="ToggleInferredAnnotationsHintsAction"
class="com.intellij.codeInsight.hints.AnnotationHintsPass$ToggleInferredAnnotationsHintsAction"
text="Show Inferred Annotations"/>
<separator/>
<reference ref="EditorPopupMenu"/>
</group>
<action id="MethodOverloadSwitchUp" class="com.intellij.codeInsight.editorActions.JavaMethodOverloadSwitchUpAction"/>
<action id="MethodOverloadSwitchDown" class="com.intellij.codeInsight.editorActions.JavaMethodOverloadSwitchDownAction" />