From 8723132ebf635e9791cc7a9557f1b6cf063eb66c Mon Sep 17 00:00:00 2001 From: Max Medvedev Date: Mon, 16 Jun 2025 19:04:00 +0200 Subject: [PATCH] Introduce SyntaxElementLanguageProvider GitOrigin-RevId: cde3c5adf106e40acebeed88f80eb25a90f4b05e --- .../syntax-util/api-dump-experimental.txt | 9 +++ .../language/SyntaxElementLanguageProvider.kt | 81 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 platform/syntax/syntax-util/src/com/intellij/platform/syntax/util/language/SyntaxElementLanguageProvider.kt diff --git a/platform/syntax/syntax-util/api-dump-experimental.txt b/platform/syntax/syntax-util/api-dump-experimental.txt index b4363964df79..636e8fe29ac9 100644 --- a/platform/syntax/syntax-util/api-dump-experimental.txt +++ b/platform/syntax/syntax-util/api-dump-experimental.txt @@ -2,6 +2,15 @@ - a:getCancellationProvider():com.intellij.platform.syntax.CancellationProvider *f:com.intellij.platform.syntax.util.cancellation.CancellationProviderExtensionKt - *sf:cancellationProvider():com.intellij.platform.syntax.CancellationProvider +*f:com.intellij.platform.syntax.util.language.FiniteSyntaxElementLanguageProvider +- com.intellij.platform.syntax.util.language.SyntaxElementLanguageProvider +- (com.intellij.platform.syntax.SyntaxLanguage,com.intellij.platform.syntax.SyntaxElementTypeSet):V +- getLanguages(com.intellij.platform.syntax.SyntaxElementType):kotlin.sequences.Sequence +*:com.intellij.platform.syntax.util.language.SyntaxElementLanguageProvider +- a:getLanguages(com.intellij.platform.syntax.SyntaxElementType):kotlin.sequences.Sequence +*f:com.intellij.platform.syntax.util.language.SyntaxElementLanguageProviderKt +- *sf:getLanguage(com.intellij.platform.syntax.util.language.SyntaxElementLanguageProvider,com.intellij.platform.syntax.SyntaxElementType):com.intellij.platform.syntax.SyntaxLanguage +- *sf:syntaxElementLanguageProvider():com.intellij.platform.syntax.util.language.SyntaxElementLanguageProvider *c:com.intellij.platform.syntax.util.lexer.DelegateLexer - com.intellij.platform.syntax.util.lexer.LexerBase - (com.intellij.platform.syntax.lexer.Lexer):V diff --git a/platform/syntax/syntax-util/src/com/intellij/platform/syntax/util/language/SyntaxElementLanguageProvider.kt b/platform/syntax/syntax-util/src/com/intellij/platform/syntax/util/language/SyntaxElementLanguageProvider.kt new file mode 100644 index 000000000000..1cda3a4fab4f --- /dev/null +++ b/platform/syntax/syntax-util/src/com/intellij/platform/syntax/util/language/SyntaxElementLanguageProvider.kt @@ -0,0 +1,81 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +@file:ApiStatus.Experimental + +package com.intellij.platform.syntax.util.language + +import com.intellij.platform.syntax.SyntaxElementType +import com.intellij.platform.syntax.SyntaxElementTypeSet +import com.intellij.platform.syntax.SyntaxLanguage +import com.intellij.platform.syntax.extensions.ExtensionPointKey +import com.intellij.platform.syntax.extensions.ExtensionSupport +import org.jetbrains.annotations.ApiStatus + +/** + * Specifies which languages a given element type belongs to. + * + * This information is used by [com.intellij.platform.syntax.tree.SyntaxNode] to determine the language of the node. + * If the language provider is missing or does not provide a single language for the given element type, the node will use the language of its parent. + * If the language provider is missing or does not provide a single language even for the parent, the language of the parser will be used. + * + * So you need to provide the languages for your language only if the given node tree contains several languages at once. + * + * You can register your own [SyntaxElementLanguageProvider] in [ExtensionSupport] via `com.intellij.syntax.syntaxElementLanguageProvider` extension point. + * N.B. There may be several language providers registered, in which case they are combined. + * + * @see getLanguage + * @see syntaxElementLanguageProvider + * @see FiniteSyntaxElementLanguageProvider + */ +@ApiStatus.Experimental +fun interface SyntaxElementLanguageProvider { + fun getLanguages(elementType: SyntaxElementType): Sequence +} + +/** + * Returns the language that the given element type belongs to or `null` if it doesn't belong to any language or if several languages are possible. + */ +@ApiStatus.Experimental +fun SyntaxElementLanguageProvider.getLanguage(elementType: SyntaxElementType): SyntaxLanguage? = + getLanguages(elementType).singleOrNull() + +/** + * Returns a [SyntaxElementLanguageProvider] from the current [ExtensionSupport]. + * Note that if several language providers are registered, the result provider combines them all. + */ +@ApiStatus.Experimental +fun syntaxElementLanguageProvider(): SyntaxElementLanguageProvider? { + val languageProviders = ExtensionSupport().getExtensions(syntaxElementLanguageProviderEP) + if (languageProviders.isEmpty()) return null + + if (languageProviders.size == 1) return languageProviders[0] + + // todo implement a more efficient way to combine FiniteSyntaxElementLanguageProviders + return SyntaxElementLanguageProvider { elementType -> + languageProviders.asSequence().flatMap { languageProvider -> + languageProvider.getLanguages(elementType) + } + } +} + +/** + * A [SyntaxElementLanguageProvider] that returns the given language for all element types in the given set. + * Prefer this class over implementing your own [SyntaxElementLanguageProvider] if the set of element types is finite. + * + * @see SyntaxElementLanguageProvider + */ +@ApiStatus.Experimental +class FiniteSyntaxElementLanguageProvider( + private val language: SyntaxLanguage, + private val elementTypes: SyntaxElementTypeSet, +) : SyntaxElementLanguageProvider { + override fun getLanguages(elementType: SyntaxElementType): Sequence { + return if (elementType in elementTypes) { + sequenceOf(language) + } + else { + emptySequence() + } + } +} + +private val syntaxElementLanguageProviderEP = ExtensionPointKey("com.intellij.syntax.syntaxElementLanguageProvider")