diff --git a/python/pluginResources/intellij.python.community.impl.xml b/python/pluginResources/intellij.python.community.impl.xml
index ce2a4462fc37..77be0882305f 100644
--- a/python/pluginResources/intellij.python.community.impl.xml
+++ b/python/pluginResources/intellij.python.community.impl.xml
@@ -63,6 +63,8 @@
+
+
diff --git a/python/python-ast/src/com/jetbrains/python/PyLanguageFacadeBase.kt b/python/python-ast/src/com/jetbrains/python/PyLanguageFacadeBase.kt
new file mode 100644
index 000000000000..23e4ce40bb12
--- /dev/null
+++ b/python/python-ast/src/com/jetbrains/python/PyLanguageFacadeBase.kt
@@ -0,0 +1,38 @@
+package com.jetbrains.python
+
+import com.intellij.injected.editor.VirtualFileWindow
+import com.intellij.notebook.editor.BackedVirtualFile
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.psi.PsiDirectory
+import com.intellij.psi.PsiElement
+import com.jetbrains.python.ast.PyAstFile
+import com.jetbrains.python.psi.LanguageLevel
+import org.jetbrains.annotations.ApiStatus
+
+@ApiStatus.Internal
+abstract class PyLanguageFacadeBase : PyLanguageFacade() {
+ final override fun getEffectiveLanguageLevel(psiElement: PsiElement): LanguageLevel {
+ if (psiElement is PsiDirectory) {
+ return getEffectiveLanguageLevel(psiElement.getProject(), psiElement.getVirtualFile())
+ }
+
+ val containingFile = psiElement.getContainingFile()
+ if (containingFile is PyAstFile) {
+ return containingFile.languageLevel
+ }
+
+ return LanguageLevel.getDefault()
+ }
+
+ final override fun getEffectiveLanguageLevel(project: Project, virtualFile: VirtualFile): LanguageLevel {
+ var file = virtualFile
+ if (file is VirtualFileWindow) {
+ file = file.getDelegate()
+ }
+ file = BackedVirtualFile.getOriginFileIfBacked(file)
+ return doGetEffectiveLanguageLevel(project, file)
+ }
+
+ protected abstract fun doGetEffectiveLanguageLevel(project: Project, virtualFile: VirtualFile): LanguageLevel
+}
diff --git a/python/python-ast/src/com/jetbrains/python/ast/PyAstFile.java b/python/python-ast/src/com/jetbrains/python/ast/PyAstFile.java
index 0b8ef50e5909..402d0278779d 100644
--- a/python/python-ast/src/com/jetbrains/python/ast/PyAstFile.java
+++ b/python/python-ast/src/com/jetbrains/python/ast/PyAstFile.java
@@ -18,6 +18,7 @@ package com.jetbrains.python.ast;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
+import com.jetbrains.python.PyLanguageFacadeKt;
import com.jetbrains.python.ast.controlFlow.AstScopeOwner;
import com.jetbrains.python.ast.docstring.DocStringUtilCore;
import com.jetbrains.python.ast.impl.PyPsiUtilsCore;
@@ -43,7 +44,9 @@ public interface PyAstFile extends PyAstElement, PsiFile, PyAstDocStringOwner, A
return stmts;
}
- LanguageLevel getLanguageLevel();
+ default LanguageLevel getLanguageLevel() {
+ return PyLanguageFacadeKt.getEffectiveLanguageLevel(this);
+ }
/**
* Return true if the file contains a 'from __future__ import ...' statement with given feature.
diff --git a/python/python-ast/src/com/jetbrains/python/psi/PyAstElementGenerator.java b/python/python-ast/src/com/jetbrains/python/psi/PyAstElementGenerator.java
index 944dbc0d4415..6e6a74a2699a 100644
--- a/python/python-ast/src/com/jetbrains/python/psi/PyAstElementGenerator.java
+++ b/python/python-ast/src/com/jetbrains/python/psi/PyAstElementGenerator.java
@@ -2,19 +2,18 @@ package com.jetbrains.python.psi;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileFactory;
import com.intellij.psi.impl.PsiFileFactoryImpl;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.util.IncorrectOperationException;
+import com.jetbrains.python.PyLanguageFacade;
import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.ast.*;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
@@ -103,7 +102,7 @@ public class PyAstElementGenerator {
final PsiFileFactory factory = PsiFileFactory.getInstance(myProject);
final String name = getDummyFileName();
final LightVirtualFile virtualFile = new LightVirtualFile(name, PythonFileType.INSTANCE, contents);
- specifyFileLanguageLevel(virtualFile, langLevel);
+ PyLanguageFacade.getINSTANCE().setEffectiveLanguageLevel(virtualFile, langLevel);
final PsiFile psiFile = ((PsiFileFactoryImpl)factory).trySetupPsiForFile(virtualFile, PythonLanguage.getInstance(), physical, true);
assert psiFile != null;
return psiFile;
@@ -127,8 +126,6 @@ public class PyAstElementGenerator {
return (PyAstPassStatement)statementList.getStatements()[0];
}
- protected void specifyFileLanguageLevel(@NotNull VirtualFile virtualFile, @Nullable LanguageLevel langLevel) { }
-
public @NotNull PyAstExpression createExpressionFromText(@NotNull LanguageLevel languageLevel, @NotNull String text)
throws IncorrectOperationException {
final PsiFile dummyFile = createDummyFile(languageLevel, text);
diff --git a/python/python-parser/src/com/jetbrains/python/PyLanguageFacade.kt b/python/python-parser/src/com/jetbrains/python/PyLanguageFacade.kt
index e4efd3e71bfc..8a25d9ba4a03 100644
--- a/python/python-parser/src/com/jetbrains/python/PyLanguageFacade.kt
+++ b/python/python-parser/src/com/jetbrains/python/PyLanguageFacade.kt
@@ -2,14 +2,32 @@ package com.jetbrains.python
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.service
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
import com.jetbrains.python.psi.LanguageLevel
+import org.jetbrains.annotations.ApiStatus
+@ApiStatus.Experimental
abstract class PyLanguageFacade {
- abstract fun forLanguage(psiElement: PsiElement): LanguageLevel
+ abstract fun getEffectiveLanguageLevel(psiElement: PsiElement): LanguageLevel
+ abstract fun getEffectiveLanguageLevel(project: Project, virtualFile: VirtualFile): LanguageLevel
+ abstract fun setEffectiveLanguageLevel(virtualFile: VirtualFile, languageLevel: LanguageLevel?)
companion object {
+ @JvmStatic
val INSTANCE: PyLanguageFacade
- get() = ApplicationManager.getApplication().service()
+ get() = ApplicationManager.getApplication().service()
}
-}
\ No newline at end of file
+}
+
+@ApiStatus.Internal
+fun getEffectiveLanguageLevel(file: PsiFile): LanguageLevel {
+ var curFile = file
+ while (curFile != curFile.getOriginalFile()) {
+ curFile = curFile.getOriginalFile()
+ }
+ val virtualFile = curFile.getVirtualFile() ?: curFile.getViewProvider().getVirtualFile()
+ return PyLanguageFacade.INSTANCE.getEffectiveLanguageLevel(curFile.project, virtualFile)
+}
diff --git a/python/python-parser/src/com/jetbrains/python/psi/LanguageLevel.java b/python/python-parser/src/com/jetbrains/python/psi/LanguageLevel.java
index f0eb2c156f95..a172d00470ae 100644
--- a/python/python-parser/src/com/jetbrains/python/psi/LanguageLevel.java
+++ b/python/python-parser/src/com/jetbrains/python/psi/LanguageLevel.java
@@ -212,7 +212,7 @@ public enum LanguageLevel {
}
public static @NotNull LanguageLevel forElement(@NotNull PsiElement element) {
- return PyLanguageFacade.Companion.getINSTANCE().forLanguage(element);
+ return PyLanguageFacade.getINSTANCE().getEffectiveLanguageLevel(element);
}
public static @NotNull LanguageLevel getLatest() {
diff --git a/python/python-psi-impl/src/com/jetbrains/python/PyLanguageFacadeImpl.kt b/python/python-psi-impl/src/com/jetbrains/python/PyLanguageFacadeImpl.kt
index 3e9989cf60c7..57deac1a2b60 100644
--- a/python/python-psi-impl/src/com/jetbrains/python/PyLanguageFacadeImpl.kt
+++ b/python/python-psi-impl/src/com/jetbrains/python/PyLanguageFacadeImpl.kt
@@ -1,22 +1,16 @@
package com.jetbrains.python
-import com.intellij.psi.PsiDirectory
-import com.intellij.psi.PsiElement
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
import com.jetbrains.python.psi.LanguageLevel
-import com.jetbrains.python.psi.PyFile
import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher
-class PyLanguageFacadeImpl : PyLanguageFacade() {
- override fun forLanguage(psiElement: PsiElement): LanguageLevel {
- if (psiElement is PsiDirectory) {
- return PythonLanguageLevelPusher.getLanguageLevelForVirtualFile(psiElement.getProject(), psiElement.getVirtualFile())
- }
+class PyLanguageFacadeImpl : PyLanguageFacadeBase() {
+ override fun doGetEffectiveLanguageLevel(project: Project, virtualFile: VirtualFile): LanguageLevel {
+ return PythonLanguageLevelPusher.getEffectiveLanguageLevel(project, virtualFile)
+ }
- val containingFile = psiElement.getContainingFile()
- if (containingFile is PyFile) {
- return containingFile.getLanguageLevel()
- }
-
- return LanguageLevel.getDefault()
+ override fun setEffectiveLanguageLevel(virtualFile: VirtualFile, languageLevel: LanguageLevel?) {
+ PythonLanguageLevelPusher.specifyFileLanguageLevel(virtualFile, languageLevel)
}
}
\ No newline at end of file
diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java
index ca531850f4a0..c42f8b784e1c 100644
--- a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java
+++ b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyElementGeneratorImpl.java
@@ -36,11 +36,6 @@ public final class PyElementGeneratorImpl extends PyElementGenerator {
super(project);
}
- @Override
- protected void specifyFileLanguageLevel(@NotNull VirtualFile virtualFile, @Nullable LanguageLevel langLevel) {
- PythonLanguageLevelPusher.specifyFileLanguageLevel(virtualFile, langLevel);
- }
-
@Override
public ASTNode createNameIdentifier(String name, LanguageLevel languageLevel) {
final PsiFile dummyFile = createDummyFile(languageLevel, name);
diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyFileImpl.java b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyFileImpl.java
index 1812889dfc20..504a0a1aef55 100644
--- a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyFileImpl.java
+++ b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PyFileImpl.java
@@ -18,7 +18,6 @@ import com.intellij.psi.scope.DelegatingScopeProcessor;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.util.PsiModificationTracker;
-import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Processor;
@@ -27,7 +26,6 @@ import com.jetbrains.python.PyNames;
import com.jetbrains.python.PythonFileType;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.ast.PyAstElementVisitor;
-import com.jetbrains.python.ast.impl.PyUtilCore;
import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
import com.jetbrains.python.documentation.docstrings.DocStringUtil;
import com.jetbrains.python.psi.*;
@@ -247,11 +245,6 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
return null;
}
- @Override
- public LanguageLevel getLanguageLevel() {
- return PythonLanguageLevelPusher.getLanguageLevelForFile(this);
- }
-
@Override
public Icon getIcon(int flags) {
return PythonFileType.INSTANCE.getIcon();
diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java
index 5bfa916fc962..dc05015dc1af 100644
--- a/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java
+++ b/python/python-psi-impl/src/com/jetbrains/python/psi/impl/PythonLanguageLevelPusher.java
@@ -2,8 +2,6 @@
package com.jetbrains.python.psi.impl;
import com.google.common.collect.Maps;
-import com.intellij.injected.editor.VirtualFileWindow;
-import com.intellij.notebook.editor.BackedVirtualFile;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.fileTypes.FileTypeRegistry;
@@ -31,9 +29,7 @@ import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.TreeNodeProcessingResult;
import com.intellij.util.indexing.IndexingBundle;
import com.intellij.util.messages.SimpleMessageBusConnection;
-import com.jetbrains.python.PythonCodeStyleService;
-import com.jetbrains.python.PythonFileType;
-import com.jetbrains.python.PythonRuntimeService;
+import com.jetbrains.python.*;
import com.jetbrains.python.codeInsight.typing.PyTypeShed;
import com.jetbrains.python.module.PyModuleService;
import com.jetbrains.python.psi.LanguageLevel;
@@ -159,6 +155,15 @@ public final class PythonLanguageLevelPusher implements FilePropertyPusher = Topic(VirtualFilePyLanguageLevelListener::class.java)
+ }
+
+ fun levelChanged(virtualFile: VirtualFile, newLevel: LanguageLevel)
+}
diff --git a/python/python-syntax/BUILD.bazel b/python/python-syntax/BUILD.bazel
index d8787acf050f..5319a6c87c25 100644
--- a/python/python-syntax/BUILD.bazel
+++ b/python/python-syntax/BUILD.bazel
@@ -28,6 +28,7 @@ jvm_library(
"//platform/code-style-impl:codeStyle-impl",
"@lib//:fastutil-min",
"@lib//:guava",
+ "@lib//:kotlinx-serialization-core",
],
runtime_deps = [":syntax_resources"]
)
diff --git a/python/python-syntax/intellij.python.syntax.iml b/python/python-syntax/intellij.python.syntax.iml
index 0dde512bff2e..c4363580c1ce 100644
--- a/python/python-syntax/intellij.python.syntax.iml
+++ b/python/python-syntax/intellij.python.syntax.iml
@@ -1,5 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $KOTLIN_BUNDLED$/lib/kotlinx-serialization-compiler-plugin.jar
+
+
+
+
+
+
+
@@ -23,5 +45,6 @@
+
\ No newline at end of file
diff --git a/python/python-syntax/src/com/jetbrains/python/highlighting/PySyntaxHighlighterFactoryBase.java b/python/python-syntax/src/com/jetbrains/python/highlighting/PySyntaxHighlighterFactoryBase.java
index 860a7c29eff2..9ef522a99cf4 100644
--- a/python/python-syntax/src/com/jetbrains/python/highlighting/PySyntaxHighlighterFactoryBase.java
+++ b/python/python-syntax/src/com/jetbrains/python/highlighting/PySyntaxHighlighterFactoryBase.java
@@ -6,6 +6,7 @@ import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.FactoryMap;
+import com.jetbrains.python.PyLanguageFacade;
import com.jetbrains.python.lexer.PythonHighlightingLexer;
import com.jetbrains.python.parsing.console.PyConsoleHighlightingLexer;
import com.jetbrains.python.psi.LanguageLevel;
@@ -48,8 +49,10 @@ public class PySyntaxHighlighterFactoryBase extends SyntaxHighlighterFactory {
return myMap.get(level);
}
- protected LanguageLevel getLanguageLevel(final @Nullable Project project, final @Nullable VirtualFile virtualFile) {
- return LanguageLevel.getDefault();
+ private static @NotNull LanguageLevel getLanguageLevel(final @Nullable Project project, final @Nullable VirtualFile virtualFile) {
+ return project != null && virtualFile != null ?
+ PyLanguageFacade.getINSTANCE().getEffectiveLanguageLevel(project, virtualFile) :
+ LanguageLevel.getDefault();
}
protected boolean useConsoleLexer(final @Nullable Project project, final @Nullable VirtualFile virtualFile) {
diff --git a/python/python-syntax/src/com/jetbrains/python/psi/LanguageLevelHolder.kt b/python/python-syntax/src/com/jetbrains/python/psi/LanguageLevelHolder.kt
new file mode 100644
index 000000000000..410ab7b90f87
--- /dev/null
+++ b/python/python-syntax/src/com/jetbrains/python/psi/LanguageLevelHolder.kt
@@ -0,0 +1,6 @@
+package com.jetbrains.python.psi
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+class LanguageLevelHolder(val languageLevel: LanguageLevel)
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/highlighting/PySyntaxHighlighterFactory.java b/python/src/com/jetbrains/python/highlighting/PySyntaxHighlighterFactory.java
index efb7c6e76908..0e6a7d93b0fe 100644
--- a/python/src/com/jetbrains/python/highlighting/PySyntaxHighlighterFactory.java
+++ b/python/src/com/jetbrains/python/highlighting/PySyntaxHighlighterFactory.java
@@ -8,20 +8,10 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.jetbrains.python.console.PydevConsoleRunnerUtil;
-import com.jetbrains.python.psi.LanguageLevel;
-import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher;
-import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class PySyntaxHighlighterFactory extends PySyntaxHighlighterFactoryBase {
- @Override
- protected @NotNull LanguageLevel getLanguageLevel(final @Nullable Project project, final @Nullable VirtualFile virtualFile) {
- return project != null && virtualFile != null ?
- PythonLanguageLevelPusher.getLanguageLevelForVirtualFile(project, virtualFile) :
- LanguageLevel.getDefault();
- }
-
@Override
protected boolean useConsoleLexer(final @Nullable Project project, final @Nullable VirtualFile virtualFile) {
if (virtualFile == null || project == null || virtualFile instanceof VirtualFileWindow) {
diff --git a/python/src/com/jetbrains/python/psi/PyLangLevelVirtualFileCustomDataProvider.kt b/python/src/com/jetbrains/python/psi/PyLangLevelVirtualFileCustomDataProvider.kt
new file mode 100644
index 000000000000..e03cb4f7cb5c
--- /dev/null
+++ b/python/src/com/jetbrains/python/psi/PyLangLevelVirtualFileCustomDataProvider.kt
@@ -0,0 +1,37 @@
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+package com.jetbrains.python.psi
+
+import com.intellij.openapi.application.readAction
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.openapi.vfs.VirtualFileCustomDataProvider
+import com.intellij.util.messages.impl.subscribeAsFlow
+import com.jetbrains.python.psi.impl.PythonLanguageLevelPusher
+import com.jetbrains.python.psi.impl.VirtualFilePyLanguageLevelListener
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import kotlin.reflect.KType
+import kotlin.reflect.typeOf
+
+class PyLangLevelVirtualFileCustomDataProvider : VirtualFileCustomDataProvider {
+ override val id: String = "virtualFilePyLangLevel"
+
+ override val dataType: KType
+ get() = typeOf()
+
+ override fun getValues(project: Project, virtualFile: VirtualFile): Flow {
+ return project.messageBus.subscribeAsFlow(VirtualFilePyLanguageLevelListener.TOPIC) {
+ val level = readAction { PythonLanguageLevelPusher.getEffectiveLanguageLevel(project, virtualFile) }
+ trySend(level)
+
+ val vFile = virtualFile
+ object : VirtualFilePyLanguageLevelListener {
+ override fun levelChanged(virtualFile: VirtualFile, newLevel: LanguageLevel) {
+ if (vFile == virtualFile) {
+ trySend(newLevel)
+ }
+ }
+ }
+ }.map { LanguageLevelHolder(it) }
+ }
+}
\ No newline at end of file
diff --git a/python/testSrc/com/jetbrains/python/console/IPythonConsoleParsingTest.kt b/python/testSrc/com/jetbrains/python/console/IPythonConsoleParsingTest.kt
index ab27a041ed7c..53ca70130ca0 100644
--- a/python/testSrc/com/jetbrains/python/console/IPythonConsoleParsingTest.kt
+++ b/python/testSrc/com/jetbrains/python/console/IPythonConsoleParsingTest.kt
@@ -4,9 +4,7 @@ package com.jetbrains.python.console
import com.intellij.lang.LanguageASTFactory
import com.intellij.openapi.application.PathManager
import com.intellij.testFramework.ParsingTestCase
-import com.jetbrains.python.PythonDialectsTokenSetContributor
-import com.jetbrains.python.PythonLanguage
-import com.jetbrains.python.PythonTokenSetContributor
+import com.jetbrains.python.*
import com.jetbrains.python.psi.impl.PythonASTFactory
/**
@@ -24,6 +22,7 @@ class IPythonConsoleParsingTest : ParsingTestCase(
override fun setUp() {
super.setUp()
+ application.registerService(PyLanguageFacade::class.java, PyLanguageFacadeImpl())
registerExtensionPoint(PythonDialectsTokenSetContributor.EP_NAME, PythonDialectsTokenSetContributor::class.java)
registerExtension(PythonDialectsTokenSetContributor.EP_NAME, PythonTokenSetContributor())
addExplicitExtension(LanguageASTFactory.INSTANCE, PythonLanguage.getInstance(), PythonASTFactory())