[polySymbols] WEB-73289 PolySymbols: simplify PolySymbol interface - extract PolySymbolWithDocumentation interface

GitOrigin-RevId: 7eff7437c872855fa72415da703781057f2e935d
This commit is contained in:
Piotr Tomiak
2025-06-04 17:29:59 +02:00
committed by intellij-monorepo-bot
parent 997750ddad
commit f56f2f22d5
23 changed files with 853 additions and 687 deletions

View File

@@ -20,14 +20,9 @@
- sf:PROP_NO_DOC:java.lang.String
- sf:PROP_READ_ONLY:java.lang.String
- adjustNameForRefactoring(com.intellij.polySymbols.query.PolySymbolsQueryExecutor,java.lang.String,java.lang.String):java.lang.String
- createDocumentation(com.intellij.psi.PsiElement):com.intellij.polySymbols.documentation.PolySymbolDocumentation
- a:createPointer():com.intellij.model.Pointer
- getApiStatus():com.intellij.polySymbols.PolySymbolApiStatus
- getAttributeValue():com.intellij.polySymbols.html.PolySymbolHtmlAttributeValue
- getDefaultValue():java.lang.String
- getDescription():java.lang.String
- getDescriptionSections():java.util.Map
- getDocUrl():java.lang.String
- getDocumentationTarget(com.intellij.psi.PsiElement):com.intellij.platform.backend.documentation.DocumentationTarget
- getIcon():javax.swing.Icon
- getModificationCount():J
@@ -420,7 +415,7 @@
- equals(java.lang.Object):Z
- hashCode():I
*:com.intellij.polySymbols.customElements.CustomElementsSymbol
- com.intellij.polySymbols.PolySymbol
- com.intellij.polySymbols.documentation.PolySymbolWithDocumentation
- *sf:Companion:com.intellij.polySymbols.customElements.CustomElementsSymbol$Companion
*f:com.intellij.polySymbols.customElements.CustomElementsSymbol$Companion
- f:getCEM_DECLARATIONS():com.intellij.polySymbols.PolySymbolQualifiedKind
@@ -441,6 +436,7 @@
*:com.intellij.polySymbols.documentation.PolySymbolDocumentation
- *sf:Companion:com.intellij.polySymbols.documentation.PolySymbolDocumentation$Companion
- appendFootnote(java.lang.String):com.intellij.polySymbols.documentation.PolySymbolDocumentation
- a:build(com.intellij.polySymbols.PolySymbolOrigin):com.intellij.platform.backend.documentation.DocumentationResult
- a:getApiStatus():com.intellij.polySymbols.PolySymbolApiStatus
- a:getDefaultValue():java.lang.String
- a:getDefinition():java.lang.String
@@ -471,8 +467,8 @@
- a:withName(java.lang.String):com.intellij.polySymbols.documentation.PolySymbolDocumentation
- a:withRequired(Z):com.intellij.polySymbols.documentation.PolySymbolDocumentation
*f:com.intellij.polySymbols.documentation.PolySymbolDocumentation$Companion
- f:create(com.intellij.polySymbols.PolySymbol,com.intellij.psi.PsiElement,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.polySymbols.PolySymbolApiStatus,Z,java.lang.String,java.lang.String,javax.swing.Icon,java.util.Map,java.lang.String):com.intellij.polySymbols.documentation.PolySymbolDocumentation
- bs:create$default(com.intellij.polySymbols.documentation.PolySymbolDocumentation$Companion,com.intellij.polySymbols.PolySymbol,com.intellij.psi.PsiElement,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.polySymbols.PolySymbolApiStatus,Z,java.lang.String,java.lang.String,javax.swing.Icon,java.util.Map,java.lang.String,I,java.lang.Object):com.intellij.polySymbols.documentation.PolySymbolDocumentation
- f:create(com.intellij.polySymbols.documentation.PolySymbolWithDocumentation,com.intellij.psi.PsiElement,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.polySymbols.PolySymbolApiStatus,Z,java.lang.String,java.lang.String,javax.swing.Icon,java.util.Map,java.lang.String):com.intellij.polySymbols.documentation.PolySymbolDocumentation
- bs:create$default(com.intellij.polySymbols.documentation.PolySymbolDocumentation$Companion,com.intellij.polySymbols.documentation.PolySymbolWithDocumentation,com.intellij.psi.PsiElement,java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String,com.intellij.polySymbols.PolySymbolApiStatus,Z,java.lang.String,java.lang.String,javax.swing.Icon,java.util.Map,java.lang.String,I,java.lang.Object):com.intellij.polySymbols.documentation.PolySymbolDocumentation
*:com.intellij.polySymbols.documentation.PolySymbolDocumentationCustomizer
- *sf:Companion:com.intellij.polySymbols.documentation.PolySymbolDocumentationCustomizer$Companion
- a:customize(com.intellij.polySymbols.PolySymbol,com.intellij.psi.PsiElement,com.intellij.polySymbols.documentation.PolySymbolDocumentation):com.intellij.polySymbols.documentation.PolySymbolDocumentation
@@ -480,10 +476,18 @@
- f:getEP_NAME():com.intellij.openapi.extensions.ExtensionPointName
*:com.intellij.polySymbols.documentation.PolySymbolDocumentationTarget
- com.intellij.platform.backend.documentation.DocumentationTarget
- computeDocumentation():com.intellij.platform.backend.documentation.DocumentationResult
- computePresentation():com.intellij.platform.backend.presentation.TargetPresentation
- a:getLocation():com.intellij.psi.PsiElement
- a:getSymbol():com.intellij.polySymbols.PolySymbol
*:com.intellij.polySymbols.documentation.PolySymbolWithDocumentation
- com.intellij.polySymbols.PolySymbol
- createDocumentation(com.intellij.psi.PsiElement):com.intellij.polySymbols.documentation.PolySymbolDocumentation
- a:createPointer():com.intellij.model.Pointer
- getDefaultValue():java.lang.String
- getDescription():java.lang.String
- getDescriptionSections():java.util.Map
- getDocUrl():java.lang.String
- getDocumentationTarget(com.intellij.psi.PsiElement):com.intellij.platform.backend.documentation.DocumentationTarget
*a:com.intellij.polySymbols.framework.PolySymbolsFramework
- *sf:Companion:com.intellij.polySymbols.framework.PolySymbolsFramework$Companion
- <init>():V
@@ -880,20 +884,14 @@
- createPointer():com.intellij.model.Pointer
- f:getNavigationItem():com.intellij.navigation.NavigationItem
- navigationRequest():com.intellij.platform.backend.navigation.NavigationRequest
*a:com.intellij.polySymbols.utils.PolySymbolDelegate
*:com.intellij.polySymbols.utils.PolySymbolDelegate
- com.intellij.polySymbols.PolySymbol
- *sf:Companion:com.intellij.polySymbols.utils.PolySymbolDelegate$Companion
- <init>(com.intellij.polySymbols.PolySymbol):V
- createDocumentation(com.intellij.psi.PsiElement):com.intellij.polySymbols.documentation.PolySymbolDocumentation
- getAbstract():Z
- getApiStatus():com.intellij.polySymbols.PolySymbolApiStatus
- getAttributeValue():com.intellij.polySymbols.html.PolySymbolHtmlAttributeValue
- getCodeCompletions(com.intellij.polySymbols.PolySymbolQualifiedName,com.intellij.polySymbols.query.PolySymbolsCodeCompletionQueryParams,com.intellij.util.containers.Stack):java.util.List
- getDefaultValue():java.lang.String
- f:getDelegate():com.intellij.polySymbols.PolySymbol
- getDescription():java.lang.String
- getDescriptionSections():java.util.Map
- getDocUrl():java.lang.String
- a:getDelegate():com.intellij.polySymbols.PolySymbol
- getDocumentationTarget(com.intellij.psi.PsiElement):com.intellij.platform.backend.documentation.DocumentationTarget
- getExtension():Z
- getIcon():javax.swing.Icon
@@ -916,9 +914,18 @@
- getType():java.lang.Object
- getVirtual():Z
- isExclusiveFor(com.intellij.polySymbols.PolySymbolQualifiedKind):Z
- sf:unwrapAllDelegates(com.intellij.polySymbols.PolySymbol):com.intellij.polySymbols.PolySymbol
- s:unwrapAllDelegates(com.intellij.polySymbols.PolySymbol):com.intellij.polySymbols.PolySymbol
*f:com.intellij.polySymbols.utils.PolySymbolDelegate$Companion
- f:unwrapAllDelegates(com.intellij.polySymbols.PolySymbol):com.intellij.polySymbols.PolySymbol
*:com.intellij.polySymbols.utils.PolySymbolDelegateWithDocumentation
- com.intellij.polySymbols.documentation.PolySymbolWithDocumentation
- com.intellij.polySymbols.utils.PolySymbolDelegate
- createDocumentation(com.intellij.psi.PsiElement):com.intellij.polySymbols.documentation.PolySymbolDocumentation
- getDefaultValue():java.lang.String
- getDescription():java.lang.String
- getDescriptionSections():java.util.Map
- getDocUrl():java.lang.String
- getDocumentationTarget(com.intellij.psi.PsiElement):com.intellij.platform.backend.documentation.DocumentationTarget
*:com.intellij.polySymbols.utils.PolySymbolTypeSupport
- a:resolve(java.util.List):java.lang.Object
*:com.intellij.polySymbols.utils.PolySymbolTypeSupport$TypeReference
@@ -1025,10 +1032,9 @@
*:com.intellij.polySymbols.utils.PolySymbolsStructuredScope$PolySymbolsPsiScopesHolder$ScopeModifier
- a:addSymbol(com.intellij.polySymbols.PolySymbol):V
- a:addSymbols(java.util.List):V
*a:com.intellij.polySymbols.utils.PsiSourcedPolySymbolDelegate
- com.intellij.polySymbols.utils.PolySymbolDelegate
*:com.intellij.polySymbols.utils.PsiSourcedPolySymbolDelegate
- com.intellij.polySymbols.search.PsiSourcedPolySymbol
- <init>(com.intellij.polySymbols.search.PsiSourcedPolySymbol):V
- com.intellij.polySymbols.utils.PolySymbolDelegate
- getNavigationTargets(com.intellij.openapi.project.Project):java.util.Collection
- getPsiContext():com.intellij.psi.PsiElement
- getSource():com.intellij.psi.PsiElement
@@ -1052,8 +1058,11 @@
- f:create(com.intellij.polySymbols.PolySymbolQualifiedKind,java.lang.String,com.intellij.polySymbols.PolySymbolOrigin,com.intellij.polySymbols.PolySymbolQualifiedKind[],com.intellij.polySymbols.PolySymbol$Priority,java.util.List):com.intellij.polySymbols.utils.ReferencingPolySymbol
- bs:create$default(com.intellij.polySymbols.utils.ReferencingPolySymbol$Companion,com.intellij.polySymbols.PolySymbolQualifiedKind,java.lang.String,com.intellij.polySymbols.PolySymbolOrigin,com.intellij.polySymbols.PolySymbolQualifiedKind[],com.intellij.polySymbols.PolySymbol$Priority,java.util.List,I,java.lang.Object):com.intellij.polySymbols.utils.ReferencingPolySymbol
*:com.intellij.polySymbols.webTypes.WebTypesSymbol
- com.intellij.polySymbols.documentation.PolySymbolWithDocumentation
- com.intellij.polySymbols.search.PsiSourcedPolySymbol
- *sf:Companion:com.intellij.polySymbols.webTypes.WebTypesSymbol$Companion
- a:createPointer():com.intellij.model.Pointer
- getDocumentationTarget(com.intellij.psi.PsiElement):com.intellij.platform.backend.documentation.DocumentationTarget
- a:getLocation():com.intellij.polySymbols.webTypes.WebTypesSymbol$Location
*f:com.intellij.polySymbols.webTypes.WebTypesSymbol$Companion
*:com.intellij.polySymbols.webTypes.WebTypesSymbol$FileExport

View File

@@ -73,26 +73,6 @@ interface PolySymbol : PolySymbolsScope, Symbol, NavigatableSymbol, PolySymbolsP
*/
val name: @NlsSafe String
/**
* An optional text, which describes the symbol purpose and usage.
* It is rendered in the documentation popup or view.
*/
val description: @Nls String?
get() = null
/**
* Additional sections, to be rendered in the symbols documentation.
* Each section should have a name, but the contents can be empty.
*/
val descriptionSections: Map<@Nls String, @Nls String>
get() = emptyMap()
/**
* An optional URL to a website with detailed symbol's documentation
*/
val docUrl: @NlsSafe String?
get() = null
/**
* An optional icon associated with the symbol, which is going to be used across the IDE.
* If none is specified, a default icon of the origin will be used and if thats not available,
@@ -101,13 +81,6 @@ interface PolySymbol : PolySymbolsScope, Symbol, NavigatableSymbol, PolySymbolsP
val icon: Icon?
get() = null
/**
* If the symbol represents some property, variable or anything that can hold a value,
* this property documents what is the default value.
*/
val defaultValue: @NlsSafe String?
get() = null
/**
* The type of the symbol. The type can be interpreted only within the context of symbol origin
* and in regard to its namespace and kind. The type may be a language type,
@@ -279,33 +252,18 @@ interface PolySymbol : PolySymbolsScope, Symbol, NavigatableSymbol, PolySymbolsP
/**
* Used by Poly Symbols framework to get a [DocumentationTarget], which handles documentation
* rendering for the symbol. Default implementation will use [createDocumentation]
* to render the documentation.
* rendering for the symbol. You may implement [com.intellij.polySymbols.documentation.PolySymbolWithDocumentation]
* interface, which will provide a default implementation to render the documentation.
*/
fun getDocumentationTarget(location: PsiElement?): DocumentationTarget? =
if (properties[PROP_NO_DOC] != true)
PolySymbolDocumentationTargetImpl(this, location)
else
null
/**
* Returns [PolySymbolDocumentation] - an interface holding information required to render documentation for the symbol.
* By default, it's contents are build from the available Poly Symbol information.
*
* To customize symbols documentation, one can override the method, or implement [PolySymbolDocumentationCustomizer].
*
* [PolySymbolDocumentation] interface provides builder methods for customizing the documentation.
* `with*` methods return a copy of the documentation with customized fields.
*/
fun createDocumentation(location: PsiElement?): PolySymbolDocumentation? =
PolySymbolDocumentation.create(this, location)
null
override fun getNavigationTargets(project: Project): Collection<NavigationTarget> =
emptyList()
/**
* Symbols can be used in [CachedValue]s as dependencies.
* If a symbol instance can mutate over the time, it should properly implement this method.
* If a symbol instance can mutate over time, it should properly implement this method.
*/
override fun getModificationCount(): Long = 0

View File

@@ -19,9 +19,9 @@ import com.intellij.psi.createSmartPointer
* for code completion.
*/
class CodeCompletionPolySymbolWithDocumentation(
delegate: PolySymbol,
override val delegate: PolySymbol,
private val location: PsiElement,
) : PolySymbolDelegate<PolySymbol>(delegate), DocumentationSymbol {
) : PolySymbolDelegate<PolySymbol>, DocumentationSymbol {
override fun createPointer(): Pointer<CodeCompletionPolySymbolWithDocumentation> {
val delegatePtr = delegate.createPointer()
@@ -40,9 +40,9 @@ class CodeCompletionPolySymbolWithDocumentation(
}
class PsiSourcedCodeCompletionPolySymbolWithDocumentation(
delegate: PsiSourcedPolySymbol,
override val delegate: PsiSourcedPolySymbol,
private val location: PsiElement,
) : PsiSourcedPolySymbolDelegate<PsiSourcedPolySymbol>(delegate), DocumentationSymbol {
) : PsiSourcedPolySymbolDelegate<PsiSourcedPolySymbol>, DocumentationSymbol {
override fun createPointer(): Pointer<PsiSourcedCodeCompletionPolySymbolWithDocumentation> {
val delegatePtr = delegate.createPointer()

View File

@@ -1,10 +1,10 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.polySymbols.customElements
import com.intellij.polySymbols.PolySymbol
import com.intellij.polySymbols.PolySymbolQualifiedKind
import com.intellij.polySymbols.documentation.PolySymbolWithDocumentation
interface CustomElementsSymbol : PolySymbol {
interface CustomElementsSymbol : PolySymbolWithDocumentation {
companion object {

View File

@@ -11,6 +11,7 @@ import com.intellij.polySymbols.customElements.CustomElementsSymbol
import com.intellij.polySymbols.customElements.json.CustomElementClassOrMixinDeclaration
import com.intellij.polySymbols.customElements.json.resolve
import com.intellij.polySymbols.customElements.json.toApiStatus
import com.intellij.polySymbols.documentation.PolySymbolWithDocumentation
import com.intellij.polySymbols.impl.StaticPolySymbolsScopeBase
import com.intellij.polySymbols.patterns.PolySymbolsPattern
import com.intellij.polySymbols.query.PolySymbolsCodeCompletionQueryParams
@@ -81,7 +82,9 @@ class CustomElementsClassOrMixinDeclarationAdapter private constructor(
override val description: String?
get() = (base.declaration.description?.takeIf { it.isNotBlank() } ?: base.declaration.summary)
?.let { origin.renderDescription(it) }
?: superContributions.asSequence().mapNotNull { it.description }.firstOrNull()
?: superContributions.asSequence()
.mapNotNull { (it as? PolySymbolWithDocumentation)?.description }
.firstOrNull()
override val apiStatus: PolySymbolApiStatus
get() = base.declaration.deprecated.toApiStatus(origin) ?: PolySymbolApiStatus.Stable

View File

@@ -29,7 +29,7 @@ class CustomElementsCustomElementExportSymbol private constructor(
override fun withQueryExecutorContext(queryExecutor: PolySymbolsQueryExecutor): PolySymbol =
this
override fun createPointer(): Pointer<out PolySymbol> =
override fun createPointer(): Pointer<out CustomElementsSymbol> =
Pointer.hardPointer(this)
override fun matchContext(context: PolyContext): Boolean =

View File

@@ -3,10 +3,11 @@ package com.intellij.polySymbols.documentation
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.text.Strings
import com.intellij.psi.PsiElement
import com.intellij.polySymbols.PolySymbol
import com.intellij.platform.backend.documentation.DocumentationResult
import com.intellij.polySymbols.PolySymbolApiStatus
import com.intellij.polySymbols.PolySymbolOrigin
import com.intellij.polySymbols.documentation.impl.PolySymbolDocumentationImpl
import com.intellij.psi.PsiElement
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import javax.swing.Icon
@@ -113,18 +114,20 @@ interface PolySymbolDocumentation {
fun withHeader(header: @Nls String?): PolySymbolDocumentation
fun with(name: @NlsSafe String = this.name,
definition: @NlsSafe String = this.definition,
definitionDetails: @Nls String? = this.definitionDetails,
description: @Nls String? = this.description,
docUrl: @NlsSafe String? = this.docUrl,
apiStatus: PolySymbolApiStatus? = this.apiStatus,
required: Boolean = this.required,
defaultValue: @NlsSafe String? = this.defaultValue,
library: @NlsSafe String? = this.library,
icon: Icon? = this.icon,
additionalSections: Map<@Nls String, @Nls String> = emptyMap(),
footnote: @Nls String? = this.footnote): PolySymbolDocumentation
fun with(
name: @NlsSafe String = this.name,
definition: @NlsSafe String = this.definition,
definitionDetails: @Nls String? = this.definitionDetails,
description: @Nls String? = this.description,
docUrl: @NlsSafe String? = this.docUrl,
apiStatus: PolySymbolApiStatus? = this.apiStatus,
required: Boolean = this.required,
defaultValue: @NlsSafe String? = this.defaultValue,
library: @NlsSafe String? = this.library,
icon: Icon? = this.icon,
additionalSections: Map<@Nls String, @Nls String> = emptyMap(),
footnote: @Nls String? = this.footnote,
): PolySymbolDocumentation
fun appendFootnote(footnote: @Nls String?): PolySymbolDocumentation =
if (footnote != null)
@@ -132,26 +135,30 @@ interface PolySymbolDocumentation {
else
this
fun build(origin: PolySymbolOrigin): DocumentationResult
companion object {
fun create(symbol: PolySymbol,
location: PsiElement?,
name: String = symbol.name,
definition: String = Strings.escapeXmlEntities(symbol.name),
definitionDetails: String? = null,
description: @Nls String? = symbol.description,
docUrl: String? = symbol.docUrl,
apiStatus: PolySymbolApiStatus? = symbol.apiStatus,
required: Boolean = symbol.required ?: false,
defaultValue: String? = symbol.defaultValue ?: symbol.attributeValue?.default,
library: String? = symbol.origin.takeIf { it.library != null }
?.let { context ->
context.library +
if (context.version?.takeIf { it != "0.0.0" } != null) "@${context.version}" else ""
},
icon: Icon? = symbol.icon,
descriptionSections: Map<@Nls String, @Nls String> = symbol.descriptionSections,
footnote: @Nls String? = null): PolySymbolDocumentation =
fun create(
symbol: PolySymbolWithDocumentation,
location: PsiElement?,
name: String = symbol.name,
definition: String = Strings.escapeXmlEntities(symbol.name),
definitionDetails: String? = null,
description: @Nls String? = symbol.description,
docUrl: String? = symbol.docUrl,
apiStatus: PolySymbolApiStatus? = symbol.apiStatus,
required: Boolean = symbol.required ?: false,
defaultValue: String? = symbol.defaultValue ?: symbol.attributeValue?.default,
library: String? = symbol.origin.takeIf { it.library != null }
?.let { context ->
context.library +
if (context.version?.takeIf { it != "0.0.0" } != null) "@${context.version}" else ""
},
icon: Icon? = symbol.icon,
descriptionSections: Map<@Nls String, @Nls String> = symbol.descriptionSections,
footnote: @Nls String? = null,
): PolySymbolDocumentation =
PolySymbolDocumentationImpl(name, definition, definitionDetails, description, docUrl, apiStatus, required, defaultValue, library, icon,
descriptionSections, footnote, null)
.let { doc: PolySymbolDocumentation ->

View File

@@ -1,12 +1,10 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.polySymbols.documentation
import com.intellij.platform.backend.documentation.DocumentationResult
import com.intellij.platform.backend.documentation.DocumentationTarget
import com.intellij.platform.backend.presentation.TargetPresentation
import com.intellij.psi.PsiElement
import com.intellij.polySymbols.PolySymbol
import com.intellij.polySymbols.documentation.impl.PolySymbolDocumentationTargetImpl
import com.intellij.psi.PsiElement
interface PolySymbolDocumentationTarget : DocumentationTarget {
@@ -20,8 +18,4 @@ interface PolySymbolDocumentationTarget : DocumentationTarget {
.presentation()
}
override fun computeDocumentation(): DocumentationResult? =
symbol.createDocumentation(location)
?.takeIf { it.isNotEmpty() }
?.let { doc -> PolySymbolDocumentationTargetImpl.buildDocumentation(symbol.origin, doc) }
}

View File

@@ -0,0 +1,61 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.polySymbols.documentation
import com.intellij.model.Pointer
import com.intellij.openapi.util.NlsSafe
import com.intellij.platform.backend.documentation.DocumentationTarget
import com.intellij.polySymbols.PolySymbol
import com.intellij.polySymbols.documentation.impl.PolySymbolDocumentationTargetImpl
import com.intellij.psi.PsiElement
import org.jetbrains.annotations.Nls
interface PolySymbolWithDocumentation : PolySymbol {
/**
* An optional text, which describes the symbol purpose and usage.
* It is rendered in the documentation popup or view.
*/
val description: @Nls String?
get() = null
/**
* Additional sections, to be rendered in the symbols documentation.
* Each section should have a name, but the contents can be empty.
*/
val descriptionSections: Map<@Nls String, @Nls String>
get() = emptyMap()
/**
* An optional URL to a website with detailed symbol's documentation
*/
val docUrl: @NlsSafe String?
get() = null
/**
* If the symbol represents some property, variable or anything that can hold a value,
* this property documents what is the default value.
*/
val defaultValue: @NlsSafe String?
get() = null
/**
* Returns a default target implementation, which uses [createDocumentation] method to render the documentation.
*/
override fun getDocumentationTarget(location: PsiElement?): DocumentationTarget? =
PolySymbolDocumentationTargetImpl(this, location)
/**
* Returns [PolySymbolDocumentation] - an interface holding information required to render documentation for the symbol.
* By default, it's contents are build from the available Poly Symbol information.
*
* To customize symbols documentation, one can override the method, or implement [PolySymbolDocumentationCustomizer].
*
* [PolySymbolDocumentation] interface provides builder methods for customizing the documentation.
* `with*` methods return a copy of the documentation with customized fields.
*/
fun createDocumentation(location: PsiElement?): PolySymbolDocumentation? =
PolySymbolDocumentation.create(this, location)
override fun createPointer(): Pointer<out PolySymbolWithDocumentation>
}

View File

@@ -1,23 +1,37 @@
package com.intellij.polySymbols.documentation.impl
import com.intellij.lang.documentation.DocumentationMarkup
import com.intellij.openapi.util.text.StringUtil
import com.intellij.platform.backend.documentation.DocumentationResult
import com.intellij.polySymbols.PolySymbolApiStatus
import com.intellij.polySymbols.PolySymbolOrigin
import com.intellij.polySymbols.PolySymbolsBundle
import com.intellij.polySymbols.documentation.PolySymbolDocumentation
import com.intellij.polySymbols.impl.scaleToHeight
import com.intellij.ui.scale.ScaleContext
import com.intellij.ui.scale.ScaleType
import com.intellij.util.IconUtil
import com.intellij.util.ui.UIUtil
import org.jetbrains.annotations.Nls
import java.awt.Image
import java.awt.image.BufferedImage
import javax.swing.Icon
internal data class PolySymbolDocumentationImpl(override val name: String,
override val definition: String,
override val definitionDetails: String?,
override val description: @Nls String?,
override val docUrl: String?,
override val apiStatus: PolySymbolApiStatus?,
override val required: Boolean,
override val defaultValue: String?,
override val library: String?,
override val icon: Icon?,
override val descriptionSections: Map<@Nls String, @Nls String>,
override val footnote: @Nls String?,
override val header: @Nls String?) : PolySymbolDocumentation {
internal data class PolySymbolDocumentationImpl(
override val name: String,
override val definition: String,
override val definitionDetails: String?,
override val description: @Nls String?,
override val docUrl: String?,
override val apiStatus: PolySymbolApiStatus?,
override val required: Boolean,
override val defaultValue: String?,
override val library: String?,
override val icon: Icon?,
override val descriptionSections: Map<@Nls String, @Nls String>,
override val footnote: @Nls String?,
override val header: @Nls String?,
) : PolySymbolDocumentation {
override fun isNotEmpty(): Boolean =
name != definition || description != null || docUrl != null || (apiStatus != null && apiStatus != PolySymbolApiStatus.Stable)
|| required || defaultValue != null || library != null || descriptionSections.isNotEmpty() || footnote != null
@@ -62,20 +76,172 @@ internal data class PolySymbolDocumentationImpl(override val name: String,
override fun withHeader(header: @Nls String?): PolySymbolDocumentation =
copy(header = header)
override fun with(name: String,
definition: String,
definitionDetails: String?,
description: @Nls String?,
docUrl: String?,
apiStatus: PolySymbolApiStatus?,
required: Boolean,
defaultValue: String?,
library: String?,
icon: Icon?,
additionalSections: Map<@Nls String, @Nls String>,
footnote: @Nls String?): PolySymbolDocumentation =
override fun with(
name: String,
definition: String,
definitionDetails: String?,
description: @Nls String?,
docUrl: String?,
apiStatus: PolySymbolApiStatus?,
required: Boolean,
defaultValue: String?,
library: String?,
icon: Icon?,
additionalSections: Map<@Nls String, @Nls String>,
footnote: @Nls String?,
): PolySymbolDocumentation =
copy(name = name, definition = definition, definitionDetails = definitionDetails, description = description,
docUrl = docUrl, apiStatus = apiStatus, required = required, defaultValue = defaultValue, library = library, icon = icon,
descriptionSections = descriptionSections + additionalSections, footnote = footnote)
override fun build(origin: PolySymbolOrigin): DocumentationResult {
val url2ImageMap = mutableMapOf<String, Image>()
@Suppress("HardCodedStringLiteral")
val contents = StringBuilder()
.appendHeader()
.appendDefinition(url2ImageMap)
.appendDescription()
.appendSections()
.appendFootnote()
.toString()
.loadLocalImages(origin, url2ImageMap)
return DocumentationResult.documentation(contents).images(url2ImageMap).externalUrl(docUrl)
.definitionDetails(definitionDetails)
}
private fun StringBuilder.appendDefinition(url2ImageMap: MutableMap<String, Image>): StringBuilder =
append(DocumentationMarkup.DEFINITION_START)
.also {
icon?.let { appendIcon(it, url2ImageMap).append("&nbsp;&nbsp;") }
}
.append(definition)
.append(DocumentationMarkup.DEFINITION_END)
.append('\n')
private fun StringBuilder.appendDescription(): StringBuilder =
description?.let {
append(DocumentationMarkup.CONTENT_START).append('\n')
.append(it).append('\n')
.append(DocumentationMarkup.CONTENT_END)
}
?: this
private fun StringBuilder.appendSections(): StringBuilder =
buildSections().let { sections ->
if (sections.isNotEmpty()) {
append(DocumentationMarkup.SECTIONS_START)
.append('\n')
sections.entries.forEach { (name, value) ->
append(DocumentationMarkup.SECTION_HEADER_START)
.append(StringUtil.capitalize(name))
if (value.isNotBlank()) {
if (!name.endsWith(":"))
append(':')
append(DocumentationMarkup.SECTION_SEPARATOR)
.append(value)
}
append(DocumentationMarkup.SECTION_END)
.append('\n')
}
append(DocumentationMarkup.SECTIONS_END)
.append('\n')
}
this
}
private fun StringBuilder.appendFootnote(): StringBuilder =
footnote?.let {
append(DocumentationMarkup.CONTENT_START)
.append(it)
.append(DocumentationMarkup.CONTENT_END)
.append('\n')
} ?: this
private fun StringBuilder.appendHeader(): StringBuilder =
header?.let {
append("<div class='" + DocumentationMarkup.CLASS_TOP + "'>")
.append(it)
.append("</div>\n")
} ?: this
private fun buildSections(): Map<String, String> =
LinkedHashMap(descriptionSections).also { sections ->
if (required) sections[PolySymbolsBundle.message("mdn.documentation.section.isRequired")] = ""
apiStatus?.let { status ->
when (status) {
is PolySymbolApiStatus.Deprecated -> {
sections[PolySymbolsBundle.message("mdn.documentation.section.status.Deprecated")] = status.message ?: ""
status.since?.let { sections[PolySymbolsBundle.message("mdn.documentation.section.status.DeprecatedSince")] = it }
}
is PolySymbolApiStatus.Obsolete -> {
sections[PolySymbolsBundle.message("mdn.documentation.section.status.Obsolete")] = status.message ?: ""
status.since?.let { sections[PolySymbolsBundle.message("mdn.documentation.section.status.ObsoleteSince")] = it }
}
is PolySymbolApiStatus.Experimental -> {
sections[PolySymbolsBundle.message("mdn.documentation.section.status.Experimental")] = status.message ?: ""
status.since?.let { sections[PolySymbolsBundle.message("mdn.documentation.section.status.Since")] = it }
}
is PolySymbolApiStatus.Stable -> {
status.since?.let { sections[PolySymbolsBundle.message("mdn.documentation.section.status.Since")] = it }
}
}
}
defaultValue?.let { sections[PolySymbolsBundle.message("mdn.documentation.section.defaultValue")] = "<p><code>$it</code>" }
library?.let { sections[PolySymbolsBundle.message("mdn.documentation.section.library")] = "<p>$it" }
}
private fun StringBuilder.appendIcon(icon: Icon, url2ImageMap: MutableMap<String, Image>): StringBuilder {
// TODO adjust it to the actual component being used
@Suppress("UndesirableClassUsage")
val bufferedImage = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)
val g = bufferedImage.createGraphics()
g.font = UIUtil.getToolTipFont()
val height = (g.fontMetrics.getStringBounds("a", g).height / ScaleContext.create().getScale(ScaleType.USR_SCALE)).toInt()
g.dispose()
val image = try {
IconUtil.toBufferedImage(icon.scaleToHeight(height))
}
catch (e: Exception) {
// ignore
return this
}
val url = "https://img${url2ImageMap.size}"
url2ImageMap[url] = image
val screenHeight = height * ScaleContext.create().getScale(ScaleType.SYS_SCALE)
append("<img src='$url' height=\"$screenHeight\" width=\"${(screenHeight * icon.iconWidth) / icon.iconHeight}\" border=0 />")
return this
}
private val imgSrcRegex = Regex("<img [^>]*src\\s*=\\s*['\"]([^'\"]+)['\"]")
private fun String.loadLocalImages(origin: PolySymbolOrigin, url2ImageMap: MutableMap<String, Image>): String {
val replaces = imgSrcRegex.findAll(this)
.mapNotNull { it.groups[1] }
.filter { !it.value.contains(':') }
.mapNotNull { group ->
origin.loadIcon(group.value)
?.let { IconUtil.toBufferedImage(it, true) }
?.let {
val url = "https://img${url2ImageMap.size}"
url2ImageMap[url] = it
Pair(group.range, url)
}
}
.sortedBy { it.first.first }
.toList()
if (replaces.isEmpty()) return this
val result = StringBuilder()
var lastIndex = 0
for (replace in replaces) {
result.appendRange(this, lastIndex, replace.first.first)
result.append(replace.second)
lastIndex = replace.first.last + 1
}
if (lastIndex < this.length) {
result.appendRange(this, lastIndex, this.length)
}
return result.toString()
}
}

View File

@@ -18,13 +18,14 @@ import com.intellij.polySymbols.PolySymbolOrigin
import com.intellij.polySymbols.PolySymbolsBundle
import com.intellij.polySymbols.documentation.PolySymbolDocumentation
import com.intellij.polySymbols.documentation.PolySymbolDocumentationTarget
import com.intellij.polySymbols.documentation.PolySymbolWithDocumentation
import com.intellij.polySymbols.impl.scaleToHeight
import java.awt.Image
import java.awt.image.BufferedImage
import javax.swing.Icon
internal class PolySymbolDocumentationTargetImpl(
override val symbol: PolySymbol,
override val symbol: PolySymbolWithDocumentation,
override val location: PsiElement?,
)
: PolySymbolDocumentationTarget {
@@ -37,158 +38,9 @@ internal class PolySymbolDocumentationTargetImpl(
}
}
companion object {
fun buildDocumentation(origin: PolySymbolOrigin, doc: PolySymbolDocumentation): DocumentationResult {
val url2ImageMap = mutableMapOf<String, Image>()
@Suppress("HardCodedStringLiteral")
val contents = StringBuilder()
.appendHeader(doc)
.appendDefinition(doc, url2ImageMap)
.appendDescription(doc)
.appendSections(doc)
.appendFootnote(doc)
.toString()
.loadLocalImages(origin, url2ImageMap)
return DocumentationResult.documentation(contents).images(url2ImageMap).externalUrl(doc.docUrl)
.definitionDetails(doc.definitionDetails)
}
private fun StringBuilder.appendDefinition(doc: PolySymbolDocumentation, url2ImageMap: MutableMap<String, Image>): StringBuilder =
append(DocumentationMarkup.DEFINITION_START)
.also {
doc.icon?.let { appendIcon(it, url2ImageMap).append("&nbsp;&nbsp;") }
}
.append(doc.definition)
.append(DocumentationMarkup.DEFINITION_END)
.append('\n')
private fun StringBuilder.appendDescription(doc: PolySymbolDocumentation): StringBuilder =
doc.description?.let {
append(DocumentationMarkup.CONTENT_START).append('\n')
.append(it).append('\n')
.append(DocumentationMarkup.CONTENT_END)
}
?: this
private fun StringBuilder.appendSections(doc: PolySymbolDocumentation): StringBuilder =
buildSections(doc).let { sections ->
if (sections.isNotEmpty()) {
append(DocumentationMarkup.SECTIONS_START)
.append('\n')
sections.entries.forEach { (name, value) ->
append(DocumentationMarkup.SECTION_HEADER_START)
.append(StringUtil.capitalize(name))
if (value.isNotBlank()) {
if (!name.endsWith(":"))
append(':')
append(DocumentationMarkup.SECTION_SEPARATOR)
.append(value)
}
append(DocumentationMarkup.SECTION_END)
.append('\n')
}
append(DocumentationMarkup.SECTIONS_END)
.append('\n')
}
this
}
private fun StringBuilder.appendFootnote(doc: PolySymbolDocumentation): StringBuilder =
doc.footnote?.let {
append(DocumentationMarkup.CONTENT_START)
.append(it)
.append(DocumentationMarkup.CONTENT_END)
.append('\n')
} ?: this
private fun StringBuilder.appendHeader(doc: PolySymbolDocumentation): StringBuilder =
doc.header?.let {
append("<div class='" + DocumentationMarkup.CLASS_TOP + "'>")
.append(it)
.append("</div>\n")
} ?: this
private fun buildSections(doc: PolySymbolDocumentation): Map<String, String> =
LinkedHashMap(doc.descriptionSections).also { sections ->
if (doc.required) sections[PolySymbolsBundle.message("mdn.documentation.section.isRequired")] = ""
doc.apiStatus?.let { status ->
when (status) {
is PolySymbolApiStatus.Deprecated -> {
sections[PolySymbolsBundle.message("mdn.documentation.section.status.Deprecated")] = status.message ?: ""
status.since?.let { sections[PolySymbolsBundle.message("mdn.documentation.section.status.DeprecatedSince")] = it }
}
is PolySymbolApiStatus.Obsolete -> {
sections[PolySymbolsBundle.message("mdn.documentation.section.status.Obsolete")] = status.message ?: ""
status.since?.let { sections[PolySymbolsBundle.message("mdn.documentation.section.status.ObsoleteSince")] = it }
}
is PolySymbolApiStatus.Experimental -> {
sections[PolySymbolsBundle.message("mdn.documentation.section.status.Experimental")] = status.message ?: ""
status.since?.let { sections[PolySymbolsBundle.message("mdn.documentation.section.status.Since")] = it }
}
is PolySymbolApiStatus.Stable -> {
status.since?.let { sections[PolySymbolsBundle.message("mdn.documentation.section.status.Since")] = it }
}
}
}
doc.defaultValue?.let { sections[PolySymbolsBundle.message("mdn.documentation.section.defaultValue")] = "<p><code>$it</code>" }
doc.library?.let { sections[PolySymbolsBundle.message("mdn.documentation.section.library")] = "<p>$it" }
}
private fun StringBuilder.appendIcon(icon: Icon, url2ImageMap: MutableMap<String, Image>): StringBuilder {
// TODO adjust it to the actual component being used
@Suppress("UndesirableClassUsage")
val bufferedImage = BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)
val g = bufferedImage.createGraphics()
g.font = UIUtil.getToolTipFont()
val height = (g.fontMetrics.getStringBounds("a", g).height / ScaleContext.create().getScale(ScaleType.USR_SCALE)).toInt()
g.dispose()
val image = try {
IconUtil.toBufferedImage(icon.scaleToHeight(height))
}
catch (e: Exception) {
// ignore
return this
}
val url = "https://img${url2ImageMap.size}"
url2ImageMap[url] = image
val screenHeight = height * ScaleContext.create().getScale(ScaleType.SYS_SCALE)
append("<img src='$url' height=\"$screenHeight\" width=\"${(screenHeight * icon.iconWidth) / icon.iconHeight}\" border=0 />")
return this
}
private val imgSrcRegex = Regex("<img [^>]*src\\s*=\\s*['\"]([^'\"]+)['\"]")
private fun String.loadLocalImages(origin: PolySymbolOrigin, url2ImageMap: MutableMap<String, Image>): String {
val replaces = imgSrcRegex.findAll(this)
.mapNotNull { it.groups[1] }
.filter { !it.value.contains(':') }
.mapNotNull { group ->
origin.loadIcon(group.value)
?.let { IconUtil.toBufferedImage(it, true) }
?.let {
val url = "https://img${url2ImageMap.size}"
url2ImageMap[url] = it
Pair(group.range, url)
}
}
.sortedBy { it.first.first }
.toList()
if (replaces.isEmpty()) return this
val result = StringBuilder()
var lastIndex = 0
for (replace in replaces) {
result.appendRange(this, lastIndex, replace.first.first)
result.append(replace.second)
lastIndex = replace.first.last + 1
}
if (lastIndex < this.length) {
result.appendRange(this, lastIndex, this.length)
}
return result.toString()
}
}
override fun computeDocumentation(): DocumentationResult? =
symbol.createDocumentation(location)
?.takeIf { it.isNotEmpty() }
?.build(symbol.origin)
}

View File

@@ -3,7 +3,7 @@ package com.intellij.polySymbols.query
import com.intellij.openapi.util.NlsSafe
import com.intellij.polySymbols.*
import com.intellij.polySymbols.query.impl.PolySymbolMatchImpl
import com.intellij.polySymbols.query.impl.PolySymbolMatchBase
import org.jetbrains.annotations.ApiStatus
@ApiStatus.NonExtendable
@@ -26,7 +26,7 @@ interface PolySymbolMatch : CompositePolySymbol {
explicitPriority: PolySymbol.Priority? = null,
explicitProximity: Int? = null,
): PolySymbolMatch =
PolySymbolMatchImpl.BuilderImpl(matchedName, qualifiedKind, origin)
PolySymbolMatchBase.BuilderImpl(matchedName, qualifiedKind, origin)
.also { builder ->
builder.addNameSegments(nameSegments)
explicitProximity?.let { builder.explicitProximity(it) }
@@ -41,7 +41,7 @@ interface PolySymbolMatch : CompositePolySymbol {
origin: PolySymbolOrigin,
builder: (PolySymbolMatchBuilder.() -> Unit),
): PolySymbolMatch =
PolySymbolMatchImpl.BuilderImpl(matchedName, qualifiedKind, origin)
PolySymbolMatchBase.BuilderImpl(matchedName, qualifiedKind, origin)
.also { builder.invoke(it) }
.build()
@@ -52,7 +52,7 @@ interface PolySymbolMatch : CompositePolySymbol {
origin: PolySymbolOrigin,
vararg nameSegments: PolySymbolNameSegment,
): PolySymbolMatch =
PolySymbolMatchImpl.BuilderImpl(matchedName, qualifiedKind, origin)
PolySymbolMatchBase.BuilderImpl(matchedName, qualifiedKind, origin)
.also { it.addNameSegments(*nameSegments) }
.build()

View File

@@ -0,0 +1,433 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.polySymbols.query.impl
import com.intellij.model.Pointer
import com.intellij.model.Symbol
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Ref
import com.intellij.platform.backend.documentation.DocumentationTarget
import com.intellij.platform.backend.navigation.NavigationTarget
import com.intellij.polySymbols.*
import com.intellij.polySymbols.PolySymbol.Priority
import com.intellij.polySymbols.documentation.PolySymbolDocumentation
import com.intellij.polySymbols.documentation.PolySymbolWithDocumentation
import com.intellij.polySymbols.documentation.impl.PolySymbolDocumentationTargetImpl
import com.intellij.polySymbols.html.PolySymbolHtmlAttributeValue
import com.intellij.polySymbols.query.PolySymbolMatch
import com.intellij.polySymbols.query.PolySymbolMatchBuilder
import com.intellij.polySymbols.refactoring.PolySymbolRenameTarget
import com.intellij.polySymbols.search.PolySymbolSearchTarget
import com.intellij.polySymbols.search.PsiSourcedPolySymbol
import com.intellij.polySymbols.utils.coalesceApiStatus
import com.intellij.polySymbols.utils.merge
import com.intellij.psi.PsiElement
import com.intellij.psi.createSmartPointer
import javax.swing.Icon
internal open class PolySymbolMatchBase internal constructor(
override val matchedName: String,
override val nameSegments: List<PolySymbolNameSegment>,
override val qualifiedKind: PolySymbolQualifiedKind,
override val origin: PolySymbolOrigin,
override val explicitPriority: Priority?,
override val explicitProximity: Int?,
override val additionalProperties: Map<String, Any>,
) : PolySymbolMatchMixin {
init {
require(nameSegments.isNotEmpty()) { "nameSegments must not be empty" }
}
internal fun withSegments(segments: List<PolySymbolNameSegment>): PolySymbolMatch =
create(matchedName, segments, qualifiedKind, origin, explicitPriority, explicitProximity, additionalProperties)
override fun equals(other: Any?): Boolean =
other is PolySymbolMatch
&& other.name == name
&& other.origin == origin
&& other.qualifiedKind == qualifiedKind
&& other.nameSegments.equalsIgnoreOffset(nameSegments)
override fun hashCode(): Int = name.hashCode()
override fun createPointer(): Pointer<out PolySymbolMatchBase> =
PolySymbolMatchPointer<PolySymbolMatchBase>(this, ::PolySymbolMatchBase)
class BuilderImpl(
private var matchedName: String,
private var qualifiedKind: PolySymbolQualifiedKind,
private var origin: PolySymbolOrigin,
) : PolySymbolMatchBuilder {
private var nameSegments = mutableListOf<PolySymbolNameSegment>()
private var properties = mutableMapOf<String, Any>()
private var explicitPriority: Priority? = null
private var explicitProximity: Int? = null
fun build(): PolySymbolMatch =
create(matchedName, nameSegments, qualifiedKind,
origin, explicitPriority, explicitProximity, properties)
override fun addNameSegments(value: List<PolySymbolNameSegment>): PolySymbolMatchBuilder = this.also {
nameSegments.addAll(value)
}
override fun addNameSegments(vararg value: PolySymbolNameSegment): PolySymbolMatchBuilder = this.also {
nameSegments.addAll(value)
}
override fun addNameSegment(value: PolySymbolNameSegment): PolySymbolMatchBuilder = this.also {
nameSegments.add(value)
}
override fun explicitPriority(value: Priority): PolySymbolMatchBuilder = this.also {
explicitPriority = value
}
override fun explicitProximity(value: Int): PolySymbolMatchBuilder = this.also {
explicitProximity = value
}
override fun setProperty(name: String, value: Any): PolySymbolMatchBuilder = this.also {
properties[name] = value
}
}
}
private class PsiSourcedPolySymbolMatch(
matchedName: String,
nameSegments: List<PolySymbolNameSegment>,
qualifiedKind: PolySymbolQualifiedKind,
origin: PolySymbolOrigin,
explicitPriority: Priority?,
explicitProximity: Int?,
additionalProperties: Map<String, Any>,
) : PolySymbolMatchBase(matchedName, nameSegments, qualifiedKind, origin, explicitPriority, explicitProximity, additionalProperties),
PsiSourcedPolySymbolMatchMixin {
override fun createPointer(): Pointer<PsiSourcedPolySymbolMatch> =
PolySymbolMatchPointer<PsiSourcedPolySymbolMatch>(this, ::PsiSourcedPolySymbolMatch)
}
private class PsiSourcedPolySymbolMatchWithDocumentation(
matchedName: String,
nameSegments: List<PolySymbolNameSegment>,
qualifiedKind: PolySymbolQualifiedKind,
origin: PolySymbolOrigin,
explicitPriority: Priority?,
explicitProximity: Int?,
additionalProperties: Map<String, Any>,
) : PolySymbolMatchBase(matchedName, nameSegments, qualifiedKind, origin, explicitPriority, explicitProximity, additionalProperties),
PsiSourcedPolySymbolMatchMixin, PolySymbolMatchWithDocumentationMixin {
override fun createPointer(): Pointer<PsiSourcedPolySymbolMatchWithDocumentation> =
PolySymbolMatchPointer<PsiSourcedPolySymbolMatchWithDocumentation>(this, ::PsiSourcedPolySymbolMatchWithDocumentation)
}
private class PolySymbolMatchWithDocumentation(
matchedName: String,
nameSegments: List<PolySymbolNameSegment>,
qualifiedKind: PolySymbolQualifiedKind,
origin: PolySymbolOrigin,
explicitPriority: Priority?,
explicitProximity: Int?,
additionalProperties: Map<String, Any>,
) : PolySymbolMatchBase(matchedName, nameSegments, qualifiedKind, origin, explicitPriority, explicitProximity, additionalProperties),
PolySymbolMatchWithDocumentationMixin {
override fun createPointer(): Pointer<PolySymbolMatchWithDocumentation> =
PolySymbolMatchPointer<PolySymbolMatchWithDocumentation>(this, ::PolySymbolMatchWithDocumentation)
}
private fun create(
matchedName: String,
nameSegments: List<PolySymbolNameSegment>,
qualifiedKind: PolySymbolQualifiedKind,
origin: PolySymbolOrigin,
explicitPriority: Priority?,
explicitProximity: Int?,
additionalProperties: Map<String, Any>,
): PolySymbolMatch {
val psiSourcedMixin = nameSegments.all { it.start == it.end || (it.symbols.isNotEmpty() && it.symbols.any { symbol -> symbol is PsiSourcedPolySymbol }) }
val withDocumentationMixin = nameSegments.any { it.symbols.any { symbol -> symbol is PolySymbolWithDocumentation } }
return if (psiSourcedMixin) {
if (withDocumentationMixin) {
PsiSourcedPolySymbolMatchWithDocumentation(matchedName, nameSegments, qualifiedKind, origin,
explicitPriority, explicitProximity, additionalProperties)
}
else {
PsiSourcedPolySymbolMatch(matchedName, nameSegments, qualifiedKind, origin,
explicitPriority, explicitProximity, additionalProperties)
}
}
else {
if (withDocumentationMixin) {
PolySymbolMatchWithDocumentation(matchedName, nameSegments, qualifiedKind, origin,
explicitPriority, explicitProximity, additionalProperties)
}
else {
PolySymbolMatchBase(matchedName, nameSegments, qualifiedKind, origin,
explicitPriority, explicitProximity, additionalProperties)
}
}
}
private interface PolySymbolMatchMixin : PolySymbolMatch {
val explicitPriority: Priority?
val explicitProximity: Int?
val additionalProperties: Map<String, Any>
fun reversedSegments() = Sequence { ReverseListIterator(nameSegments) }
override fun withCustomProperties(properties: Map<String, Any>): PolySymbolMatch =
create(matchedName, nameSegments, qualifiedKind, origin, explicitPriority, explicitProximity, additionalProperties + properties)
override val psiContext: PsiElement?
get() = reversedSegments().flatMap { it.symbols.asSequence() }
.mapNotNull { it.psiContext }.firstOrNull()
override val name: String
get() = matchedName.substring(nameSegments.firstOrNull()?.start ?: 0,
nameSegments.lastOrNull()?.end ?: 0)
override val virtual: Boolean
get() = nameSegments.any { segment -> segment.symbols.any { it.virtual } }
override val extension: Boolean
get() = nameSegments.isNotEmpty() && nameSegments.all { segment -> segment.symbols.isNotEmpty() && segment.symbols.all { it.extension } }
override val priority: Priority?
get() = explicitPriority ?: reversedSegments().mapNotNull { it.priority }.firstOrNull()
override val proximity: Int?
get() = explicitProximity ?: reversedSegments().mapNotNull { it.proximity }.firstOrNull()
override val queryScope: List<PolySymbolsScope>
get() = nameSegments.asSequence()
.flatMap { it.symbols }
.flatMap { it.queryScope }
.toList()
override val type: Any?
get() = reversedSegments().flatMap { it.symbols }
.mapNotNull { it.type }.firstOrNull()
override val attributeValue: PolySymbolHtmlAttributeValue?
get() = reversedSegments().flatMap { it.symbols }.mapNotNull { it.attributeValue }.merge()
override val required: Boolean?
get() = reversedSegments().flatMap { it.symbols }.mapNotNull { it.required }.firstOrNull()
override val apiStatus: PolySymbolApiStatus
get() = coalesceApiStatus(reversedSegments().flatMap { it.symbols }) { it.apiStatus }
override val icon: Icon?
get() = reversedSegments().flatMap { it.symbols }.mapNotNull { it.icon }.firstOrNull()
override val properties: Map<String, Any>
get() = nameSegments.asSequence().flatMap { it.symbols }
.flatMap { it.properties.entries }
.filter { it.key != PolySymbol.PROP_HIDE_FROM_COMPLETION }
.plus(additionalProperties.entries)
.map { Pair(it.key, it.value) }
.toMap()
override fun getNavigationTargets(project: Project): Collection<NavigationTarget> =
if (nameSegments.size == 1)
nameSegments[0].symbols.asSequence()
.flatMap { it.getNavigationTargets(project) }
.distinct()
.toList()
else emptyList()
override fun getDocumentationTarget(location: PsiElement?): DocumentationTarget? =
reversedSegments()
.flatMap { it.symbols.asSequence() }
.map {
if (it === this) null
else it.getDocumentationTarget(location)
}
.filter { it !is PolySymbolDocumentationTargetImpl || it.symbol.createDocumentation(location)?.isNotEmpty() == true }
.firstOrNull()
?: if (this is PolySymbolWithDocumentation) PolySymbolDocumentationTargetImpl(this, location) else null
override fun isEquivalentTo(symbol: Symbol): Boolean =
super<PolySymbolMatch>.isEquivalentTo(symbol)
|| nameSegments.filter { it.start != it.end }
.let { nonEmptySegments ->
nonEmptySegments.size == 1
&& nonEmptySegments[0].symbols.any { it.isEquivalentTo(symbol) }
}
override val searchTarget: PolySymbolSearchTarget?
get() = nameSegments.filter { it.start != it.end }
.takeIf { it.size == 1 }
?.get(0)
?.symbols
?.takeIf { it.size == 1 }
?.get(0)
?.searchTarget
override val renameTarget: PolySymbolRenameTarget?
get() = nameSegments.filter { it.start != it.end }
.takeIf { it.size == 1 }
?.get(0)
?.symbols
?.takeIf { it.size == 1 }
?.get(0)
?.renameTarget
}
private interface PolySymbolMatchWithDocumentationMixin : PolySymbolMatchMixin, PolySymbolWithDocumentation {
override fun getDocumentationTarget(location: PsiElement?): DocumentationTarget? =
super<PolySymbolMatchMixin>.getDocumentationTarget(location)
override fun createDocumentation(location: PsiElement?): PolySymbolDocumentation? =
reversedSegments().flatMap { it.symbols.asSequence() }
.firstNotNullOfOrNull { (it as? PolySymbolWithDocumentation)?.createDocumentation(location) }
override val description: String?
get() = nameSegments.takeIf { it.size == 1 }
?.get(0)?.symbols?.asSequence()?.mapNotNull { (it as? PolySymbolWithDocumentation)?.description }?.firstOrNull()
override val docUrl: String?
get() = nameSegments.takeIf { it.size == 1 }
?.get(0)?.symbols?.asSequence()?.mapNotNull { (it as? PolySymbolWithDocumentation)?.docUrl }?.firstOrNull()
override val descriptionSections: Map<String, String>
get() = nameSegments.takeIf { it.size == 1 }
?.get(0)?.symbols?.asSequence()
?.flatMap { (it as? PolySymbolWithDocumentation)?.descriptionSections?.asSequence() ?: emptySequence() }
?.distinct()
?.associateBy({ it.key }, { it.value })
?: emptyMap()
}
private interface PsiSourcedPolySymbolMatchMixin : PolySymbolMatchMixin, PsiSourcedPolySymbol {
override val psiContext: PsiElement?
get() = reversedSegments().flatMap { it.symbols.asSequence() }
.mapNotNull { it.psiContext }.firstOrNull()
override val source: PsiElement?
get() = reversedSegments().flatMap { it.symbols }
.mapNotNull { (it as? PsiSourcedPolySymbol)?.source }.singleOrNull()
override fun getNavigationTargets(project: Project): Collection<NavigationTarget> =
super<PolySymbolMatchMixin>.getNavigationTargets(project)
override fun isEquivalentTo(symbol: Symbol): Boolean =
super<PolySymbolMatchMixin>.isEquivalentTo(symbol)
}
private fun List<PolySymbolNameSegment>.equalsIgnoreOffset(other: List<PolySymbolNameSegment>): Boolean {
if (size != other.size) return false
if (this.isEmpty()) return true
val startOffset1 = this[0].start
val startOffset2 = other[0].start
for (i in indices) {
val segment1 = this[i]
val segment2 = other[i]
if (segment1.start - startOffset1 != segment2.start - startOffset2
|| segment1.end - startOffset1 != segment2.end - startOffset2
|| segment1.apiStatus != segment2.apiStatus
|| segment1.symbols != segment2.symbols
|| segment1.problem != segment2.problem
|| segment1.displayName != segment2.displayName
|| segment1.priority != segment2.priority
|| segment1.proximity != segment2.proximity) {
return false
}
}
return true
}
private class ReverseListIterator<T>(list: List<T>) : Iterator<T> {
private val iterator = list.listIterator(list.size)
override operator fun hasNext(): Boolean {
return iterator.hasPrevious()
}
override operator fun next(): T {
return iterator.previous()
}
}
private class PolySymbolMatchPointer<T : PolySymbolMatch>(
polySymbolMatch: PolySymbolMatchBase,
private val newInstanceProvider: (
matchedName: String,
nameSegments: List<PolySymbolNameSegment>,
qualifiedKind: PolySymbolQualifiedKind,
origin: PolySymbolOrigin,
explicitPriority: Priority?,
explicitProximity: Int?,
additionalProperties: Map<String, Any>,
) -> T,
) : Pointer<T> {
private val matchedName = polySymbolMatch.matchedName
private val nameSegments = polySymbolMatch.nameSegments
.map { it.createPointer() }
private val qualifiedKind = polySymbolMatch.qualifiedKind
private val origin = polySymbolMatch.origin
private val explicitPriority = polySymbolMatch.explicitPriority
private val explicitProximity = polySymbolMatch.explicitProximity
private val additionalProperties = polySymbolMatch.additionalProperties
.createPointers()
override fun dereference(): T? =
nameSegments.map { it.dereference() }
.takeIf { it.all { segment -> segment != null } }
?.let {
val dereferencingProblems = Ref(false)
val dereferencedProperties = additionalProperties.dereferencePointers(dereferencingProblems)
if (dereferencingProblems.get()) return null
@Suppress("UNCHECKED_CAST")
newInstanceProvider(matchedName, it as List<PolySymbolNameSegment>, qualifiedKind, origin,
explicitPriority, explicitProximity, dereferencedProperties)
}
private fun Map<String, Any>.createPointers(): Map<String, Any> =
mapValues { (_, value) -> value.createPointers() }
private fun Any.createPointers(): Any =
when (this) {
is Symbol -> createPointer()
is PsiElement -> createSmartPointer()
is Map<*, *> -> mapValues { (_, value) -> value?.createPointers() }
is List<*> -> map { it?.createPointers() }
is Set<*> -> mapTo(HashSet()) { it?.createPointers() }
else -> this
}
private fun Map<String, Any>.dereferencePointers(anyProblems: Ref<Boolean>): Map<String, Any> =
mapValues { (_, value) -> value.dereferencePointers(anyProblems) }
private fun Any.dereferencePointers(anyProblems: Ref<Boolean>): Any =
when (this) {
is Pointer<*> -> dereference().also { if (it == null) anyProblems.set(true) } ?: this
is Map<*, *> -> mapValues { (_, value) -> value?.dereferencePointers(anyProblems) }
is List<*> -> map { it?.dereferencePointers(anyProblems) }
is Set<*> -> mapTo(HashSet()) { it?.dereferencePointers(anyProblems) }
else -> this
}
}

View File

@@ -1,363 +0,0 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.polySymbols.query.impl
import com.intellij.model.Pointer
import com.intellij.model.Symbol
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Ref
import com.intellij.platform.backend.documentation.DocumentationTarget
import com.intellij.platform.backend.navigation.NavigationTarget
import com.intellij.polySymbols.*
import com.intellij.polySymbols.PolySymbol.Priority
import com.intellij.polySymbols.documentation.PolySymbolDocumentation
import com.intellij.polySymbols.documentation.PolySymbolDocumentationTarget
import com.intellij.polySymbols.html.PolySymbolHtmlAttributeValue
import com.intellij.polySymbols.query.PolySymbolMatch
import com.intellij.polySymbols.query.PolySymbolMatchBuilder
import com.intellij.polySymbols.refactoring.PolySymbolRenameTarget
import com.intellij.polySymbols.search.PolySymbolSearchTarget
import com.intellij.polySymbols.search.PsiSourcedPolySymbol
import com.intellij.polySymbols.utils.coalesceApiStatus
import com.intellij.polySymbols.utils.merge
import com.intellij.psi.PsiElement
import com.intellij.psi.createSmartPointer
import javax.swing.Icon
internal open class PolySymbolMatchImpl private constructor(
override val matchedName: String,
override val nameSegments: List<PolySymbolNameSegment>,
override val qualifiedKind: PolySymbolQualifiedKind,
override val origin: PolySymbolOrigin,
private val explicitPriority: Priority?,
private val explicitProximity: Int?,
private val additionalProperties: Map<String, Any>,
) : PolySymbolMatch {
init {
require(nameSegments.isNotEmpty()) { "nameSegments must not be empty" }
}
protected fun reversedSegments() = Sequence { ReverseListIterator(nameSegments) }
override fun withCustomProperties(properties: Map<String, Any>): PolySymbolMatch =
create(matchedName, nameSegments, qualifiedKind, origin, explicitPriority, explicitProximity, additionalProperties + properties)
override val psiContext: PsiElement?
get() = reversedSegments().flatMap { it.symbols.asSequence() }
.mapNotNull { it.psiContext }.firstOrNull()
override fun createDocumentation(location: PsiElement?): PolySymbolDocumentation? =
reversedSegments().flatMap { it.symbols.asSequence() }
.firstNotNullOfOrNull { it.createDocumentation(location) }
override val name: String
get() = matchedName.substring(nameSegments.firstOrNull()?.start ?: 0,
nameSegments.lastOrNull()?.end ?: 0)
override val description: String?
get() = nameSegments.takeIf { it.size == 1 }
?.get(0)?.symbols?.asSequence()?.map { it.description }?.firstOrNull()
override val docUrl: String?
get() = nameSegments.takeIf { it.size == 1 }
?.get(0)?.symbols?.asSequence()?.map { it.docUrl }?.firstOrNull()
override val descriptionSections: Map<String, String>
get() = nameSegments.takeIf { it.size == 1 }
?.get(0)?.symbols?.asSequence()
?.flatMap { it.descriptionSections.asSequence() }
?.distinct()
?.associateBy({ it.key }, { it.value })
?: emptyMap()
override val virtual: Boolean
get() = nameSegments.any { segment -> segment.symbols.any { it.virtual } }
override val extension: Boolean
get() = nameSegments.isNotEmpty() && nameSegments.all { segment -> segment.symbols.isNotEmpty() && segment.symbols.all { it.extension } }
override val priority: Priority?
get() = explicitPriority ?: reversedSegments().mapNotNull { it.priority }.firstOrNull()
override val proximity: Int?
get() = explicitProximity ?: reversedSegments().mapNotNull { it.proximity }.firstOrNull()
override val queryScope: List<PolySymbolsScope>
get() = nameSegments.asSequence()
.flatMap { it.symbols }
.flatMap { it.queryScope }
.toList()
override val type: Any?
get() = reversedSegments().flatMap { it.symbols }
.mapNotNull { it.type }.firstOrNull()
override val attributeValue: PolySymbolHtmlAttributeValue?
get() = reversedSegments().flatMap { it.symbols }.mapNotNull { it.attributeValue }.merge()
override val required: Boolean?
get() = reversedSegments().flatMap { it.symbols }.mapNotNull { it.required }.firstOrNull()
override val apiStatus: PolySymbolApiStatus
get() = coalesceApiStatus(reversedSegments().flatMap { it.symbols }) { it.apiStatus }
override val icon: Icon?
get() = reversedSegments().flatMap { it.symbols }.mapNotNull { it.icon }.firstOrNull()
override val properties: Map<String, Any>
get() = nameSegments.asSequence().flatMap { it.symbols }
.flatMap { it.properties.entries }
.filter { it.key != PolySymbol.PROP_HIDE_FROM_COMPLETION }
.plus(additionalProperties.entries)
.map { Pair(it.key, it.value) }
.toMap()
override fun createPointer(): Pointer<out PolySymbolMatchImpl> =
PolySymbolMatchPointer<PolySymbolMatchImpl>(this, ::PolySymbolMatchImpl)
override fun getNavigationTargets(project: Project): Collection<NavigationTarget> =
if (nameSegments.size == 1)
nameSegments[0].symbols.asSequence()
.flatMap { it.getNavigationTargets(project) }
.distinct()
.toList()
else emptyList()
override fun getDocumentationTarget(location: PsiElement?): DocumentationTarget? =
reversedSegments()
.flatMap { it.symbols.asSequence() }
.map {
if (it === this) super<PolySymbolMatch>.getDocumentationTarget(location)
else it.getDocumentationTarget(location)
}
.filter { it !is PolySymbolDocumentationTarget || it.symbol.createDocumentation(location)?.isNotEmpty() == true }
.firstOrNull()
?: super<PolySymbolMatch>.getDocumentationTarget(location)
override fun isEquivalentTo(symbol: Symbol): Boolean =
super<PolySymbolMatch>.isEquivalentTo(symbol)
|| nameSegments.filter { it.start != it.end }
.let { nonEmptySegments ->
nonEmptySegments.size == 1
&& nonEmptySegments[0].symbols.any { it.isEquivalentTo(symbol) }
}
override val searchTarget: PolySymbolSearchTarget?
get() = nameSegments.filter { it.start != it.end }
.takeIf { it.size == 1 }
?.get(0)
?.symbols
?.takeIf { it.size == 1 }
?.get(0)
?.searchTarget
override val renameTarget: PolySymbolRenameTarget?
get() = nameSegments.filter { it.start != it.end }
.takeIf { it.size == 1 }
?.get(0)
?.symbols
?.takeIf { it.size == 1 }
?.get(0)
?.renameTarget
override fun equals(other: Any?): Boolean =
other is PolySymbolMatch
&& other.name == name
&& other.origin == origin
&& other.qualifiedKind == qualifiedKind
&& other.nameSegments.equalsIgnoreOffset(nameSegments)
override fun hashCode(): Int = name.hashCode()
internal fun withSegments(segments: List<PolySymbolNameSegment>): PolySymbolMatch =
create(matchedName, segments, qualifiedKind, origin, explicitPriority, explicitProximity, additionalProperties)
class ReverseListIterator<T>(list: List<T>) : Iterator<T> {
private val iterator = list.listIterator(list.size)
override operator fun hasNext(): Boolean {
return iterator.hasPrevious()
}
override operator fun next(): T {
return iterator.previous()
}
}
companion object {
private fun create(
matchedName: String,
nameSegments: List<PolySymbolNameSegment>,
qualifiedKind: PolySymbolQualifiedKind,
origin: PolySymbolOrigin,
explicitPriority: Priority?,
explicitProximity: Int?,
additionalProperties: Map<String, Any>,
): PolySymbolMatch =
if (nameSegments.all { it.start == it.end || (it.symbols.isNotEmpty() && it.symbols.any { symbol -> symbol is PsiSourcedPolySymbol }) })
PsiSourcedPolySymbolMatch(matchedName, nameSegments, qualifiedKind, origin,
explicitPriority, explicitProximity, additionalProperties)
else PolySymbolMatchImpl(matchedName, nameSegments, qualifiedKind, origin,
explicitPriority, explicitProximity, additionalProperties)
private fun List<PolySymbolNameSegment>.equalsIgnoreOffset(other: List<PolySymbolNameSegment>): Boolean {
if (size != other.size) return false
if (this.isEmpty()) return true
val startOffset1 = this[0].start
val startOffset2 = other[0].start
for (i in indices) {
val segment1 = this[i]
val segment2 = other[i]
if (segment1.start - startOffset1 != segment2.start - startOffset2
|| segment1.end - startOffset1 != segment2.end - startOffset2
|| segment1.apiStatus != segment2.apiStatus
|| segment1.symbols != segment2.symbols
|| segment1.problem != segment2.problem
|| segment1.displayName != segment2.displayName
|| segment1.priority != segment2.priority
|| segment1.proximity != segment2.proximity) {
return false
}
}
return true
}
}
private class PsiSourcedPolySymbolMatch(
matchedName: String,
nameSegments: List<PolySymbolNameSegment>,
qualifiedKind: PolySymbolQualifiedKind,
origin: PolySymbolOrigin,
explicitPriority: Priority?,
explicitProximity: Int?,
additionalProperties: Map<String, Any>,
) : PolySymbolMatchImpl(matchedName, nameSegments, qualifiedKind, origin, explicitPriority, explicitProximity, additionalProperties),
PsiSourcedPolySymbol {
override val psiContext: PsiElement?
get() = reversedSegments().flatMap { it.symbols.asSequence() }
.mapNotNull { it.psiContext }.firstOrNull()
override val source: PsiElement?
get() = reversedSegments().flatMap { it.symbols }
.mapNotNull { (it as? PsiSourcedPolySymbol)?.source }.singleOrNull()
override fun createPointer(): Pointer<PsiSourcedPolySymbolMatch> =
PolySymbolMatchPointer<PsiSourcedPolySymbolMatch>(this, ::PsiSourcedPolySymbolMatch)
override fun getNavigationTargets(project: Project): Collection<NavigationTarget> =
super<PolySymbolMatchImpl>.getNavigationTargets(project)
override fun isEquivalentTo(symbol: Symbol): Boolean =
super<PolySymbolMatchImpl>.isEquivalentTo(symbol)
}
class BuilderImpl(
private var matchedName: String,
private var qualifiedKind: PolySymbolQualifiedKind,
private var origin: PolySymbolOrigin,
) : PolySymbolMatchBuilder {
private var nameSegments = mutableListOf<PolySymbolNameSegment>()
private var properties = mutableMapOf<String, Any>()
private var explicitPriority: Priority? = null
private var explicitProximity: Int? = null
fun build(): PolySymbolMatch =
create(matchedName, nameSegments, qualifiedKind,
origin, explicitPriority, explicitProximity, properties)
override fun addNameSegments(value: List<PolySymbolNameSegment>): PolySymbolMatchBuilder = this.also {
nameSegments.addAll(value)
}
override fun addNameSegments(vararg value: PolySymbolNameSegment): PolySymbolMatchBuilder = this.also {
nameSegments.addAll(value)
}
override fun addNameSegment(value: PolySymbolNameSegment): PolySymbolMatchBuilder = this.also {
nameSegments.add(value)
}
override fun explicitPriority(value: Priority): PolySymbolMatchBuilder = this.also {
explicitPriority = value
}
override fun explicitProximity(value: Int): PolySymbolMatchBuilder = this.also {
explicitProximity = value
}
override fun setProperty(name: String, value: Any): PolySymbolMatchBuilder = this.also {
properties[name] = value
}
}
private class PolySymbolMatchPointer<T : PolySymbolMatch>(
polySymbolMatch: PolySymbolMatchImpl,
private val newInstanceProvider: (
matchedName: String,
nameSegments: List<PolySymbolNameSegment>,
qualifiedKind: PolySymbolQualifiedKind,
origin: PolySymbolOrigin,
explicitPriority: Priority?,
explicitProximity: Int?,
additionalProperties: Map<String, Any>,
) -> T,
) : Pointer<T> {
private val matchedName = polySymbolMatch.matchedName
private val nameSegments = polySymbolMatch.nameSegments
.map { it.createPointer() }
private val qualifiedKind = polySymbolMatch.qualifiedKind
private val origin = polySymbolMatch.origin
private val explicitPriority = polySymbolMatch.explicitPriority
private val explicitProximity = polySymbolMatch.explicitProximity
private val additionalProperties = polySymbolMatch.additionalProperties
.createPointers()
override fun dereference(): T? =
nameSegments.map { it.dereference() }
.takeIf { it.all { segment -> segment != null } }
?.let {
var dereferencingProblems = Ref(false)
val dereferencedProperties = additionalProperties.dereferencePointers(dereferencingProblems)
if (dereferencingProblems.get()) return null
@Suppress("UNCHECKED_CAST")
newInstanceProvider(matchedName, it as List<PolySymbolNameSegment>, qualifiedKind, origin,
explicitPriority, explicitProximity, dereferencedProperties)
}
private fun Map<String, Any>.createPointers(): Map<String, Any> =
mapValues { (_, value) -> value.createPointers() }
private fun Any.createPointers(): Any =
when (this) {
is Symbol -> createPointer()
is PsiElement -> createSmartPointer()
is Map<*, *> -> mapValues { (_, value) -> value?.createPointers() }
is List<*> -> map { it?.createPointers() }
is Set<*> -> mapTo(HashSet()) { it?.createPointers() }
else -> this
}
private fun Map<String, Any>.dereferencePointers(anyProblems: Ref<Boolean>): Map<String, Any> =
mapValues { (_, value) -> value.dereferencePointers(anyProblems) }
private fun Any.dereferencePointers(anyProblems: Ref<Boolean>): Any =
when (this) {
is Pointer<*> -> dereference().also { if (it == null) anyProblems.set(true) } ?: this
is Map<*, *> -> mapValues { (_, value) -> value?.dereferencePointers(anyProblems) }
is List<*> -> map { it?.dereferencePointers(anyProblems) }
is Set<*> -> mapTo(HashSet()) { it?.dereferencePointers(anyProblems) }
else -> this
}
}
}

View File

@@ -28,7 +28,9 @@ import com.intellij.refactoring.rename.api.RenameTarget
import com.intellij.util.containers.Stack
import javax.swing.Icon
abstract class PolySymbolDelegate<T : PolySymbol>(val delegate: T) : PolySymbol {
interface PolySymbolDelegate<T : PolySymbol> : PolySymbol {
val delegate: T
override val psiContext: PsiElement?
get() = delegate.psiContext
@@ -44,12 +46,6 @@ abstract class PolySymbolDelegate<T : PolySymbol>(val delegate: T) : PolySymbol
get() = delegate.queryScope
override val name: String
get() = delegate.name
override val description: String?
get() = delegate.description
override val descriptionSections: Map<String, String>
get() = delegate.descriptionSections
override val docUrl: String?
get() = delegate.docUrl
override val icon: Icon?
get() = delegate.icon
override val apiStatus: PolySymbolApiStatus
@@ -62,8 +58,6 @@ abstract class PolySymbolDelegate<T : PolySymbol>(val delegate: T) : PolySymbol
get() = delegate.extension
override val required: Boolean?
get() = delegate.required
override val defaultValue: String?
get() = delegate.defaultValue
override val priority: PolySymbol.Priority?
get() = delegate.priority
override val proximity: Int?
@@ -77,9 +71,6 @@ abstract class PolySymbolDelegate<T : PolySymbol>(val delegate: T) : PolySymbol
override val properties: Map<String, Any>
get() = delegate.properties
override fun createDocumentation(location: PsiElement?): PolySymbolDocumentation? =
delegate.createDocumentation(location)
override fun getDocumentationTarget(location: PsiElement?): DocumentationTarget? =
delegate.getDocumentationTarget(location)

View File

@@ -0,0 +1,26 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.polySymbols.utils
import com.intellij.platform.backend.documentation.DocumentationTarget
import com.intellij.polySymbols.documentation.PolySymbolDocumentation
import com.intellij.polySymbols.documentation.PolySymbolWithDocumentation
import com.intellij.psi.PsiElement
interface PolySymbolDelegateWithDocumentation<T : PolySymbolWithDocumentation> : PolySymbolDelegate<T>, PolySymbolWithDocumentation {
override val description: String?
get() = delegate.description
override val descriptionSections: Map<String, String>
get() = delegate.descriptionSections
override val docUrl: String?
get() = delegate.docUrl
override val defaultValue: String?
get() = delegate.defaultValue
override fun createDocumentation(location: PsiElement?): PolySymbolDocumentation? =
delegate.createDocumentation(location)
override fun getDocumentationTarget(location: PsiElement?): DocumentationTarget? =
delegate.getDocumentationTarget(location)
}

View File

@@ -21,7 +21,7 @@ import com.intellij.polySymbols.impl.withOffset
import com.intellij.polySymbols.impl.withRange
import com.intellij.polySymbols.patterns.impl.applyIcons
import com.intellij.polySymbols.query.*
import com.intellij.polySymbols.query.impl.PolySymbolMatchImpl
import com.intellij.polySymbols.query.impl.PolySymbolMatchBase
import com.intellij.polySymbols.references.PolySymbolReferenceProblem.ProblemKind
import com.intellij.pom.Navigatable
import com.intellij.psi.PsiElement
@@ -84,7 +84,10 @@ fun PolySymbol.withMatchedKind(qualifiedKind: PolySymbolQualifiedKind): PolySymb
else this
fun PolySymbol.withNavigationTarget(target: PsiElement): PolySymbol =
object : PolySymbolDelegate<PolySymbol>(this@withNavigationTarget) {
object : PolySymbolDelegate<PolySymbol> {
override val delegate: PolySymbol
get() = this@withNavigationTarget
override fun getNavigationTargets(project: Project): Collection<NavigationTarget> =
listOf(SymbolNavigationService.getInstance().psiElementNavigationTarget(target))
@@ -139,7 +142,7 @@ fun PolySymbolNameSegment.withSymbols(symbols: List<PolySymbol>): PolySymbolName
(this as PolySymbolNameSegmentImpl).withSymbols(symbols)
fun PolySymbolMatch.withSegments(segments: List<PolySymbolNameSegment>): PolySymbolMatch =
(this as PolySymbolMatchImpl).withSegments(segments)
(this as PolySymbolMatchBase).withSegments(segments)
fun PolySymbol.match(
nameToMatch: String,

View File

@@ -6,7 +6,7 @@ import com.intellij.platform.backend.navigation.NavigationTarget
import com.intellij.polySymbols.search.PsiSourcedPolySymbol
import com.intellij.psi.PsiElement
abstract class PsiSourcedPolySymbolDelegate<T : PsiSourcedPolySymbol>(delegate: T) : PolySymbolDelegate<T>(delegate), PsiSourcedPolySymbol {
interface PsiSourcedPolySymbolDelegate<T : PsiSourcedPolySymbol> : PolySymbolDelegate<T>, PsiSourcedPolySymbol {
override val source: PsiElement?
get() = delegate.source

View File

@@ -1,14 +1,28 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.polySymbols.webTypes
import com.intellij.model.Pointer
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.backend.documentation.DocumentationTarget
import com.intellij.polySymbols.PolySymbol
import com.intellij.polySymbols.PolySymbol.Companion.PROP_NO_DOC
import com.intellij.polySymbols.documentation.PolySymbolWithDocumentation
import com.intellij.polySymbols.documentation.impl.PolySymbolDocumentationTargetImpl
import com.intellij.polySymbols.search.PsiSourcedPolySymbol
import com.intellij.psi.PsiElement
interface WebTypesSymbol : PsiSourcedPolySymbol {
interface WebTypesSymbol : PsiSourcedPolySymbol, PolySymbolWithDocumentation {
val location: Location?
override fun getDocumentationTarget(location: PsiElement?): DocumentationTarget? =
if (properties[PROP_NO_DOC] != true)
PolySymbolDocumentationTargetImpl(this, location)
else
null
override fun createPointer(): Pointer<out WebTypesSymbol>
sealed interface Location
sealed interface FileLocation {

View File

@@ -6,6 +6,7 @@ import com.intellij.model.Symbol
import com.intellij.polySymbols.*
import com.intellij.polySymbols.completion.PolySymbolCodeCompletionItem
import com.intellij.polySymbols.context.PolyContext
import com.intellij.polySymbols.documentation.PolySymbolWithDocumentation
import com.intellij.polySymbols.html.PolySymbolHtmlAttributeValue
import com.intellij.polySymbols.patterns.PolySymbolsPattern
import com.intellij.polySymbols.query.PolySymbolsCodeCompletionQueryParams
@@ -17,6 +18,7 @@ import com.intellij.polySymbols.webTypes.impl.WebTypesJsonContributionAdapter
import com.intellij.polySymbols.webTypes.impl.wrap
import com.intellij.polySymbols.webTypes.json.*
import com.intellij.psi.PsiElement
import com.intellij.util.asSafely
import com.intellij.util.containers.Stack
import javax.swing.Icon
@@ -111,17 +113,21 @@ open class WebTypesSymbolBase : WebTypesSymbol {
final override val description: String?
get() = base.contribution.description
?.let { base.jsonOrigin.renderDescription(base.contribution.description) }
?: superContributions.asSequence().mapNotNull { it.description }.firstOrNull()
?: superContributions.asSequence()
.mapNotNull { it.asSafely<PolySymbolWithDocumentation>()?.description }
.firstOrNull()
final override val descriptionSections: Map<String, String>
get() = (base.contribution.descriptionSections?.additionalProperties?.asSequence() ?: emptySequence())
.plus(superContributions.asSequence().flatMap { it.descriptionSections.asSequence() })
.plus(superContributions.asSequence().flatMap {
it.asSafely<PolySymbolWithDocumentation>()?.descriptionSections?.asSequence() ?: emptySequence()
})
.distinctBy { it.key }
.associateBy({ it.key }, { base.jsonOrigin.renderDescription(it.value) })
final override val docUrl: String?
get() = base.contribution.docUrl
?: superContributions.asSequence().mapNotNull { it.docUrl }.firstOrNull()
?: superContributions.asSequence().mapNotNull { it.asSafely<PolySymbolWithDocumentation>()?.docUrl }.firstOrNull()
final override val icon: Icon?
get() = base.icon ?: superContributions.asSequence().mapNotNull { it.icon }.firstOrNull()
@@ -180,12 +186,11 @@ open class WebTypesSymbolBase : WebTypesSymbol {
final override val defaultValue: String?
get() = (base.contribution as? GenericContribution)?.default
?: (base.contribution as? HtmlAttribute)?.default
?: superContributions.firstOrNull()?.defaultValue
?: superContributions.firstNotNullOfOrNull { it.asSafely<PolySymbolWithDocumentation>()?.defaultValue }
final override val pattern: PolySymbolsPattern?
get() = base.jsonPattern?.wrap(base.contribution.name, origin as WebTypesJsonOrigin)
final override val queryScope: List<PolySymbolsScope>
get() = superContributions.asSequence()
.flatMap { it.queryScope }

View File

@@ -6,6 +6,7 @@ import com.intellij.polySymbols.PolySymbolApiStatus
import com.intellij.polySymbols.PolySymbolNameSegment
import com.intellij.polySymbols.search.PsiSourcedPolySymbol
import com.intellij.polySymbols.completion.PolySymbolCodeCompletionItem
import com.intellij.polySymbols.documentation.PolySymbolWithDocumentation
import com.intellij.polySymbols.html.PolySymbolHtmlAttributeValue
import com.intellij.polySymbols.testFramework.DebugOutputPrinter
import com.intellij.polySymbols.utils.completeMatch
@@ -70,9 +71,11 @@ open class PolySymbolsDebugOutputPrinter : DebugOutputPrinter() {
printProperty(level, "type", source.type)
printProperty(level, "attrValue", source.attributeValue)
printProperty(level, "complete", source.completeMatch)
printProperty(level, "description", source.description?.ellipsis(45))
printProperty(level, "docUrl", source.docUrl)
printProperty(level, "descriptionSections", source.descriptionSections.takeIf { it.isNotEmpty() })
if (source is PolySymbolWithDocumentation) {
printProperty(level, "description", source.description?.ellipsis(45))
printProperty(level, "docUrl", source.docUrl)
printProperty(level, "descriptionSections", source.descriptionSections.takeIf { it.isNotEmpty() })
}
printProperty(level, "abstract", source.abstract.takeIf { it })
printProperty(level, "virtual", source.virtual.takeIf { it })
printProperty(level, "apiStatus", source.apiStatus.takeIf { it !is PolySymbolApiStatus.Stable || it.since != null })

View File

@@ -5,8 +5,9 @@ import com.intellij.psi.PsiElement
import com.intellij.polySymbols.PolySymbol
import com.intellij.polySymbols.PolySymbolApiStatus
import com.intellij.polySymbols.documentation.PolySymbolDocumentation
import com.intellij.polySymbols.documentation.PolySymbolWithDocumentation
abstract class MdnDocumentedSymbol : PolySymbol {
abstract class MdnDocumentedSymbol : PolySymbolWithDocumentation {
private val mdnDoc by lazy(LazyThreadSafetyMode.PUBLICATION) {
getMdnDocumentation()

View File

@@ -12,6 +12,7 @@ import com.intellij.polySymbols.PolySymbol.Companion.HTML_ATTRIBUTE_VALUES
import com.intellij.polySymbols.completion.PolySymbolCodeCompletionItem
import com.intellij.polySymbols.completion.PolySymbolCodeCompletionItemCustomizer
import com.intellij.polySymbols.context.PolyContext
import com.intellij.polySymbols.documentation.PolySymbolWithDocumentation
import com.intellij.polySymbols.html.PolySymbolHtmlAttributeValue
import com.intellij.polySymbols.query.*
import com.intellij.polySymbols.search.PsiSourcedPolySymbol
@@ -182,7 +183,9 @@ class PolySymbolsHtmlQueryConfigurator : PolySymbolsQueryConfigurator {
}
}
abstract class StandardHtmlSymbol : MdnDocumentedSymbol(), PsiSourcedPolySymbol
abstract class StandardHtmlSymbol : MdnDocumentedSymbol(), PsiSourcedPolySymbol {
abstract override fun createPointer(): Pointer<out StandardHtmlSymbol>
}
class HtmlElementDescriptorBasedSymbol(
val descriptor: XmlElementDescriptor,