Merge ktij/vd/KTIJ-37261/kt-master-merge-2026-01-26 into master

GitOrigin-RevId: 2f351d64ccc9180dacc0a1ab519ea8176dc39d9f
This commit is contained in:
Patronus
2026-01-30 22:01:16 +00:00
committed by intellij-monorepo-bot
127 changed files with 1312 additions and 583 deletions
+18
View File
@@ -41,6 +41,24 @@ Registry.`is`("my.key", false)
Registry.`is`("my.key")
```
### Early Startup: Registry.is() with Default Value
When code may run before `COMPONENTS_LOADED` state (e.g., during EULA dialog, splash screen),
you MUST use `Registry.is(key, defaultValue)` with an explicit default:
```kotlin
// Required for early startup code
Registry.`is`("my.key", false) // default must match registry.properties
```
**How to find the default value:**
1. Search in `community/platform/util/resources/misc/registry.properties`
2. Or check the `<registryKey>` declaration in `plugin.xml`
**Why:** `Registry.is(key)` without default throws an exception if called before
`LoadingState.COMPONENTS_LOADED`. The safe overload returns the provided default
when Registry is not yet initialized.
### Override via Command Line
For testing or run configurations:
+35 -3
View File
@@ -104,7 +104,8 @@
"https://bcr.bazel.build/modules/nlohmann_json/3.12.0.bcr.1/source.json": "93f82a5ae985eb935c539bfee95e04767187818189241ac956f3ccadbdb8fb02",
"https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74",
"https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92",
"https://bcr.bazel.build/modules/package_metadata/0.0.2/source.json": "e53a759a72488d2c0576f57491ef2da0cf4aab05ac0997314012495935531b73",
"https://bcr.bazel.build/modules/package_metadata/0.0.3/MODULE.bazel": "77890552ecea9e284b5424c9de827a58099348763a4359e975c359a83d4faa83",
"https://bcr.bazel.build/modules/package_metadata/0.0.3/source.json": "742075a428ad12a3fa18a69014c2f57f01af910c6d9d18646c990200853e641a",
"https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5",
"https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f",
"https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee",
@@ -204,7 +205,8 @@
"https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0",
"https://bcr.bazel.build/modules/rules_jvm_external/6.6/MODULE.bazel": "153042249c7060536dc95b6bb9f9bb8063b8a0b0cb7acdb381bddbc2374aed55",
"https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd",
"https://bcr.bazel.build/modules/rules_jvm_external/6.7/source.json": "5426f412d0a7fc6b611643376c7e4a82dec991491b9ce5cb1cfdd25fe2e92be4",
"https://bcr.bazel.build/modules/rules_jvm_external/6.9/MODULE.bazel": "07c5db05527db7744a54fcffd653e1550d40e0540207a7f7e6d0a4de5bef8274",
"https://bcr.bazel.build/modules/rules_jvm_external/6.9/source.json": "b12970214f3cc144b26610caeb101fa622d910f1ab3d98f0bae1058edbd00bd4",
"https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59",
"https://bcr.bazel.build/modules/rules_kotlin/1.9.5/MODULE.bazel": "043a16a572f610558ec2030db3ff0c9938574e7dd9f58bded1bb07c0192ef025",
"https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3",
@@ -216,7 +218,8 @@
"https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb",
"https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc",
"https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff",
"https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a",
"https://bcr.bazel.build/modules/rules_pkg/1.2.0/MODULE.bazel": "c7db3c2b407e673c7a39e3625dc05dc9f12d6682cbd82a3a5924a13b491eda7e",
"https://bcr.bazel.build/modules/rules_pkg/1.2.0/source.json": "9062e00845bf91a4247465d371baa837adf9b6ff44c542f73ba084f07667e1dc",
"https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06",
"https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7",
"https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483",
@@ -495,6 +498,35 @@
"recordedRepoMappingEntries": []
}
},
"@@rules_jvm+//:test_bazel_tool.bzl%extension": {
"general": {
"bzlTransitiveDigest": "qGkIs/3yNSU+RyxsUfa8+9i0jlmnXoYwohYDio9VQt8=",
"usagesDigest": "W8DfOeyDZF6L7pySiHBbDCXlAWCUd2mYD/Tnijjo5/o=",
"recordedFileInputs": {},
"recordedDirentsInputs": {},
"envVariables": {},
"generatedRepoSpecs": {
"test_bazel_tool": {
"repoRuleId": "@@rules_jvm+//:test_bazel_tool.bzl%_repo",
"attributes": {
"url_darwin_arm64": "https://github.com/JetBrains/bazel/releases/download/8.4.2-jb_20251027_92/bazel-8.4.2-jb_20251027_92-darwin-arm64",
"url_darwin_x86_64": "https://github.com/JetBrains/bazel/releases/download/8.4.2-jb_20251027_92/bazel-8.4.2-jb_20251027_92-darwin-x86_64",
"url_linux_arm64": "https://github.com/JetBrains/bazel/releases/download/8.4.2-jb_20251027_92/bazel-8.4.2-jb_20251027_92-linux-arm64",
"url_linux_x86_64": "https://github.com/JetBrains/bazel/releases/download/8.4.2-jb_20251027_92/bazel-8.4.2-jb_20251027_92-linux-x86_64",
"url_windows_arm64": "https://github.com/JetBrains/bazel/releases/download/8.4.2-jb_20251027_92/bazel-8.4.2-jb_20251027_92-windows-arm64.exe",
"url_windows_x86_64": "https://github.com/JetBrains/bazel/releases/download/8.4.2-jb_20251027_92/bazel-8.4.2-jb_20251027_92-windows-x86_64.exe",
"sha256_darwin_arm64": "ac07be2a25647be6ecdcf780cd9ebded1048149ebadd7a81087e19f7372da706",
"sha256_darwin_x86_64": "681ed55cac703c63a31ae46b94f5dcf283c002ce989ab893d230572b4d913ee7",
"sha256_linux_arm64": "06513528cff8eacc8674585463bce6e72dd229c52fe53fd14dc1bdbc83e38977",
"sha256_linux_x86_64": "b85195356864e3a6dbaf21ab438e66b6e686229f83e3d4e08ac2fb9b79d57d45",
"sha256_windows_arm64": "35857356462fd05ebee367278c3008c3da43300a122cc25d5d246406404de07e",
"sha256_windows_x86_64": "3f1922b13c84a6ccb0f2c738bc80e0eb90cefd118fa066109d1d3aaa0d44a828"
}
}
},
"recordedRepoMappingEntries": []
}
},
"@@rules_python+//python/extensions:config.bzl%config": {
"general": {
"bzlTransitiveDigest": "xaCns8Qt+8bJqVLy8r6nc/eL2AjEIX/vOdjqoh5xYac=",
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
1769703539
1769805526
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
1769717454
1769809507
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
1768926036
1769807983
@@ -4,8 +4,5 @@
<applicationService serviceInterface="com.intellij.platform.ide.customization.ExternalProductResourceUrls"
serviceImplementation="com.intellij.idea.customization.base.IntelliJIdeaExternalResourceUrls"
overrides="true"/>
<applicationActivity implementation="com.intellij.internal.statistic.updater.StatisticsJobsScheduler"/>
<applicationActivity implementation="com.intellij.internal.statistic.updater.StatisticsStateCollectorsScheduler"/>
<postStartupActivity implementation="com.intellij.internal.statistic.updater.StatisticsStateCollectorsScheduler$MyStartupActivity"/>
</extensions>
</idea-plugin>
@@ -19,6 +19,7 @@ import com.intellij.debugger.ui.breakpoints.StackCapturingLineBreakpoint;
import com.intellij.execution.JavaExecutionUtil;
import com.intellij.execution.configurations.JavaParameters;
import com.intellij.execution.configurations.ParametersList;
import com.intellij.icons.AllIcons;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.ide.plugins.PluginManagerCoreKt;
import com.intellij.idea.AppMode;
@@ -562,11 +563,12 @@ public final class AsyncStacksUtils {
}
}
private static class ThrottledFrame extends XStackFrame implements XStackFrameWithSeparatorAbove, JVMStackFrameInfoProvider {
private static class ThrottledFrame extends XStackFrame implements XStackFrameWithSeparatorAbove {
private boolean myWithSeparator;
@Override
public void customizePresentation(@NotNull ColoredTextContainer component) {
component.setIcon(AllIcons.Empty);
component.append(JavaDebuggerBundle.message("async.stack.throttled.frame.label"), SimpleTextAttributes.GRAY_ITALIC_ATTRIBUTES);
}
@@ -591,15 +593,5 @@ public final class AsyncStacksUtils {
public void setWithSeparator(boolean withSeparator) {
myWithSeparator = withSeparator;
}
@Override
public boolean isSynthetic() {
return true;
}
@Override
public boolean isInLibraryContent() {
return false;
}
}
}
@@ -1065,6 +1065,9 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
}
catch (Throwable ignored) {
}
finally {
DebuggerManagerThreadImpl.getCurrentThread().setVmProxy(null);
}
}
var attachedNewThread = unstashAndReattach();
@@ -61,9 +61,9 @@ class DebuggerManagerThreadImpl @ApiStatus.Internal constructor(
get() = _vmProxy.get()
@ApiStatus.Internal
fun setVmProxy(proxy: VirtualMachineProxyImpl) {
fun setVmProxy(proxy: VirtualMachineProxyImpl?) {
val current = vmProxy
if (current != null && current.virtualMachine !== proxy.virtualMachine) {
if (current != null && proxy != null && current.virtualMachine !== proxy.virtualMachine) {
LOG.error("VM proxy changed from $current to $proxy")
}
_vmProxy = WeakReference(proxy)
@@ -4,6 +4,7 @@ package com.intellij.debugger.engine
import com.intellij.debugger.engine.events.SuspendContextCommandImpl
import com.intellij.debugger.impl.DebuggerContextImpl
import com.intellij.debugger.impl.DebuggerUtilsAsync
import com.intellij.debugger.impl.instanceOf
import com.intellij.debugger.settings.DebuggerSettings
import com.intellij.debugger.ui.breakpoints.FilteredRequestor
import com.intellij.openapi.diagnostic.logger
@@ -14,9 +15,7 @@ import com.intellij.xdebugger.breakpoints.XBreakpointProperties
import com.intellij.xdebugger.impl.breakpoints.BreakpointState
import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase
import com.intellij.xdebugger.impl.breakpoints.XBreakpointUtil
import com.sun.jdi.ClassType
import com.sun.jdi.ObjectReference
import com.sun.jdi.Type
import com.sun.jdi.event.LocatableEvent
import com.sun.jdi.request.EventRequest
import kotlinx.coroutines.CompletableDeferred
@@ -26,7 +25,6 @@ import kotlinx.coroutines.withTimeout
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties
import kotlin.time.Duration
import org.jetbrains.org.objectweb.asm.Type as AsmType
/**
* Find some suspend context in which evaluation is possible.
@@ -166,40 +164,6 @@ fun <Self : XBreakpoint<P>, P : XBreakpointProperties<*>, S : BreakpointState> s
return (isLoggingBp || isConditionalBp) && !(isLoggingBp && isConditionalBp)
}
@ApiStatus.Internal
fun Type.isSubtype(className: String): Boolean = isSubtype(AsmType.getObjectType(className))
@ApiStatus.Internal
fun Type.isSubTypeOrSame(className: String): Boolean =
name() == className || isSubtype(className)
@ApiStatus.Internal
fun Type.isSubtype(type: AsmType): Boolean {
if (this.signature() == type.descriptor) {
return true
}
if (type.sort != AsmType.OBJECT || this !is ClassType) {
return false
}
val superTypeName = type.className
if (allInterfaces().any { it.name() == superTypeName }) {
return true
}
var superClass = superclass()
while (superClass != null) {
if (superClass.name() == superTypeName) {
return true
}
superClass = superClass.superclass()
}
return false
}
@ApiStatus.Internal
enum class ClientEvaluationExceptionType {
USER_EXCEPTION,
@@ -217,12 +181,12 @@ fun extractTypeFromClientException(exceptionFromCodeFragment: ObjectReference, h
}
}
if (type.signature().startsWith("Ljava/lang/invoke/")
|| type.isSubTypeOrSame("java.lang.ReflectiveOperationException")
|| type.isSubTypeOrSame("java.lang.LinkageError")
|| type.instanceOf("java.lang.ReflectiveOperationException")
|| type.instanceOf("java.lang.LinkageError")
) {
return ClientEvaluationExceptionType.MISCOMPILED
}
if (type.isSubTypeOrSame("java.lang.ClassCastException")) {
if (type.instanceOf("java.lang.ClassCastException")) {
return if (hasCast) ClientEvaluationExceptionType.USER_EXCEPTION else ClientEvaluationExceptionType.MISCOMPILED
}
return ClientEvaluationExceptionType.USER_EXCEPTION
@@ -138,3 +138,5 @@ internal fun <T> runBlockingAssertNotInReadAction(block: suspend CoroutineScope.
}
return runBlockingMaybeCancellable(block)
}
fun Type.instanceOf(className: String): Boolean = DebuggerUtils.instanceOf(this, className)
@@ -555,9 +555,9 @@ column.name.shallow.size=Shallow Size
column.name.retained.size=Retained Size
stepping.filter.real.thread.name=thread {0}
debugger.variables.not.available.in.async=Variables are not available for async stack frames
async.stack.throttled.frame.label=Async frames are omitted
async.stack.throttled.frame.info=Async stack trace was not collected due to performance overhead. You can disable throttling in
label.capture.configurable.throttling=Throttle async stack traces collection on significant overhead
async.stack.throttled.frame.label=Could not capture
async.stack.throttled.frame.info=Async stack trace was not collected for performance reasons. You can disable throttling in
label.capture.configurable.throttling=Throttle async stack traces on significant overhead
exception=Exception
ref=Ref
thread.dump.during.previous.dump.evaluation.warning=Evaluation of the previous dump is still in progress. Java platform thread dump is taken.
+26 -1
View File
@@ -30,4 +30,29 @@ jvm_library(
"//notebooks/visualization",
]
)
### auto-generated section `build intellij.java.ide.customization` end
jvm_library(
name = "ide-customization_test_lib",
visibility = ["//visibility:public"],
srcs = glob(["test/**/*.kt", "test/**/*.java", "test/**/*.form"], allow_empty = True),
associates = [":ide-customization"],
deps = [
"//java/java-psi-api:psi",
"//platform/core-api:core",
"//platform/lang-api:lang",
"//platform/platform-impl:ide-impl",
"//platform/lang-core",
"//platform/testFramework",
"//platform/testFramework:testFramework_test_lib",
]
)
### auto-generated section `build intellij.java.ide.customization` end
### auto-generated section `test intellij.java.ide.customization` start
load("@community//build:tests-options.bzl", "jps_test")
jps_test(
name = "ide-customization_test",
runtime_deps = [":ide-customization_test_lib"]
)
### auto-generated section `test intellij.java.ide.customization` end
@@ -5,6 +5,7 @@
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
@@ -20,5 +21,6 @@
<orderEntry type="module" module-name="intellij.notebooks.visualization" scope="RUNTIME" />
<orderEntry type="module" module-name="intellij.platform.ide.impl" />
<orderEntry type="module" module-name="intellij.platform.lang.core" />
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
</component>
</module>
@@ -4,7 +4,7 @@
<vendor>JetBrains</vendor>
<category>IDE Settings</category>
<description>
Provides customization for IDEs which main language is Java (e.g., IntelliJ IDEA).
Provides customization for IDEs which main language is Java (e.g., IntelliJ IDEA, Android Studio).
</description>
<incompatible-with>com.intellij.modules.appcode.ide</incompatible-with>
@@ -20,6 +20,10 @@
<resource-bundle>messages.ActionsBundle</resource-bundle>
<extensions defaultExtensionNs="com.intellij">
<applicationActivity implementation="com.intellij.internal.statistic.updater.StatisticsJobsScheduler"/>
<applicationActivity implementation="com.intellij.internal.statistic.updater.StatisticsStateCollectorsScheduler"/>
<postStartupActivity implementation="com.intellij.internal.statistic.updater.StatisticsStateCollectorsScheduler$MyStartupActivity"/>
<applicationService serviceInterface="com.intellij.lang.IdeLanguageCustomization"
serviceImplementation="com.intellij.ide.customization.java.JavaIdeLanguageCustomization"
overrides="true"/>
@@ -0,0 +1,47 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide.customization.java;
import com.intellij.ide.ApplicationActivity;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
import com.intellij.openapi.util.Pair;
import com.intellij.testFramework.fixtures.BasePlatformTestCase;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Map;
public class FusSchedulersRegistrationTest extends BasePlatformTestCase {
// runs with intellij.java.tests classpath
public static final String PLUGIN_ID = "com.intellij.java.ide";
private final ExtensionPointName<ApplicationActivity> APP_ACTIVITIES_EP =
new ExtensionPointName<>("com.intellij.applicationActivity");
private final ExtensionPointName<ApplicationActivity> POST_STARTUP_ACTIVITIES_EP =
new ExtensionPointName<>("com.intellij.postStartupActivity");
public void testSchedulerActivitiesAvailable() {
var applicationActivities = getExtensionClasses(APP_ACTIVITIES_EP);
assertTrue("StatisticsJobsScheduler is missing",
applicationActivities.containsKey("com.intellij.internal.statistic.updater.StatisticsJobsScheduler"));
assertTrue("StatisticsStateCollectorsScheduler is missing",
applicationActivities.containsKey("com.intellij.internal.statistic.updater.StatisticsStateCollectorsScheduler"));
assertEquals(PLUGIN_ID, applicationActivities.get("com.intellij.internal.statistic.updater.StatisticsJobsScheduler"));
assertEquals(PLUGIN_ID, applicationActivities.get("com.intellij.internal.statistic.updater.StatisticsStateCollectorsScheduler"));
var postStartup = getExtensionClasses(POST_STARTUP_ACTIVITIES_EP);
assertTrue("StatisticsStateCollectorsScheduler$MyStartupActivity is missing",
postStartup.containsKey("com.intellij.internal.statistic.updater.StatisticsStateCollectorsScheduler$MyStartupActivity"));
assertEquals(PLUGIN_ID, applicationActivities.get("com.intellij.internal.statistic.updater.StatisticsStateCollectorsScheduler"));
}
// operates with EP adapters instead of instances, because those activities are not created in unit tests mode
// they throw ExtensionNotApplicableException.create() from init
private static @NotNull Map<@NotNull String, String> getExtensionClasses(ExtensionPointName<ApplicationActivity> epName) {
return ContainerUtil.map2Map(((ExtensionPointImpl<?>)epName.getPoint()).getSortedAdapters(),
it -> new Pair<>(it.getAssignableToClassName(), it.pluginDescriptor.getPluginId().getIdString()));
}
}
@@ -35,9 +35,6 @@ public interface PsiClass
/**
* Returns the fully qualified name of the class, or null for anonymous and local classes, and for type parameters.
* <p>
* To display the class name in the UI, taking into account the context where the class is defined,
* consider using {@link com.intellij.psi.presentation.java.ClassPresentationUtil#getNameForClass}.
*/
@Override
@Nullable @NlsSafe
@@ -15,6 +15,21 @@ public final class ClassPresentationUtil {
private ClassPresentationUtil() {
}
/**
* Returns a human-readable name for the class, including its containing context for local and anonymous classes.
* <p>
* Examples:
* <ul>
* <li>Top-level class: {@code "MyClass"} (or {@code "com.example.MyClass"} if qualified)</li>
* <li>Local class in method: {@code "LocalClass in method() in ContainerClass"}</li>
* <li>Anonymous class: {@code "Anonymous in method() in ContainerClass"}</li>
* <li>Enum constant initializer: {@code "CONSTANT in MyEnum"}</li>
* </ul>
*
* @param aClass the class to get the name for
* @param qualified if {@code true}, uses fully qualified names where applicable
* @return a presentable name suitable for UI display
*/
public static @Nls String getNameForClass(@NotNull PsiClass aClass, boolean qualified) {
if (aClass instanceof PsiImplicitClass) {
String name = aClass.getQualifiedName();
+1
View File
@@ -178,6 +178,7 @@ jvm_library(
"//platform/testFramework/junit5:junit5_test_lib",
"//platform/ml-api:ml",
"//java/ide-customization",
"//java/ide-customization:ide-customization_test_lib",
"//platform/backend/workspace",
"//platform/util",
"//platform/core-api:core",
@@ -1,14 +1,14 @@
<idea-plugin visibility="internal" package="com.intellij.notebooks.jupyter.core">
<idea-plugin visibility="internal">
<!-- Jupyter language -->
<extensions defaultExtensionNs="com.intellij">
<fileType name="Jupyter"
language="Jupyter"
extensions="ipynb"
implementationClass="com.intellij.notebooks.jupyter.core.jupyter.JupyterFileType"
fieldName="INSTANCE"/>
<lang.substitutor language="Jupyter"
implementationClass="com.intellij.notebooks.jupyter.core.jupyter.JupyterTemplateLanguageSubstitutor"/>
</extensions>
<extensions defaultExtensionNs="com.intellij">
<fileType name="Jupyter"
language="Jupyter"
extensions="ipynb"
implementationClass="com.intellij.notebooks.jupyter.core.jupyter.JupyterFileType"
fieldName="INSTANCE"/>
<lang.substitutor language="Jupyter"
implementationClass="com.intellij.notebooks.jupyter.core.jupyter.JupyterTemplateLanguageSubstitutor"/>
</extensions>
<applicationListeners>
@@ -5,6 +5,7 @@ import com.intellij.ide.plugins.cl.PluginAwareClassLoader
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.util.io.FileUtilRt
import com.intellij.openapi.util.io.toCanonicalPath
import com.intellij.util.PathUtil
import io.netty.channel.ChannelHandlerContext
import io.netty.handler.codec.http.EmptyHttpHeaders
@@ -12,8 +13,11 @@ import io.netty.handler.codec.http.FullHttpRequest
import io.netty.handler.codec.http.QueryStringDecoder
import org.apache.http.client.utils.URIBuilder
import org.jetbrains.ide.HttpRequestHandler
import java.io.File
import java.net.URL
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.exists
import kotlin.io.path.readBytes
import kotlin.io.path.toPath
/**
* "Web server" to serve Jupyter HTML
@@ -38,16 +42,17 @@ abstract class JupyterCefHttpHandlerBase(private val absolutePathFiles: Set<Stri
* Resources are in different folders when launched locally versus installation.
* This method handles this difference; see build scripts.
*/
private fun getResource(javaClass: Class<*>, path: String): URL {
private fun getResource(javaClass: Class<*>, folderPath: String): Path {
// After optimizations in PluginClassLoader, classLoader.getResource return null in debug,
// so we have additional logic with PluginClassLoader.pluginDescriptor. This is only for debugging purposes.
var url = javaClass.classLoader.getResource(path)
?: (javaClass.classLoader as? PluginAwareClassLoader)?.pluginDescriptor?.getPluginPath()?.normalize()?.resolve(path)?.toUri()?.toURL()
var path = javaClass.classLoader.getResource(folderPath)?.toURI()?.toPath()
?: (javaClass.classLoader as? PluginAwareClassLoader)?.pluginDescriptor?.getPluginPath()?.normalize()?.resolve(folderPath)
?.toUri()?.toPath()
//Fixme Newest version of hack for split mode.
// Frontend files "jupyter-web", is now not under lib folder, but directly under jupyter-plugin.
if (url.toString().contains("plugins/jupyter-plugin/lib/jupyter-web/")) {
url = URL(url.toString().replace("plugins/jupyter-plugin/lib/jupyter-web/", "plugins/jupyter-plugin/jupyter-web/"))
if (path != null && path.toCanonicalPath().contains("plugins/jupyter-plugin/lib/jupyter-web/")) {
path = Path(path.toCanonicalPath().replace("plugins/jupyter-plugin/lib/jupyter-web/", "plugins/jupyter-plugin/jupyter-web/"))
}
// In debug when we running remote-front via 'split (dev-build)' run config from IDE, we have:
@@ -55,19 +60,27 @@ abstract class JupyterCefHttpHandlerBase(private val absolutePathFiles: Set<Stri
// = out/classes/production/intellij.jupyter.plugin or out/classes/production/intellij.notebooks.plugin
// PathUtil.getJarPathForClass(javaClass) = out/classes/production/intellij.jupyter.core
// But our resources located not in out/classes but in out/dev-run
else if (url.toString().contains("out/classes/production/intellij.jupyter.plugin")) {
url = URL(url.toString().replace("out/classes/production/intellij.jupyter.plugin", "out/dev-run/Python/plugins/jupyter-plugin"))
else if (path != null && path.toCanonicalPath().contains("out/classes/production/intellij.jupyter.plugin")) {
path = Path(path.toCanonicalPath()
.replace("out/classes/production/intellij.jupyter.plugin", "out/dev-run/Python/plugins/jupyter-plugin"))
}
if (url != null) {
return url
if (path?.exists() == true) {
return path
}
val myPath = PathUtil.getJarPathForClass(javaClass)
val jarPath = Path(PathUtil.getJarPathForClass(javaClass))
// static/css/.. <--our resource
// lib/python.jar <--getJarPathForClass
val result = File(File(myPath).parentFile.parentFile, path)
return result.toURI().toURL()
val parentFromLibs = jarPath.parent?.parent?.resolve(folderPath)?.takeIf { it.exists() }
if (parentFromLibs != null)
return parentFromLibs
val parentFromLibsModules = jarPath.parent?.parent?.parent?.resolve(folderPath)?.takeIf { it.exists() }
if (parentFromLibsModules != null)
return parentFromLibsModules
throw IllegalStateException("Cannot find resource: $folderPath based on $jarPath")
}
}
@@ -1,4 +1,4 @@
<idea-plugin visibility="internal" package="com.intellij.notebooks.ui">
<idea-plugin visibility="internal">
<extensionPoints>
<extensionPoint qualifiedName="com.intellij.notebooks.ui.cellCaretTracker"
interface="com.intellij.notebooks.ui.editor.actions.command.mode.NotebookCellCaretTracker"
@@ -1,5 +1,5 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.notebooks.visualization;
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.notebooks.visualization.icons;
import com.intellij.ui.IconManager;
import org.jetbrains.annotations.NotNull;
@@ -1,4 +1,4 @@
<idea-plugin visibility="internal" package="com.intellij.notebooks.visualization">
<idea-plugin visibility="internal">
<dependencies>
<module name="intellij.notebooks.ui"/>
<plugin id="org.intellij.plugins.markdown"/>
@@ -1,6 +1,5 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:JvmName("EelProviderUtil")
package com.intellij.platform.eel.provider
import com.intellij.openapi.application.ApplicationManager
@@ -10,7 +9,6 @@ import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.progress.runBlockingMaybeCancellable
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.SystemInfo
import com.intellij.platform.eel.*
import com.intellij.platform.eel.annotations.MultiRoutingFileSystemPath
import com.intellij.platform.util.coroutines.mapNotNullConcurrent
@@ -172,7 +170,7 @@ fun Project.getEelDescriptor(): EelDescriptor {
@get:ApiStatus.Experimental
val localEel: LocalEelApi by lazy {
if (SystemInfo.isWindows) ApplicationManager.getApplication().service<LocalWindowsEelApi>()
if (OS.CURRENT == OS.Windows) ApplicationManager.getApplication().service<LocalWindowsEelApi>()
else ApplicationManager.getApplication().service<LocalPosixEelApi>()
}
@@ -198,9 +196,7 @@ data object LocalEelMachine : EelMachine {
return localEel
}
override fun ownsPath(path: Path): Boolean {
return path.getEelDescriptor() === LocalEelDescriptor
}
override fun ownsPath(path: Path): Boolean = path.getEelDescriptor() === LocalEelDescriptor
}
@ApiStatus.Experimental
@@ -210,9 +206,9 @@ data object LocalEelDescriptor : EelDescriptor {
override val name: @NonNls String = "Local: ${System.getProperty("os.name")}"
override val osFamily: EelOsFamily by lazy {
when {
SystemInfo.isWindows -> EelOsFamily.Windows
SystemInfo.isMac || SystemInfo.isLinux || SystemInfo.isFreeBSD -> EelOsFamily.Posix
when (OS.CURRENT) {
OS.Windows -> EelOsFamily.Windows
OS.macOS, OS.Linux, OS.FreeBSD -> EelOsFamily.Posix
else -> {
LOG.info("Eel is not supported on current platform")
EelOsFamily.Posix
@@ -224,7 +220,7 @@ data object LocalEelDescriptor : EelDescriptor {
@ApiStatus.Internal
interface EelProvider {
companion object {
val EP_NAME: ExtensionPointName<EelProvider> = ExtensionPointName<EelProvider>("com.intellij.eelProvider")
val EP_NAME: ExtensionPointName<EelProvider> = ExtensionPointName("com.intellij.eelProvider")
}
/**
@@ -260,4 +256,4 @@ fun EelApi.systemOs(): OS {
is EelPlatform.Windows -> OS.Windows
is EelPlatform.FreeBSD -> OS.FreeBSD
}
}
}
+12 -8
View File
@@ -215,6 +215,7 @@ f:com.intellij.ide.GeneralSettings
- f:isAutoSaveIfInactive():Z
- f:isBackgroundSync():Z
- f:isConfirmExit():Z
- f:isDeletingToBin():Z
- f:isReopenLastProject():Z
- f:isSaveOnFrameDeactivation():Z
- f:isSearchInBackground():Z
@@ -229,6 +230,7 @@ f:com.intellij.ide.GeneralSettings
- f:setBackgroundSync(Z):V
- f:setConfirmExit(Z):V
- f:setConfirmOpenNewProject(I):V
- f:setDeletingToBin(Z):V
- f:setInactiveTimeout(I):V
- f:setProcessCloseConfirmation(com.intellij.ide.ProcessCloseConfirmation):V
- f:setReopenLastProject(Z):V
@@ -260,6 +262,7 @@ f:com.intellij.ide.GeneralSettingsState
- confirmExit:Z
- confirmOpenNewProject2:java.lang.Integer
- defaultProjectDirectory:java.lang.String
- deleteToBin:Z
- inactiveTimeout:I
- isShowWelcomeScreen:Z
- isUseSafeWrite:Z
@@ -270,17 +273,18 @@ f:com.intellij.ide.GeneralSettingsState
- supportScreenReaders:Z
- useDefaultBrowser:Z
- <init>():V
- <init>(java.lang.String,java.lang.String,java.lang.Boolean,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,java.lang.Integer,com.intellij.ide.ProcessCloseConfirmation,I,Z):V
- b:<init>(java.lang.String,java.lang.String,java.lang.Boolean,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,java.lang.Integer,com.intellij.ide.ProcessCloseConfirmation,I,Z,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- <init>(java.lang.String,java.lang.String,java.lang.Boolean,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,java.lang.Integer,com.intellij.ide.ProcessCloseConfirmation,I,Z):V
- b:<init>(java.lang.String,java.lang.String,java.lang.Boolean,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,java.lang.Integer,com.intellij.ide.ProcessCloseConfirmation,I,Z,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:component1():java.lang.String
- f:component10():Z
- f:component11():Z
- f:component12():Z
- f:component13():Z
- f:component14():java.lang.Integer
- f:component15():com.intellij.ide.ProcessCloseConfirmation
- f:component16():I
- f:component17():Z
- f:component14():Z
- f:component15():java.lang.Integer
- f:component16():com.intellij.ide.ProcessCloseConfirmation
- f:component17():I
- f:component18():Z
- f:component2():java.lang.String
- f:component3():java.lang.Boolean
- f:component4():Z
@@ -289,8 +293,8 @@ f:com.intellij.ide.GeneralSettingsState
- f:component7():Z
- f:component8():Z
- f:component9():Z
- f:copy(java.lang.String,java.lang.String,java.lang.Boolean,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,java.lang.Integer,com.intellij.ide.ProcessCloseConfirmation,I,Z):com.intellij.ide.GeneralSettingsState
- bs:copy$default(com.intellij.ide.GeneralSettingsState,java.lang.String,java.lang.String,java.lang.Boolean,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,java.lang.Integer,com.intellij.ide.ProcessCloseConfirmation,I,Z,I,java.lang.Object):com.intellij.ide.GeneralSettingsState
- f:copy(java.lang.String,java.lang.String,java.lang.Boolean,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,java.lang.Integer,com.intellij.ide.ProcessCloseConfirmation,I,Z):com.intellij.ide.GeneralSettingsState
- bs:copy$default(com.intellij.ide.GeneralSettingsState,java.lang.String,java.lang.String,java.lang.Boolean,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,Z,java.lang.Integer,com.intellij.ide.ProcessCloseConfirmation,I,Z,I,java.lang.Object):com.intellij.ide.GeneralSettingsState
- equals(java.lang.Object):Z
- hashCode():I
e:com.intellij.ide.ProcessCloseConfirmation
@@ -1,6 +1,10 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.ide
import com.intellij.ide.GeneralSettings.Companion.OPEN_PROJECT_ASK
import com.intellij.ide.GeneralSettings.Companion.OPEN_PROJECT_NEW_WINDOW
import com.intellij.ide.GeneralSettings.Companion.OPEN_PROJECT_SAME_WINDOW
import com.intellij.ide.GeneralSettings.Companion.OPEN_PROJECT_SAME_WINDOW_ATTACH
import com.intellij.ide.ui.UINumericRange
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.application.ApplicationManager
@@ -17,7 +21,6 @@ import org.intellij.lang.annotations.MagicConstant
import org.jetbrains.annotations.ApiStatus.Internal
import org.jetbrains.annotations.SystemDependent
@State(name = "GeneralSettings", storages = [Storage(GeneralSettings.IDE_GENERAL_XML)], category = SettingsCategory.SYSTEM)
class GeneralSettings : PersistentStateComponent<GeneralSettingsState> {
private var state = GeneralSettingsState()
@@ -38,6 +41,12 @@ class GeneralSettings : PersistentStateComponent<GeneralSettingsState> {
state.reopenLastProject = value
}
var isDeletingToBin: Boolean
get() = state.deleteToBin
set(value) {
state.deleteToBin = value
}
var isSyncOnFrameActivation: Boolean
get() = state.autoSyncFiles
set(value) {
@@ -138,7 +147,6 @@ class GeneralSettings : PersistentStateComponent<GeneralSettingsState> {
const val OPEN_PROJECT_NEW_WINDOW: Int = 0
const val OPEN_PROJECT_SAME_WINDOW: Int = 1
const val OPEN_PROJECT_SAME_WINDOW_ATTACH: Int = 2
@Suppress("SpellCheckingInspection")
const val SUPPORT_SCREEN_READERS: String = "ide.support.screenreaders.enabled"
val SAVE_FILES_AFTER_IDLE_SEC: UINumericRange = UINumericRange(15, 1, 300)
@@ -214,6 +222,8 @@ data class GeneralSettingsState(
@JvmField
var reopenLastProject: Boolean = true,
@JvmField
var deleteToBin: Boolean = true,
@JvmField
var autoSyncFiles: Boolean = true,
@JvmField
var backgroundSyncFiles: Boolean = true,
@@ -254,4 +264,4 @@ private const val CONFIGURED_PROPERTY = "GeneralSettings.initiallyConfigured"
private val SUPPORT_SCREEN_READERS_OVERRIDDEN = System.getProperty(GeneralSettings.SUPPORT_SCREEN_READERS)?.toBoolean()
@Internal
fun isSupportScreenReadersOverridden(): Boolean = SUPPORT_SCREEN_READERS_OVERRIDDEN != null
fun isSupportScreenReadersOverridden(): Boolean = SUPPORT_SCREEN_READERS_OVERRIDDEN != null
@@ -6,6 +6,7 @@ import com.intellij.history.LocalHistory;
import com.intellij.history.LocalHistoryAction;
import com.intellij.ide.DataManager;
import com.intellij.ide.DeleteProvider;
import com.intellij.ide.GeneralSettings;
import com.intellij.ide.IdeBundle;
import com.intellij.ide.actions.RevealFileAction;
import com.intellij.lang.LangBundle;
@@ -20,7 +21,6 @@ import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileEditor.impl.NonProjectFileWritingAccessProvider;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
@@ -49,7 +49,9 @@ import com.intellij.testFramework.LightVirtualFile;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.PlatformNioHelper;
import com.intellij.util.io.ReadOnlyAttributeUtil;
import com.intellij.util.io.TrashBin;
import com.intellij.util.ui.IoErrorText;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -59,7 +61,6 @@ import java.io.IOException;
import java.nio.file.FileSystemException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
@@ -235,11 +236,8 @@ public final class DeleteHandler {
}
private static boolean isLocalFile(PsiElement e) {
if (e instanceof PsiFileSystemItem) {
VirtualFile file = ((PsiFileSystemItem)e).getVirtualFile();
if (file != null && file.isInLocalFileSystem()) return true;
}
return false;
var file = e instanceof PsiFileSystemItem fsItem ? fsItem.getVirtualFile() : null;
return file != null && file.isInLocalFileSystem();
}
private static boolean clearFileReadOnlyFlags(Project project, PsiElement elementToDelete) {
@@ -307,18 +305,18 @@ public final class DeleteHandler {
}
private static void doDeleteFiles(Project project, PsiElement[] fileElements) {
for (PsiElement file : fileElements) {
for (var file : fileElements) {
if (!clearFileReadOnlyFlags(project, file)) return;
}
LocalFilesDeleteTask task = new LocalFilesDeleteTask(project, fileElements);
var task = new LocalFilesDeleteTask(project, fileElements);
ProgressManager.getInstance().run(task);
if (task.error != null) {
String file = task.error instanceof FileSystemException ? ((FileSystemException)task.error).getFile() : null;
var file = task.error instanceof FileSystemException ? ((FileSystemException)task.error).getFile() : null;
if (file != null) {
String message = IoErrorText.message(task.error), yes = RevealFileAction.getActionName(), no = CommonBundle.getCloseButtonText();
if (Messages.showYesNoDialog(project, message, CommonBundle.getErrorTitle(), yes, no, Messages.getErrorIcon()) == Messages.YES) {
RevealFileAction.openFile(Paths.get(file));
RevealFileAction.openFile(Path.of(file));
}
}
else {
@@ -330,13 +328,14 @@ public final class DeleteHandler {
}
if (!task.processed.isEmpty()) {
ApplicationManager.getApplication().runWriteAction(() -> {
for (PsiElement fileElement : task.processed) {
for (var fileElement : task.processed) {
try {
fileElement.delete();
}
catch (IncorrectOperationException e) {
ApplicationManager.getApplication().invokeLater(
() -> Messages.showMessageDialog(project, e.getMessage(), CommonBundle.getErrorTitle(), Messages.getErrorIcon()));
() -> Messages.showMessageDialog(project, e.getMessage(), CommonBundle.getErrorTitle(), Messages.getErrorIcon())
);
}
}
});
@@ -383,11 +382,11 @@ public final class DeleteHandler {
private static final class LocalFilesDeleteTask extends Task.Modal {
private final PsiElement[] myFileElements;
List<PsiElement> processed = new ArrayList<>();
VirtualFile aborted = null;
Throwable error = null;
private final List<PsiElement> processed = new ArrayList<>();
private VirtualFile aborted = null;
private Throwable error = null;
LocalFilesDeleteTask(Project project, PsiElement[] fileElements) {
private LocalFilesDeleteTask(Project project, PsiElement[] fileElements) {
super(project, IdeBundle.message("progress.deleting"), true);
myFileElements = fileElements;
}
@@ -396,9 +395,12 @@ public final class DeleteHandler {
public void run(@NotNull ProgressIndicator indicator) {
try {
indicator.setText(IdeBundle.message("progress.counting.files"));
Ref<Integer> curFileCount = new Ref<>(0);
for (PsiElement element : myFileElements) {
VirtualFile file = ((PsiFileSystemItem)element).getVirtualFile();
var toBin = TrashBin.isSupported() && GeneralSettings.getInstance().isDeletingToBin();
var curFileCount = new Ref<>(0);
for (var element : myFileElements) {
var file = ((PsiFileSystemItem)element).getVirtualFile();
Files.walkFileTree(file.toNioPath(), new NioFiles.StatsCollectingVisitor() {
@Override
protected void countDirectory(Path dir, BasicFileAttributes attrs) {
@@ -416,35 +418,38 @@ public final class DeleteHandler {
}
});
}
final int totalFileCount = curFileCount.get();
int totalFileCount = curFileCount.get();
curFileCount.set(0);
indicator.setIndeterminate(totalFileCount <= 1); // don't show progression when deleting single file
for (int i = 0; i < myFileElements.length; i++) {
PsiElement element = myFileElements[i];
var element = myFileElements[i];
if (indicator.isCanceled()) break;
indicator.setFraction((double) i / myFileElements.length);
VirtualFile file = ((PsiFileSystemItem)element).getVirtualFile();
var file = ((PsiFileSystemItem)element).getVirtualFile();
aborted = file;
Path path = file.toNioPath();
var path = file.toNioPath();
indicator.setText(path.toString());
try {
if (toBin && PlatformNioHelper.isLocal(path)) {
TrashBin.moveToTrash(path);
}
else {
NioFiles.deleteRecursively(path, p -> {
curFileCount.set(curFileCount.get() + 1);
indicator.checkCanceled();
indicator.setFraction((double)curFileCount.get() / totalFileCount);
indicator.setText2(path.relativize(p).toString());
curFileCount.set(curFileCount.get() + 1);
});
processed.add(element);
aborted = null;
}
catch (ProcessCanceledException ignored) { }
processed.add(element);
aborted = null;
}
}
catch (Throwable t) {
Logger.getInstance(getClass()).info(t);
error = t;
catch (IOException e) {
Logger.getInstance(getClass()).info(e);
error = e;
}
}
}
@@ -13,28 +13,29 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.PropertyKey;
import java.util.HashMap;
import java.util.Map;
@ApiStatus.Internal
public final class DeleteUtil {
private DeleteUtil() { }
public static @NlsContexts.DialogMessage String generateWarningMessage(@PropertyKey(resourceBundle = IdeBundle.BUNDLE) String key,
PsiElement @NotNull [] elements) {
public static @NlsContexts.DialogMessage String generateWarningMessage(
@PropertyKey(resourceBundle = IdeBundle.BUNDLE) String key,
@NotNull PsiElement @NotNull [] elements
) {
if (elements.length == 1) {
String name = ElementDescriptionUtil.getElementDescription(elements[0], DeleteNameDescriptionLocation.INSTANCE);
String type = ElementDescriptionUtil.getElementDescription(elements[0], DeleteTypeDescriptionLocation.SINGULAR);
var name = ElementDescriptionUtil.getElementDescription(elements[0], DeleteNameDescriptionLocation.INSTANCE);
var type = ElementDescriptionUtil.getElementDescription(elements[0], DeleteTypeDescriptionLocation.SINGULAR);
return IdeBundle.message(key, type + (StringUtil.isEmptyOrSpaces(name) ? "" : " \"" + name + '"'));
}
Map<String, Integer> countMap = FactoryMap.create(k -> 0);
Map<String, String> pluralToSingular = new HashMap<>();
int directoryCount = 0;
String containerType = null;
for (PsiElement elementToDelete : elements) {
String type = ElementDescriptionUtil.getElementDescription(elementToDelete, DeleteTypeDescriptionLocation.PLURAL);
var countMap = FactoryMap.<String, Integer>create(k -> 0);
var pluralToSingular = new HashMap<String, String>();
var directoryCount = 0;
var containerType = (String)null;
for (var elementToDelete : elements) {
var type = ElementDescriptionUtil.getElementDescription(elementToDelete, DeleteTypeDescriptionLocation.PLURAL);
pluralToSingular.put(type, ElementDescriptionUtil.getElementDescription(elementToDelete, DeleteTypeDescriptionLocation.SINGULAR));
int oldCount = countMap.get(type).intValue();
var oldCount = countMap.get(type).intValue();
countMap.put(type, oldCount+1);
if (elementToDelete instanceof PsiDirectoryContainer) {
containerType = type;
@@ -42,13 +43,13 @@ public final class DeleteUtil {
}
}
StringBuilder buffer = new StringBuilder();
for (Map.Entry<String, Integer> entry : countMap.entrySet()) {
var buffer = new StringBuilder();
for (var entry : countMap.entrySet()) {
if (!buffer.isEmpty()) {
buffer.append(" ").append(IdeBundle.message("prompt.delete.and")).append(" ");
}
int count = entry.getValue().intValue();
var count = entry.getValue().intValue();
buffer.append(count).append(" ");
if (count == 1) {
buffer.append(pluralToSingular.get(entry.getKey()));
@@ -723,7 +723,7 @@ public final class FileBasedIndexImpl extends FileBasedIndexEx {
}
IntSet orphanDirtyFilesFromThisSession = getAllDirtyFiles(null);
int maxSize = Registry.intValue("maximum.size.of.orphan.dirty.files.queue");
int maxSize = Registry.intValue("maximum.size.of.orphan.dirty.files.queue", 1000000);
orphanDirtyFileIds.plus(orphanDirtyFilesFromThisSession).takeLast(maxSize).store(vfsCreationStamp);
// remove events from event merger, so they don't show up after FileBasedIndex is restarted using tumbler
getChangedFilesCollector().clear();
@@ -70,7 +70,7 @@ title.general=System Settings
editbox.default.encoding.for.properties.files=Default encoding for properties files:
checkbox.transparent.native.to.ascii.conversion=Transparent native-to-ascii conversion
checkbox.confirm.application.exit=Confirm before exiting the IDE
settings.general.autosave=Autosave
settings.general.files=Files
checkbox.save.files.automatically=Save files if the IDE is idle for
label.inactive.timeout.sec= seconds
checkbox.save.files.on.frame.deactivation=Save files when switching to a different application or a built-in terminal
@@ -79,6 +79,7 @@ label.autosave.comment=Autosave cannot be disabled completely. <a>How it works</
settings.general.synchronization=Sync external changes:
checkbox.synchronize.files.on.frame.activation=When switching to the IDE window or opening an editor tab
checkbox.synchronize.files.in.background=Periodically when the IDE is inactive (experimental)
checkbox.delete.to.trash.bin=Delete moves files to Bin
checkbox.different.toolwindow.background=Different tool window background
different.toolwindow.background.comment=Use lighter color in the dark theme and darker color in the light theme as a background
title.accessibility=Accessibility
@@ -2033,7 +2034,6 @@ large.file.editor.name=Large File Editor
large.file.preview.notification=The file is too large ({0}). Showing a read-only preview of the first {1}.
dialog.message.could.not.erase.files.or.folders.0.1=Could not erase files or folders:\n{0}{1}
command.deleting.files=Deleting Files
progress.title.deleting.files=Deleting Files\u2026
dialog.title.choose.plugins.to.install.or.enable=Choose Plugins to Install or Enable
action.text.merge.tabs.to.group=Merge Tabs to ''{0}'' Group
action.text.split.group=Split ''{0}'' Group
@@ -1,14 +0,0 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.logCollector
import com.intellij.ide.ui.IdeUiService
import com.intellij.openapi.components.Service
import com.intellij.openapi.project.Project
import org.jetbrains.annotations.ApiStatus
@Deprecated("Use 'IdeUiService.invokeLogCollectionAction' instead")
@ApiStatus.Internal
@Service(Service.Level.APP)
class CollectZippedLogsService {
fun collectZippedLogs(project: Project?): Unit = IdeUiService.getInstance().invokeLogCollectionAction(project)
}
@@ -238,7 +238,7 @@ object ActionUtil {
*/
@JvmStatic
fun updateAction(action: AnAction, e: AnActionEvent): AnActionResult {
val checkDumb = Registry.`is`("actionSystem.update.dumb.mode.check.awareness")
val checkDumb = Registry.`is`("actionSystem.update.dumb.mode.check.awareness", false)
val presentation = e.presentation
if (LightEdit.owns(e.project) && !isActionLightEditCompatible(action)) {
presentation.isEnabledAndVisible = false
@@ -1,6 +1,4 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@file:Suppress("ReplacePutWithAssignment")
package com.intellij.platform.ide.bootstrap
import com.intellij.diagnostic.StartUpMeasurer
@@ -74,8 +72,8 @@ internal suspend fun configureCssUiDefaults() {
span("html style patching") {
// create a separate copy for each case
val globalStyleSheet = createGlobalStyleSheet()
uiDefaults.put("javax.swing.JLabel.userStyleSheet", globalStyleSheet)
uiDefaults.put("HTMLEditorKit.jbStyleSheet", globalStyleSheet)
uiDefaults["javax.swing.JLabel.userStyleSheet"] = globalStyleSheet
uiDefaults["HTMLEditorKit.jbStyleSheet"] = globalStyleSheet
}
}
}
@@ -130,7 +128,6 @@ private suspend fun initAwtToolkit(busyThread: Thread) {
checkHiDPISettings()
blockATKWrapper()
@Suppress("SpellCheckingInspection")
System.setProperty("sun.awt.noerasebackground", "true")
// mute system Cmd+`/Cmd+Shift+` shortcuts on macOS to avoid a conflict with corresponding platform actions (JBR-specific option)
if (System.getProperty("apple.awt.captureNextAppWinKey") == null) {
@@ -196,14 +193,13 @@ private suspend fun replaceIdeEventQueue(isHeadless: Boolean) {
*/
private fun blockATKWrapper() {
// the registry must not be used here, because this method is called before application loading
@Suppress("SpellCheckingInspection")
if (OS.CURRENT != OS.Linux || !System.getProperty("linux.jdk.accessibility.atkwrapper.block", "true").toBoolean()) {
return
}
val activity = StartUpMeasurer.startActivity("atk wrapper blocking")
if (ScreenReader.isEnabled(ScreenReader.ATK_WRAPPER)) {
// Replacing `AtkWrapper` with a fake `Object`. It'll be instantiated, and garbage collected right away, a NOP.
// Replacing the ATK wrapper with a fake object. It'll be instantiated and garbage-collected right away; a NOP.
System.setProperty("javax.accessibility.assistive_technologies", "java.lang.Object")
logger<AppStarter>().info("${ScreenReader.ATK_WRAPPER} is blocked, see IDEA-149219")
}
@@ -17,6 +17,7 @@ import com.intellij.platform.ide.core.customization.ProjectLifecycleUiCustomizat
import com.intellij.ui.IdeUICustomization
import com.intellij.ui.dsl.builder.*
import com.intellij.util.PlatformUtils
import com.intellij.util.io.TrashBin
private val model: GeneralSettings
get() = GeneralSettings.getInstance()
@@ -25,6 +26,8 @@ private val myChkReopenLastProject: CheckboxDescriptor
get() = CheckboxDescriptor(IdeUICustomization.getInstance().projectMessage("checkbox.reopen.last.project.on.startup"), model::isReopenLastProject)
private val myConfirmExit: CheckboxDescriptor
get() = CheckboxDescriptor(IdeBundle.message("checkbox.confirm.application.exit"), model::isConfirmExit)
private val myDeleteToBin
get() = CheckboxDescriptor(IdeBundle.message("checkbox.delete.to.trash.bin"), model::isDeletingToBin)
private val myChkSyncOnFrameActivation
get() = CheckboxDescriptor(IdeBundle.message("checkbox.synchronize.files.on.frame.activation"), model::isSyncOnFrameActivation)
private val myChkSyncInBackground
@@ -41,6 +44,7 @@ internal val allOptionDescriptors: List<BooleanOptionDescription>
listOfNotNull(
myChkReopenLastProject.takeIf { ProjectLifecycleUiCustomization.getInstance().canReopenProjectOnStartup },
myConfirmExit.takeIf { IdeLifecycleUiCustomization.getInstance().canShowExitConfirmation },
myDeleteToBin,
myChkSyncOnFrameActivation,
myChkSyncInBackground,
myChkSaveOnFrameDeactivation,
@@ -113,7 +117,12 @@ internal class GeneralSettingsConfigurable :
}
}
group(IdeBundle.message("settings.general.autosave")) {
group(IdeBundle.message("settings.general.files")) {
if (TrashBin.isSupported()) {
row {
checkBox(myDeleteToBin)
}.bottomGap(BottomGap.SMALL)
}
row {
val autoSaveCheckbox = checkBox(myChkAutoSaveIfInactive).gap(RightGap.SMALL)
intTextField(GeneralSettings.SAVE_FILES_AFTER_IDLE_SEC.asRange())
@@ -1053,7 +1053,7 @@ private suspend fun fireLastProjectsReopenedEvent(activeProject: Project) {
}
}
private fun isUseProjectFrameAsSplash() = Registry.`is`("ide.project.frame.as.splash")
private fun isUseProjectFrameAsSplash() = Registry.`is`("ide.project.frame.as.splash", false)
private const val IDEA_PROJECT_FILE_EXTENSION = ".ipr"
@@ -17,20 +17,20 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
public class DeleteAction extends AnAction implements DumbAware, LightEditCompatible,
ActionRemoteBehaviorSpecification.FrontendOtherwiseBackend {
public class DeleteAction extends AnAction implements DumbAware, LightEditCompatible, ActionRemoteBehaviorSpecification.FrontendOtherwiseBackend {
private static final Logger LOG = Logger.getInstance(DeleteAction.class);
public DeleteAction() { }
@SuppressWarnings("ActionPresentationInstantiatedInCtor")
public DeleteAction(@NlsActions.ActionText String text, @NlsActions.ActionDescription String description, Icon icon) {
super(text, description, icon);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
DataContext dataContext = e.getDataContext();
DeleteProvider provider = getDeleteProvider(dataContext);
var dataContext = e.getDataContext();
var provider = getDeleteProvider(dataContext);
if (provider == null) return;
try {
provider.deleteElement(dataContext);
@@ -46,7 +46,7 @@ public class DeleteAction extends AnAction implements DumbAware, LightEditCompat
@Override
public void update(@NotNull AnActionEvent e) {
Presentation presentation = e.getPresentation();
var presentation = e.getPresentation();
presentation.putClientProperty(ActionRemoteBehavior.SKIP_FALLBACK_UPDATE, null);
if (e.isFromContextMenu()) {
@@ -55,8 +55,9 @@ public class DeleteAction extends AnAction implements DumbAware, LightEditCompat
else {
presentation.setText(IdeBundle.messagePointer("action.delete"));
}
if (e.isFromActionToolbar() && e.getPresentation().getIcon() == null) {
e.getPresentation().setIcon(IconUtil.getRemoveIcon());
if (e.isFromActionToolbar() && presentation.getIcon() == null) {
presentation.setIcon(IconUtil.getRemoveIcon());
}
if (e.getProject() == null) {
@@ -67,12 +68,12 @@ public class DeleteAction extends AnAction implements DumbAware, LightEditCompat
CopyAction.updateWithProvider(e, getDeleteProvider(e.getDataContext()), false, provider -> {
// if a provider is found on the frontend, don't look for it on the backend
presentation.putClientProperty(ActionRemoteBehavior.SKIP_FALLBACK_UPDATE, true);
boolean isPopupPlace = e.isFromContextMenu();
boolean enabled = provider.canDeleteElement(e.getDataContext());
var isPopupPlace = e.isFromContextMenu();
var enabled = provider != null && provider.canDeleteElement(e.getDataContext());
presentation.setEnabled(enabled);
presentation.setVisible(!isPopupPlace || enabled);
if (provider instanceof TitledHandler) {
presentation.setText(((TitledHandler)provider).getActionTitle());
if (provider instanceof TitledHandler th) {
presentation.setText(th.getActionTitle());
}
});
}
@@ -81,4 +82,4 @@ public class DeleteAction extends AnAction implements DumbAware, LightEditCompat
public @NotNull ActionUpdateThread getActionUpdateThread() {
return ActionUpdateThread.BGT;
}
}
}
@@ -42,7 +42,7 @@ public abstract class WindowAction extends AnAction implements ActionRemoteBehav
@Nullable Component component = event.getData(PlatformCoreDataKeys.CONTEXT_COMPONENT);
Window window = ComponentUtil.getWindow(component);
boolean enabled = isEnabledFor(window);
if (enabled && Registry.is("no.window.actions.in.editor")) {
if (enabled && Registry.is("no.window.actions.in.editor", false)) {
Editor editor = event.getData(CommonDataKeys.EDITOR);
enabled = editor == null || !editor.getContentComponent().hasFocus();
}
@@ -4,6 +4,7 @@ package com.intellij.openapi.application.impl.islands
import com.intellij.ide.ProjectWidgetGradientLocationService
import com.intellij.ide.ProjectWindowCustomizerService
import com.intellij.ide.ui.GradientTextureCache
import com.intellij.openapi.application.impl.InternalUICustomization
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.AbstractPainter
@@ -36,11 +37,25 @@ private val islandsGradientColors = setOf(
"StatusBar.background",
)
private val islandsToolWindowGradientColors = setOf(
"ToolWindow.background",
"ToolWindow.header.background",
"ToolWindow.Header.inactiveBackground",
)
internal fun isIslandsGradientColor(paint: Paint?): Boolean {
val colorName = (paint as? JBColor)?.name ?: return false
if (isColorIslandGradient() && colorName in islandsToolWindowGradientColors) {
return true
}
return colorName in islandsGradientColors
}
private fun isIslandsToolWindowGradientColor(paint: Paint?): Boolean {
val colorName = (paint as? JBColor)?.name ?: return false
return colorName in islandsToolWindowGradientColors
}
internal class IslandsGradientPainter(private val frame: IdeFrame, private val mainColor: Color, private val enabled: () -> Boolean) : AbstractPainter() {
private val projectWindowCustomizer = ProjectWindowCustomizerService.getInstance()
@@ -75,7 +90,7 @@ internal fun islandsGradientPaint(frame: IdeFrame, mainColor: Color, projectWind
val project = frame.project ?: return
if (Registry.`is`("idea.islands.color.gradient.enabled", false)) {
if (isColorIslandGradient()) {
doColorGradientPaint(project, projectWindowCustomizer, component, g)
}
else {
@@ -83,6 +98,11 @@ internal fun islandsGradientPaint(frame: IdeFrame, mainColor: Color, projectWind
}
}
internal fun isColorIslandGradient(): Boolean = Registry.`is`("idea.islands.color.gradient.enabled", false)
// TODO: replace isRoundedTabDuringDrag to publuc API to check islands theme
internal fun isColorIslandGradientAvailable(): Boolean = isColorIslandGradient() && InternalUICustomization.getInstance()?.isRoundedTabDuringDrag == true
private fun doGradientPaint(frame: IdeFrame, mainColor: Color, project: Project, projectWindowCustomizer: ProjectWindowCustomizerService,
component: Component, g: Graphics2D) {
val centerX = project.service<ProjectWidgetGradientLocationService>().gradientOffsetRelativeToRootPane
@@ -185,22 +205,30 @@ private fun doColorGradientPaint(project: Project, projectWindowCustomizer: Proj
val initialComposite = g.composite
val info = projectWindowCustomizer.getProjectGradients(project)
if (SwingUtilities.getWindowAncestor(component)?.isActive == false) {
if (isIslandsToolWindowGradientColor(g.paint)) {
g.composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, JBUI.getFloat("Island.toolWindowAlpha", 0.2f))
}
else if (SwingUtilities.getWindowAncestor(component)?.isActive == false) {
islandsInactiveFrameGraphics2D?.preserveComposite = true
g.composite = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, islandsInactiveAlpha)
}
g.paint = LinearGradientPaint(0f, 0f, component.width.toFloat(), component.height.toFloat(),
val width = component.width
val height = component.height
val widthF = width.toFloat()
val heightF = height.toFloat()
g.paint = LinearGradientPaint(0f, 0f, widthF, heightF,
floatArrayOf(info.getDiagonalFraction1(0f), info.getDiagonalFraction2(0.13f),
info.getDiagonalFraction3(0.3f), info.getDiagonalFraction4(1f)),
arrayOf(info.diagonalColor1, info.diagonalColor2, info.diagonalColor3, info.diagonalColor4))
g.fillRect(0, 0, component.width, component.height)
g.fillRect(0, 0, width, height)
val ovalRadius = component.width / 4f
val ovalWidth = component.width / 2
val ovalCenterX = component.width * 0.2f
val ovalRadius = width / 4f
val ovalWidth = width / 2
val ovalCenterX = width * 0.2f
val ovalCenterY = 36f
g.paint = RadialGradientPaint(ovalCenterX, ovalCenterY, ovalRadius,
@@ -209,13 +237,13 @@ private fun doColorGradientPaint(project: Project, projectWindowCustomizer: Proj
g.fillOval((ovalCenterX - ovalRadius).toInt(), (ovalCenterY - ovalRadius).toInt(), ovalWidth, ovalWidth)
g.paint = GradientPaint(0f, 0f, info.horizontalColor1, component.width.toFloat(), 0f, info.horizontalColor2)
g.paint = GradientPaint(0f, 0f, info.horizontalColor1, widthF, 0f, info.horizontalColor2)
g.fillRect(0, 0, component.width, component.height)
g.fillRect(0, 0, width, height)
g.paint = GradientPaint(0f, 0f, info.verticalColor1, 0f, component.height.toFloat(), info.verticalColor2)
g.paint = GradientPaint(0f, 0f, info.verticalColor1, 0f, heightF, info.verticalColor2)
g.fillRect(0, 0, component.width, component.height)
g.fillRect(0, 0, width, height)
g.composite = initialComposite
islandsInactiveFrameGraphics2D?.preserveComposite = false
@@ -171,7 +171,9 @@ internal class IslandsUICustomization : InternalUICustomization() {
if (isManyIslandEnabled) {
background = JBUI.CurrentTheme.ToolWindow.background()
createToolWindowBorderPainter(toolWindow, this)
configureBackgroundPainting(child, recursive = false)
if (!isColorIslandGradient()) {
configureBackgroundPainting(child, recursive = false)
}
}
else {
border = originalBorderBuilder()
@@ -433,8 +435,10 @@ internal class IslandsUICustomization : InternalUICustomization() {
createToolWindowBorderPainter(toolwindow, holder)
clearParentNoBackground(holder)
for (child in holder.components) {
configureBackgroundPainting(child as JComponent, recursive = true)
if (!isColorIslandGradient()) {
for (child in holder.components) {
configureBackgroundPainting(child as JComponent, recursive = true)
}
}
}
@@ -1,7 +1,8 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.fileChooser.actions;
import com.intellij.CommonBundle;
import com.intellij.ide.GeneralSettings;
import com.intellij.ide.IdeBundle;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.PlatformCoreDataKeys;
@@ -17,6 +18,9 @@ import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.NlsActions;
import com.intellij.openapi.util.io.NioFiles;
import com.intellij.ui.UIBundle;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.PlatformNioHelper;
import com.intellij.util.io.TrashBin;
import com.intellij.util.ui.IoErrorText;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
@@ -27,23 +31,19 @@ import java.io.IOException;
import java.nio.file.Path;
public final class FileDeleteAction extends FileChooserAction {
/** @deprecated please use {@link FileDeleteAction#FileDeleteAction(String, String, Icon)} instead */
@Deprecated(forRemoval = true)
@SuppressWarnings("unused")
public FileDeleteAction() { }
@SuppressWarnings("unused")
@SuppressWarnings({"unused", "ActionPresentationInstantiatedInCtor"})
public FileDeleteAction(@NlsActions.ActionText String text, @NlsActions.ActionDescription String description, Icon icon) {
super(text, description, icon);
}
@Override
protected void update(@NotNull FileChooserPanel panel, @NotNull AnActionEvent e) {
var visible = isEnabled(e);
var visible = isVisible(e);
e.getPresentation().setVisible(visible);
e.getPresentation().setEnabled(
visible &&
!(e.getInputEvent() instanceof KeyEvent && e.getData(PlatformCoreDataKeys.CONTEXT_COMPONENT) instanceof JTextField) && // do not override text deletion
!panel.selectedPaths().isEmpty());
e.getPresentation().setEnabled(visible && isEnabled(e) && !panel.selectedPaths().isEmpty());
}
@Override
@@ -52,24 +52,37 @@ public final class FileDeleteAction extends FileChooserAction {
if (paths.isEmpty()) return;
var project = e.getProject();
var ok = MessageDialogBuilder.yesNo(UIBundle.message("file.chooser.delete.title"), UIBundle.message("file.chooser.delete.confirm"))
.yesText(ApplicationBundle.message("button.delete")).noText(CommonBundle.getCancelButtonText())
.icon(UIUtil.getWarningIcon())
.ask(project);
if (!ok) return;
var toBin = TrashBin.isSupported() && GeneralSettings.getInstance().isDeletingToBin();
if (!(toBin && ContainerUtil.all(paths, PlatformNioHelper::isLocal))) {
var ok = MessageDialogBuilder.yesNo(UIBundle.message("file.chooser.delete.title"), UIBundle.message("file.chooser.delete.confirm"))
.yesText(ApplicationBundle.message("button.delete"))
.noText(CommonBundle.getCancelButtonText())
.icon(UIUtil.getWarningIcon())
.ask(project);
if (!ok) return;
}
try {
var progress = IdeBundle.message("progress.deleting");
panel.reloadAfter(() -> ProgressManager.getInstance().run(new Task.WithResult<Path, IOException>(e.getProject(), panel.getComponent(), progress, true) {
panel.reloadAfter(() -> ProgressManager.getInstance().run(new Task.WithResult<Path, IOException>(project, panel.getComponent(), progress, true) {
@Override
protected Path compute(@NotNull ProgressIndicator indicator) throws IOException {
indicator.setIndeterminate(false);
var i = 0;
for (var path : paths) {
if (indicator.isCanceled()) break;
indicator.setText(path.toString());
NioFiles.deleteRecursively(path, p -> {
indicator.checkCanceled();
indicator.setText2(path.relativize(p).toString());
});
indicator.setFraction((double)i++ / paths.size());
if (toBin && PlatformNioHelper.isLocal(path)) {
TrashBin.moveToTrash(path);
}
else {
NioFiles.deleteRecursively(path, p -> {
indicator.checkCanceled();
indicator.setText2(path.relativize(p).toString());
});
}
}
return null;
}
@@ -80,18 +93,19 @@ public final class FileDeleteAction extends FileChooserAction {
}
}
private static boolean isEnabled(AnActionEvent e) {
private static boolean isVisible(AnActionEvent e) {
return e.getData(FileChooserKeys.DELETE_ACTION_AVAILABLE) != Boolean.FALSE;
}
private static boolean isEnabled(@NotNull AnActionEvent e) {
return !(e.getInputEvent() instanceof KeyEvent && e.getData(PlatformCoreDataKeys.CONTEXT_COMPONENT) instanceof JTextField); // do not override text deletion
}
@Override
protected void update(@NotNull FileSystemTree fileChooser, @NotNull AnActionEvent e) {
boolean visible = isEnabled(e);
var visible = isVisible(e);
e.getPresentation().setVisible(visible);
e.getPresentation().setEnabled(
visible &&
!(e.getInputEvent() instanceof KeyEvent && e.getData(PlatformCoreDataKeys.CONTEXT_COMPONENT) instanceof JTextField) && // do not override text deletion
new VirtualFileDeleteProvider().canDeleteElement(e.getDataContext()));
e.getPresentation().setEnabled(visible && isEnabled(e) && new VirtualFileDeleteProvider().canDeleteElement(e.getDataContext()));
}
@Override
@@ -7,7 +7,7 @@ import com.intellij.openapi.fileChooser.FileSystemTree;
import org.jetbrains.annotations.NotNull;
abstract sealed class GoToHistoryAction extends FileChooserAction {
@SuppressWarnings("FieldMayBeStatic") private final boolean myBackward = this instanceof Backward;
private final boolean myBackward = this instanceof Backward;
@Override
protected void update(@NotNull FileChooserPanel panel, @NotNull AnActionEvent e) {
@@ -35,8 +35,9 @@ import java.nio.file.Path;
public class NewFolderAction extends FileChooserAction implements LightEditCompatible {
@SuppressWarnings("unused")
private NewFolderAction() { }
NewFolderAction() { }
@SuppressWarnings("ActionPresentationInstantiatedInCtor")
public NewFolderAction(@NlsActions.ActionText String text, @NlsActions.ActionDescription String description, Icon icon) {
super(text, description, icon);
}
@@ -1,8 +1,9 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.openapi.fileChooser.actions;
import com.intellij.CommonBundle;
import com.intellij.ide.DeleteProvider;
import com.intellij.ide.GeneralSettings;
import com.intellij.ide.IdeBundle;
import com.intellij.openapi.actionSystem.ActionUpdateThread;
import com.intellij.openapi.actionSystem.CommonDataKeys;
@@ -13,12 +14,13 @@ import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.UIBundle;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.PlatformNioHelper;
import com.intellij.util.io.TrashBin;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
@@ -27,7 +29,7 @@ import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
public final class VirtualFileDeleteProvider implements DeleteProvider{
public final class VirtualFileDeleteProvider implements DeleteProvider {
private static final Logger LOG = Logger.getInstance(VirtualFileDeleteProvider.class);
@Override
@@ -37,37 +39,43 @@ public final class VirtualFileDeleteProvider implements DeleteProvider{
@Override
public boolean canDeleteElement(@NotNull DataContext dataContext) {
final VirtualFile[] files = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext);
var files = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext);
return files != null && files.length > 0;
}
@Override
public void deleteElement(@NotNull DataContext dataContext) {
final VirtualFile[] files = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext);
var files = CommonDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext);
if (files == null || files.length == 0) return;
Project project = CommonDataKeys.PROJECT.getData(dataContext);
String message = createConfirmationMessage(files);
int returnValue = Messages.showOkCancelDialog(message, UIBundle.message("delete.dialog.title"), ApplicationBundle.message("button.delete"),
CommonBundle.getCancelButtonText(), Messages.getQuestionIcon());
if (returnValue != Messages.OK) return;
var project = CommonDataKeys.PROJECT.getData(dataContext);
var toBin = TrashBin.isSupported() && GeneralSettings.getInstance().isDeletingToBin();
Arrays.sort(files, FileComparator.getInstance());
if (!(toBin && ContainerUtil.all(files, PlatformNioHelper::isLocal))) {
var message = createConfirmationMessage(files);
var returnValue = Messages.showOkCancelDialog(
message, UIBundle.message("delete.dialog.title"), ApplicationBundle.message("button.delete"), CommonBundle.getCancelButtonText(), Messages.getQuestionIcon()
);
if (returnValue != Messages.OK) return;
}
List<String> problems = new LinkedList<>();
CommandProcessor.getInstance().executeCommand(project, () -> new Task.Modal(project, IdeBundle.message("progress.title.deleting.files"), true) {
Arrays.sort(files, Comparator.comparing(VirtualFile::getPath));
var problems = new LinkedList<String>();
CommandProcessor.getInstance().executeCommand(project, () -> new Task.Modal(project, IdeBundle.message("progress.deleting"), true) {
@Override
public void run(@NotNull ProgressIndicator indicator) {
indicator.setIndeterminate(false);
int i = 0;
for (VirtualFile file : files) {
indicator.checkCanceled();
indicator.setText2(file.getPresentableUrl());
indicator.setFraction((double)i / files.length);
i++;
var i = 0;
for (var file : files) {
if (indicator.isCanceled()) break;
indicator.setText(file.getPresentableUrl());
indicator.setFraction((double)i++ / files.length);
try {
WriteAction.runAndWait(()-> file.delete(this));
if (toBin && PlatformNioHelper.isLocal(file)) {
TrashBin.moveToTrash(file.toNioPath());
}
WriteAction.runAndWait(() -> file.delete(this));
}
catch (IOException e) {
LOG.info("Error when deleting " + file, e);
@@ -95,28 +103,13 @@ public final class VirtualFileDeleteProvider implements DeleteProvider{
}
private static void reportDeletionProblem(List<String> problems) {
boolean more = false;
var more = false;
if (problems.size() > 10) {
problems = problems.subList(0, 10);
more = true;
}
Messages.showMessageDialog(
IdeBundle.message("dialog.message.could.not.erase.files.or.folders.0.1", StringUtil.join(problems, ",\n "), more ? "\n ..." : ""),
UIBundle.message("error.dialog.title"), Messages.getErrorIcon());
}
private static final class FileComparator implements Comparator<VirtualFile> {
private static final FileComparator ourInstance = new FileComparator();
public static FileComparator getInstance() {
return ourInstance;
}
@Override
public int compare(final VirtualFile o1, final VirtualFile o2) {
// files first
return o2.getPath().compareTo(o1.getPath());
}
var message = IdeBundle.message("dialog.message.could.not.erase.files.or.folders.0.1", String.join(",\n ", problems), more ? "\n ..." : "");
Messages.showMessageDialog(message, UIBundle.message("error.dialog.title"), Messages.getErrorIcon());
}
private static @NlsContexts.DialogMessage String createConfirmationMessage(VirtualFile[] filesToDelete) {
@@ -129,10 +122,10 @@ public final class VirtualFileDeleteProvider implements DeleteProvider{
}
}
else {
boolean hasFiles = false;
boolean hasFolders = false;
for (VirtualFile file : filesToDelete) {
boolean isDirectory = file.isDirectory();
var hasFiles = false;
var hasFolders = false;
for (var file : filesToDelete) {
var isDirectory = file.isDirectory();
hasFiles |= !isDirectory;
hasFolders |= isDirectory;
}
@@ -148,4 +141,4 @@ public final class VirtualFileDeleteProvider implements DeleteProvider{
}
}
}
}
}
@@ -20,8 +20,11 @@ import com.intellij.openapi.fileChooser.ex.FileTextFieldImpl;
import com.intellij.openapi.fileChooser.ex.LocalFsFinder;
import com.intellij.openapi.fileTypes.FileTypeRegistry;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.NlsContexts.DialogMessage;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.ThrowableComputable;
import com.intellij.openapi.util.io.NioFiles;
import com.intellij.openapi.util.io.OSAgnosticPathUtil;
import com.intellij.openapi.util.text.Formats;
@@ -37,6 +40,7 @@ import com.intellij.ui.table.TableView;
import com.intellij.util.UriUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.PlatformNioHelper;
import com.intellij.util.system.OS;
import com.intellij.util.text.DateFormatUtil;
import com.intellij.util.ui.*;
import org.jetbrains.annotations.NotNull;
@@ -48,14 +52,13 @@ import javax.swing.table.TableCellRenderer;
import java.awt.*;
import java.awt.datatransfer.Transferable;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.List;
import java.util.*;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
@@ -103,10 +106,12 @@ final class FileChooserPanelImpl extends JBPanel<FileChooserPanelImpl> implement
@SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized") private int myHistoryIndex = -1; // points to the last added or used element
private volatile boolean myReloadSuppressed = false;
FileChooserPanelImpl(@NotNull FileChooserDescriptor descriptor,
@NotNull Runnable callback,
@NotNull Consumer<@Nullable @DialogMessage String> errorSink,
Path @NotNull [] recentPaths) {
FileChooserPanelImpl(
@NotNull FileChooserDescriptor descriptor,
@NotNull Runnable callback,
@NotNull Consumer<@Nullable @DialogMessage String> errorSink,
Path @NotNull [] recentPaths
) {
super(new GridBagLayout());
myRegistry = FileTypeRegistry.getInstance();
@@ -680,10 +685,11 @@ final class FileChooserPanelImpl extends JBPanel<FileChooserPanelImpl> implement
var attrs = Files.readAttributes(root, BasicFileAttributes.class);
var virtualFile = new LazyDirectoryOrFile(null, root, attrs);
var name = NioFiles.getFileName(root);
if (name.length() > 1 && name.endsWith(File.separator)) {
//noinspection IO_FILE_USAGE,UnnecessaryFullyQualifiedName
if (name.length() > 1 && name.endsWith(java.io.File.separator)) {
name = name.substring(0, name.length() - 1);
}
if (SystemInfo.isWindows) {
if (OS.CURRENT == OS.Windows) {
try {
var store = Files.getFileStore(root).name();
if (!store.isBlank()) {
@@ -751,8 +757,8 @@ final class FileChooserPanelImpl extends JBPanel<FileChooserPanelImpl> implement
return NioFiles.toPath(text);
}
private static boolean isLocalFs(Path file) {
return file.getFileSystem() == FileSystems.getDefault();
private static boolean isLocalFs(Path path) {
return PlatformNioHelper.isLocal(path);
}
// faster than `Files#getFileStore` (at least for ZipFS); not suitable for local FS
@@ -8,6 +8,7 @@ import com.intellij.ide.RecentProjectsManagerBase
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.remoting.ActionRemoteBehaviorSpecification
import com.intellij.openapi.application.impl.islands.isColorIslandGradientAvailable
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.NlsActions
@@ -111,7 +112,7 @@ internal class ChooseCustomProjectColorAction: AnAction(IdeBundle.message("actio
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
override fun update(e: AnActionEvent) {
e.presentation.isEnabled = e.project != null
e.presentation.isEnabled = e.project != null && !isColorIslandGradientAvailable()
}
}
@@ -2,6 +2,7 @@
package com.intellij.toolWindow.xNext.island
import com.intellij.openapi.application.impl.InternalUICustomization
import com.intellij.openapi.application.impl.islands.isColorIslandGradient
import com.intellij.ui.BorderPainter
import com.intellij.ui.DefaultBorderPainter
import org.jetbrains.annotations.ApiStatus
@@ -31,9 +32,11 @@ class XNextIslandHolder : JPanel() {
}
override fun addImpl(comp: Component?, constraints: Any?, index: Int) {
comp?.let {
if (it is JComponent) {
InternalUICustomization.getInstance()?.installEditorBackground(it)
if (!isColorIslandGradient()) {
comp?.let {
if (it is JComponent) {
InternalUICustomization.getInstance()?.installEditorBackground(it)
}
}
}
super.addImpl(comp, constraints, index)
@@ -2,12 +2,15 @@
package com.intellij.util.io;
import com.intellij.openapi.util.io.NioFiles;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.platform.core.nio.fs.BasicFileAttributesHolder2.FetchAttributesFilter;
import com.intellij.platform.eel.provider.LocalEelMachine;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
@@ -79,4 +82,12 @@ public final class PlatformNioHelper {
}
}
}
public static boolean isLocal(@NotNull Path path) {
return path.getFileSystem() == FileSystems.getDefault() && LocalEelMachine.INSTANCE.ownsPath(path);
}
public static boolean isLocal(@NotNull VirtualFile file) {
return file.isInLocalFileSystem() && isLocal(file.toNioPath());
}
}
@@ -1305,7 +1305,7 @@
<action id="FileChooser.NewFolder" class="com.intellij.openapi.fileChooser.actions.NewFolderAction"
icon="AllIcons.Actions.NewFolder"/>
<action id="FileChooser.Delete" class="com.intellij.openapi.fileChooser.actions.FileDeleteAction"
icon="AllIcons.Actions.Cancel" use-shortcut-of="$Delete"/>
icon="AllIcons.General.Delete" use-shortcut-of="$Delete"/>
<separator/>
<action id="FileChooser.Refresh" class="com.intellij.openapi.fileChooser.actions.RefreshFileChooserAction"
icon="AllIcons.Actions.Refresh" use-shortcut-of="Synchronize"/>
@@ -347,7 +347,8 @@
"borderWidth": 6,
"borderWidth.compact": 4,
"borderColor": "container-tool-window-bg",
"inactiveAlpha": 0.56
"inactiveAlpha": 0.56,
"toolWindowAlpha": 0.2
},
"alt": {
@@ -347,7 +347,8 @@
"borderWidth": 6,
"borderWidth.compact": 4,
"borderColor": "container-tool-window-bg",
"inactiveAlpha": 0.56
"inactiveAlpha": 0.56,
"toolWindowAlpha": 0.2
},
"alt": {
@@ -53,3 +53,6 @@ com.intellij.execution.process.SelfKiller
- s:shellCommand(java.lang.String,java.nio.file.Path,java.util.List):java.lang.ProcessBuilder
- s:shellCommand(java.lang.String,java.nio.file.Path,Z,java.util.List):java.lang.ProcessBuilder
- s:winShellCommand(java.nio.file.Path,java.util.List):java.lang.ProcessBuilder
*f:com.intellij.util.io.TrashBin
- s:isSupported():Z
- s:moveToTrash(java.nio.file.Path):V
@@ -2,6 +2,7 @@
dialog.message.failed.to.make.temp.file.executable=Failed to make temp file executable: {0}
error.message.cannot.connect.unknown.error=Cannot connect: unknown error
error.message.interrupted=Interrupted
error.message.cannot.trash=Cannot move file/directory to Bin
run.canceled.by.user.message=Run canceled by the user
run.configuration.error.executable.not.specified=Executable is not specified
run.configuration.error.working.directory.does.not.exist=Cannot start a process, the working directory ''{0}'' does not exist
@@ -0,0 +1,38 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.util.io;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.io.IOException;
import java.nio.file.FileSystemException;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* A convenience wrapper around `java.awt.Desktop#moveToTrash`.
* Make sure the path belongs to the local file system.
*/
@ApiStatus.Experimental
public final class TrashBin {
private TrashBin() { }
public static boolean isSupported() {
return Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.MOVE_TO_TRASH);
}
public static void moveToTrash(@NotNull Path path) throws IOException {
@SuppressWarnings("IO_FILE_USAGE") var ioFile = path.toFile();
var trashed = false;
try {
trashed = Desktop.getDesktop().moveToTrash(ioFile);
}
catch (IllegalArgumentException ignored) { }
if (!trashed && Files.exists(path)) {
throw new FileSystemException(path.toString(), null, IdeUtilIoBundle.message("error.message.cannot.trash"));
}
}
}
@@ -312,6 +312,7 @@ com.intellij.java.*
org.jetbrains.java.*
org.jetbrains.uast.test.java.*
com.intellij.codeInspection.tests.java.*
com.intellij.ide.customization.java.*
com.siyeh.*
-com.intellij.java.compiler.*
-com.intellij.java.propertyBased.*
@@ -2360,6 +2360,7 @@ station.enable.welcome.screen.promo.description=Enables promo for Toolbox App
idea.islands.color.gradient.enabled=false
idea.islands.color.gradient.enabled.description=Enable gradient backgrounds for UI Islands color scheme elements
idea.islands.color.gradient.enabled.restartRequired=true
vfs.fetch.case.sensitivity.using.eel=true
vfs.fetch.case.sensitivity.using.eel.description=Enable directory case sensitivity fetching using EEL on WSL and Docker. \
@@ -259,7 +259,7 @@ public final class NioFiles {
* <p>Implementation detail: the method tries to delete a file up to 10 times with 10 ms pause between attempts -
* usually it's enough to overcome intermittent file lock on Windows.</p>
*/
public static void deleteRecursively(@NotNull Path fileOrDirectory, @NotNull Consumer<? super Path> callback) throws IOException {
public static void deleteRecursively(@NotNull Path fileOrDirectory, @NotNull Consumer<Path> callback) throws IOException {
FileUtilRt.deleteRecursively(fileOrDirectory, callback::accept);
}
@@ -15,6 +15,7 @@ import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -262,14 +263,14 @@ public class NioFilesTest {
assertEquals(42, runAndGetExitValue(script.toString()));
}
private static int runAndGetExitValue(String command) throws IOException, InterruptedException {
private static int runAndGetExitValue(String... command) throws IOException, InterruptedException {
var process = Runtime.getRuntime().exec(command);
if (process.waitFor(30, TimeUnit.SECONDS)) {
return process.exitValue();
}
else {
process.destroy();
throw new AssertionError("Timed out and killed: " + command);
throw new AssertionError("Timed out and killed: " + List.of(command));
}
}
@@ -5,11 +5,12 @@ import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.backend.workspace.WorkspaceModelChangeListener
import com.intellij.platform.backend.workspace.virtualFile
import com.intellij.platform.workspace.jps.entities.ContentRootEntity
import com.intellij.platform.workspace.storage.ImmutableEntityStorage
import com.intellij.platform.workspace.storage.VersionedStorageChange
import com.intellij.platform.workspace.storage.url.VirtualFileUrl
abstract class ContentRootChangeListener(
val skipFileChanges: Boolean
val skipFileChanges: Boolean,
) : WorkspaceModelChangeListener {
override fun changed(event: VersionedStorageChange) {
val removedUrls = mutableSetOf<VirtualFileUrl>()
@@ -40,12 +41,27 @@ abstract class ContentRootChangeListener(
added = added.filter { it.isDirectory }
}
val removedList = removed.toList()
val addedList = added.toList()
val removedList = collectChangedFiles(removed, event.storageAfter)
val addedList = collectChangedFiles(added, event.storageBefore)
if (removedList.isNotEmpty() || addedList.isNotEmpty()) {
contentRootsChanged(removedList, addedList)
}
}
// There might be a case when a content root with duplicating URL was registered or removed
private fun collectChangedFiles(files: Sequence<VirtualFile>, storage: ImmutableEntityStorage): List<VirtualFile> {
if (files.none()) return emptyList()
val state = storage.contentRootPaths(skipFileChanges)
return files.filterNot { it in state }.toList()
}
private fun ImmutableEntityStorage.contentRootPaths(skipFileChanges: Boolean): Set<VirtualFile> =
entities(ContentRootEntity::class.java)
.mapNotNull { it.url.virtualFile }
.filter { it.isInLocalFileSystem }
.let { entities -> if (skipFileChanges) entities.filter { it.isDirectory } else entities }
.toSet()
abstract fun contentRootsChanged(removed: List<VirtualFile>, added: List<VirtualFile>)
}
@@ -7,6 +7,7 @@ import org.jetbrains.kotlin.analysis.api.KaSession
import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KaFirDiagnostic
import org.jetbrains.kotlin.analysis.api.symbols.KaNamedFunctionSymbol
import org.jetbrains.kotlin.analysis.api.types.KaClassType
import org.jetbrains.kotlin.analysis.api.types.KaErrorType
import org.jetbrains.kotlin.analysis.api.types.KaType
import org.jetbrains.kotlin.analysis.api.types.symbol
import org.jetbrains.kotlin.idea.codeinsight.api.applicators.fixes.KotlinQuickFixFactory
@@ -29,16 +30,11 @@ internal object ConvertCollectionFixFactory {
val expressionCollectionType = getCollectionType(expressionType) ?: return null
val expectedCollectionType = getCollectionType(expectedType) ?: return null
if (expressionCollectionType == expectedCollectionType) return null
val expressionTypeArg = (expressionType as? KaClassType)?.typeArguments?.singleOrNull()?.type ?: return null
val expectedTypeArg = (expectedType as? KaClassType)?.typeArguments?.singleOrNull()?.type ?: return null
if (!expressionTypeArg.isSubtypeOf(expectedTypeArg)) return null
if (!areTypeArgumentsCompatible(expectedType, expressionType)) return null
return expectedCollectionType.specializeFor(expressionCollectionType)
}
private fun KaSession.createFixIfAvailable(
element: PsiElement,
expectedType: KaType,
@@ -50,7 +46,7 @@ internal object ConvertCollectionFixFactory {
// with their mutable equivalents (e.g., mutableListOf, mutableSetOf). If that's not applicable,
// fall back to creating a fix that adds a conversion function (e.g., toList, toMutableSet) to
// convert between incompatible collection types.
val fix = createReplaceWithMutableCollectionFactoryFix(expression, expectedType)
val fix = createReplaceWithMutableCollectionFactoryFix(expression, expectedType, actualType)
?: createConvertCollectionFix(expression, expectedType, actualType)
return listOfNotNull(fix)
}
@@ -58,6 +54,7 @@ internal object ConvertCollectionFixFactory {
private fun KaSession.createReplaceWithMutableCollectionFactoryFix(
expression: KtExpression,
expectedType: KaType,
actualType: KaType,
): ModCommandAction? {
if (expression !is KtCallExpression) return null
@@ -74,6 +71,7 @@ internal object ConvertCollectionFixFactory {
val literalFunctionName = expectedCollectionType.literalFunctionName ?: return null
if (getCollectionType(expectedType) != expectedCollectionType) return null
if (!areTypeArgumentsCompatible(expectedType, actualType)) return null
return ReplaceWithMutableCollectionFactoryFix(expression, literalFunctionName)
}
@@ -111,4 +109,24 @@ internal object ConvertCollectionFixFactory {
val assignmentTypeMismatch = KotlinQuickFixFactory.ModCommandBased { diagnostic: KaFirDiagnostic.AssignmentTypeMismatch ->
createFixIfAvailable(diagnostic.expression, diagnostic.expectedType, diagnostic.actualType)
}
}
private fun KaSession.areTypeArgumentsCompatible(expectedType: KaType, actualType: KaType): Boolean {
val actualClassType = actualType as? KaClassType ?: return false
val expectedClassType = expectedType as? KaClassType ?: return false
val actualTypeArgs = actualClassType.typeArguments.mapNotNull { it.type }
val expectedTypeArgs = expectedClassType.typeArguments.mapNotNull { it.type }
// If we lost type arguments due to null values, return false
if (actualTypeArgs.size != actualClassType.typeArguments.size) return false
if (expectedTypeArgs.size != expectedClassType.typeArguments.size) return false
if (actualTypeArgs.size != expectedTypeArgs.size) return false
return actualTypeArgs.zip(expectedTypeArgs).all { (actualArg, expectedArg) ->
// Allow KaErrorType when type inference is impossible due to the mismatch itself,
// e.g., `listOf()` in `fun baz(): MutableList<Int> = listOf()` has an error type
actualArg is KaErrorType || actualArg.isSubtypeOf(expectedArg)
}
}
@@ -15194,6 +15194,16 @@ public abstract class HighLevelQuickFixTestGenerated extends AbstractHighLevelQu
public void testSetOfToMutableSet() throws Exception {
runTest("../../../idea/tests/testData/quickfix/typeMismatch/convertCollection/setOfToMutableSet.kt");
}
@TestMetadata("typeArgumentMismatchWithExplicitType.kt")
public void testTypeArgumentMismatchWithExplicitType() throws Exception {
runTest("../../../idea/tests/testData/quickfix/typeMismatch/convertCollection/typeArgumentMismatchWithExplicitType.kt");
}
@TestMetadata("typeArgumentMismatchWithInferredType.kt")
public void testTypeArgumentMismatchWithInferredType() throws Exception {
runTest("../../../idea/tests/testData/quickfix/typeMismatch/convertCollection/typeArgumentMismatchWithInferredType.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
+1
View File
@@ -218,6 +218,7 @@ jvm_library(
"//plugins/kotlin/kotlin.searching/base",
"//plugins/kotlin/j2k/k1.old.post-processing",
"//java/ide-customization",
"//java/ide-customization:ide-customization_test_lib",
"//plugins/kotlin/refactorings/kotlin.refactorings.common:kotlin-refactorings-common",
"//plugins/kotlin/base/test:test_test_lib",
"@lib//:hamcrest",
@@ -19263,6 +19263,16 @@ public abstract class K1QuickFixTestGenerated extends AbstractK1QuickFixTest {
public void testSetOfToMutableSet() throws Exception {
runTest("testData/quickfix/typeMismatch/convertCollection/setOfToMutableSet.kt");
}
@TestMetadata("typeArgumentMismatchWithExplicitType.kt")
public void testTypeArgumentMismatchWithExplicitType() throws Exception {
runTest("testData/quickfix/typeMismatch/convertCollection/typeArgumentMismatchWithExplicitType.kt");
}
@TestMetadata("typeArgumentMismatchWithInferredType.kt")
public void testTypeArgumentMismatchWithInferredType() throws Exception {
runTest("testData/quickfix/typeMismatch/convertCollection/typeArgumentMismatchWithInferredType.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@@ -0,0 +1,9 @@
// "Replace 'setOf<Int>()' with 'mutableSetOf<Int>()'" "false"
// K2_AFTER_ERROR: Assignment type mismatch: actual type is 'Set<Int>', but 'MutableSet<String>' was expected.
fun test() {
val strs: MutableSet<String>
strs =<caret> setOf<Int>()
}
// IGNORE_K1
@@ -0,0 +1,8 @@
// "Replace 'listOf(1)' with 'mutableListOf(1)'" "false"
// K2_AFTER_ERROR: Return type mismatch: expected 'MutableList<String>', actual 'List<Int>'.
fun test(): MutableList<String> {
return list<caret>Of(1)
}
// IGNORE_K1
@@ -8,6 +8,22 @@ import com.intellij.debugger.engine.DebuggerUtils
import com.sun.jdi.ClassType
import com.sun.jdi.Method
import com.sun.jdi.ReferenceType
import com.sun.jdi.Type
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.org.objectweb.asm.Type as AsmType
@ApiStatus.Internal
fun Type.isSubtype(type: AsmType): Boolean {
if (this.signature() == type.descriptor) {
return true
}
if (type.sort != AsmType.OBJECT || this !is ClassType) {
return false
}
return DebuggerUtils.instanceOf(this, type.className)
}
fun ClassType.hasSuperClass(jdiName: String): Boolean {
var current = this
@@ -5,10 +5,10 @@ package org.jetbrains.kotlin.idea.debugger
import com.intellij.debugger.JavaDebuggerBundle
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
import com.intellij.debugger.engine.DebuggerUtils
import com.intellij.debugger.engine.FieldVisibilityProvider
import com.intellij.debugger.engine.JVMNameUtil
import com.intellij.debugger.engine.evaluation.EvaluationContext
import com.intellij.debugger.impl.instanceOf
import com.intellij.debugger.impl.DebuggerUtilsAsync
import com.intellij.debugger.settings.NodeRendererSettings
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
@@ -21,6 +21,7 @@ import com.intellij.debugger.ui.tree.render.DescriptorLabelListener
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.project.Project
import com.sun.jdi.*
import org.jetbrains.kotlin.analysis.api.KaExperimentalApi
import org.jetbrains.kotlin.idea.debugger.base.util.isLateinitVariableGetter
import org.jetbrains.kotlin.idea.debugger.base.util.isSimpleGetter
import org.jetbrains.kotlin.idea.debugger.base.util.safeFields
@@ -32,7 +33,6 @@ import org.jetbrains.kotlin.idea.debugger.core.isInKotlinSourcesAsync
import org.jetbrains.kotlin.idea.debugger.core.render.GetterDescriptor
import java.util.concurrent.CompletableFuture
import java.util.function.Function
import org.jetbrains.kotlin.analysis.api.KaExperimentalApi
import kotlin.metadata.ExperimentalContextReceivers
import kotlin.metadata.KmProperty
import kotlin.metadata.isNotDefault
@@ -228,7 +228,7 @@ class KotlinClassRenderer : ClassRenderer() {
"java.util.Map.Entry",
)
return typesWithGoodDefaultRenderers.any { superType -> DebuggerUtils.instanceOf(this, superType) }
return typesWithGoodDefaultRenderers.any(this::instanceOf)
}
private fun Field.isInstanceFieldOfType(type: Type) =
@@ -5,7 +5,7 @@ package org.jetbrains.kotlin.idea.debugger.core.stackFrame
import com.intellij.debugger.engine.JavaStackFrame
import com.intellij.debugger.engine.JavaValue
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.engine.isSubtype
import com.intellij.debugger.impl.instanceOf
import com.intellij.debugger.jdi.LocalVariableProxyImpl
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.idea.debugger.base.util.KotlinDebuggerConstants.INLI
import org.jetbrains.kotlin.idea.debugger.base.util.KotlinDebuggerConstants.INLINE_SCOPE_NUMBER_SEPARATOR
import org.jetbrains.kotlin.idea.debugger.base.util.dropInlineSuffix
import org.jetbrains.kotlin.idea.debugger.base.util.getInlineDepth
import org.jetbrains.kotlin.idea.debugger.base.util.isSubtype
import org.jetbrains.kotlin.idea.debugger.base.util.safeVisibleVariables
import org.jetbrains.kotlin.idea.debugger.core.ToggleKotlinVariablesState
import org.jetbrains.kotlin.load.java.JvmAbi
@@ -111,7 +112,7 @@ open class KotlinStackFrame(
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return false
val thisObjectType = thisObject.type()
if (thisObjectType.isSubtype(Function::class.java.name) && '$' in thisObjectType.signature()) {
if (thisObjectType.instanceOf(Function::class.java.name) && '$' in thisObjectType.signature()) {
val existingThis = ExistingInstanceThisRemapper.find(children)
if (existingThis != null) {
existingThis.remove()
@@ -128,7 +129,7 @@ open class KotlinStackFrame(
ExistingInstanceThisRemapper.find(children)?.remove()
val dispatchReceiver = (evaluationContext.frameProxy as? KotlinStackFrameProxyImpl)?.dispatchReceiver() ?: return true
val dispatchReceiverType = dispatchReceiver.type()
if (dispatchReceiverType.isSubtype(Function::class.java.name) && '$' in dispatchReceiverType.signature()) {
if (dispatchReceiverType.instanceOf(Function::class.java.name) && '$' in dispatchReceiverType.signature()) {
attachCapturedValues(evaluationContext, children, dispatchReceiver, existingVariables)
}
return true
@@ -2,7 +2,7 @@
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror
import com.intellij.debugger.engine.isSubTypeOrSame
import com.intellij.debugger.impl.instanceOf
import com.sun.jdi.ObjectReference
import com.sun.jdi.ReferenceType
import org.jetbrains.kotlin.idea.debugger.base.util.evaluate.DefaultExecutionContext
@@ -15,7 +15,7 @@ abstract class BaseMirror<T: ObjectReference, F>(val name: String, context: Defa
override fun getCls(): ReferenceType = cls
override fun isCompatible(value: T?) =
value?.referenceType()?.isSubTypeOrSame(name) ?: false
value?.referenceType()?.instanceOf(name) ?: false
override fun mirror(value: T?, context: DefaultExecutionContext): F? {
if (value == null) return null
@@ -2,7 +2,7 @@
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror
import com.intellij.debugger.engine.isSubTypeOrSame
import com.intellij.debugger.impl.instanceOf
import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.base.util.evaluate.DefaultExecutionContext
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
@@ -110,10 +110,10 @@ class DebugProbesImplCoroutineOwner(private val coroutineInfo: CoroutineInfo?, c
}
companion object {
const val COROUTINE_OWNER_CLASS_NAME = "kotlinx.coroutines.debug.internal.DebugProbesImpl\$CoroutineOwner"
private const val COROUTINE_OWNER_CLASS_NAME = "kotlinx.coroutines.debug.internal.DebugProbesImpl\$CoroutineOwner"
fun instanceOf(value: ObjectReference?) =
value?.referenceType()?.isSubTypeOrSame(COROUTINE_OWNER_CLASS_NAME) ?: false
internal fun instanceOf(value: ObjectReference?): Boolean =
value?.referenceType()?.instanceOf(COROUTINE_OWNER_CLASS_NAME) ?: false
}
}
@@ -6,7 +6,7 @@ import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.engine.isSubtype
import com.intellij.debugger.impl.instanceOf
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
@@ -19,7 +19,6 @@ import org.jetbrains.kotlin.idea.debugger.base.util.*
import org.jetbrains.kotlin.idea.debugger.base.util.evaluate.DefaultExecutionContext
import org.jetbrains.kotlin.idea.debugger.coroutine.data.SuspendExitMode
import org.jetbrains.kotlin.idea.util.application.isUnitTestMode
import org.jetbrains.org.objectweb.asm.Type as AsmType
const val CREATION_STACK_TRACE_SEPARATOR = "\b\b\b" // the "\b\b\b" is used as creation stacktrace separator in kotlinx.coroutines
const val CREATION_CLASS_NAME = "_COROUTINE._CREATION"
@@ -75,18 +74,17 @@ internal fun extractContinuation(frameProxy: StackFrameProxyImpl): ObjectReferen
fun Location.safeCoroutineExitPointLineNumber() =
(wrapIllegalArgumentException { DebuggerUtilsEx.getLineNumber(this, false) } ?: -2) == -1
fun Type.isBaseContinuationImpl() =
isSubtype("kotlin.coroutines.jvm.internal.BaseContinuationImpl")
fun Type.isCoroutineScope() =
isSubtype("kotlinx.coroutines.CoroutineScope")
@Deprecated("Use isSubTypeOrSame from com.intellij.debugger.engine.evaluation.expression instead")
fun Type.isSubTypeOrSame(className: String): Boolean = isSubtype(AsmType.getObjectType(className))
instanceOf("kotlinx.coroutines.CoroutineScope")
@Deprecated(
"Use com.intellij.debugger.engine.DebuggerUtils.instanceOf instead",
ReplaceWith("com.intellij.debugger.engine.DebuggerUtils.instanceOf(this, className)")
)
fun Type.isSubTypeOrSame(className: String): Boolean = instanceOf(className)
fun ReferenceType.isSuspendLambda() =
SUSPEND_LAMBDA_CLASSES.any { isSubtype(it) }
SUSPEND_LAMBDA_CLASSES.any { instanceOf(it) }
fun Location.isInvokeSuspend() =
safeMethod()?.isInvokeSuspend() ?: false
@@ -3,11 +3,11 @@
package org.jetbrains.kotlin.idea.debugger.evaluate.variables
import com.intellij.debugger.engine.DebuggerUtils
import com.intellij.debugger.engine.isSubtype
import com.sun.jdi.*
import org.jetbrains.kotlin.fileClasses.internalNameWithoutInnerClasses
import org.jetbrains.kotlin.idea.debugger.base.util.evaluate.ExecutionContext
import org.jetbrains.kotlin.idea.debugger.base.util.findMethod
import org.jetbrains.kotlin.idea.debugger.base.util.isSubtype
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder.Result
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
import kotlin.jvm.internal.Ref
@@ -6,7 +6,7 @@ import com.intellij.debugger.engine.DebuggerUtils
import com.intellij.debugger.engine.JavaValue
import com.intellij.debugger.engine.evaluation.AdditionalContextProvider
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.engine.isSubtype
import com.intellij.debugger.impl.instanceOf
import com.intellij.debugger.jdi.LocalVariableProxyImpl
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.sun.jdi.*
@@ -476,7 +476,7 @@ internal class VariableFinder(val context: ExecutionContext) {
val continuation = frameProxy.continuation ?: return null
val continuationType = continuation.referenceType()
if (SUSPEND_LAMBDA_CLASSES.none { continuationType.isSubtype(it) }) {
if (SUSPEND_LAMBDA_CLASSES.none { continuationType.instanceOf(it) }) {
return null
}
@@ -3,7 +3,7 @@ Run Java
Connected to the target VM
coroutineStepping.kt:18
coroutineStepping.kt:19
Packets sent: 35, methods called: 4
Packets sent: 36, methods called: 4
coroutineStepping.kt:20
Packets sent: 27, methods called: 4
coroutineStepping.kt:19
@@ -87,7 +87,7 @@ Packets sent: 49, methods called: 2
veryLongCoroutineStack.kt:12
Packets sent: 48, methods called: 2
veryLongCoroutineStack.kt:12
Packets sent: 54, methods called: 3
Packets sent: 57, methods called: 3
veryLongCoroutineStack.kt:16
Packets sent: 36, methods called: 2
veryLongCoroutineStack.kt:17
@@ -173,7 +173,7 @@ Packets sent: 16, methods called: 2
veryLongCoroutineStack.kt:17
Packets sent: 16, methods called: 2
veryLongCoroutineStack.kt:22
Packets sent: 23, methods called: 4
Packets sent: 29, methods called: 4
veryLongCoroutineStack.kt:19
Packets sent: 3, methods called: 1
veryLongCoroutineStack.kt:22
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
1769717454
1769809507
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
1768926036
1769807983
@@ -169,6 +169,8 @@
<applicationService serviceImplementation="com.jetbrains.python.packaging.PyPackageService"/>
<applicationService serviceInterface="com.jetbrains.python.module.PyModuleService"
serviceImplementation="com.jetbrains.python.module.PyModuleServiceImpl"/>
<applicationService serviceInterface="com.jetbrains.python.psi.resolve.PackageAvailabilityService"
serviceImplementation="com.jetbrains.python.packaging.PackageAvailabilityServiceImpl"/>
<typedHandler implementation="com.jetbrains.python.codeInsight.PyMethodNameTypedHandler" id="pyMethodNameTypedHandler"/>
<idIndexer filetype="Python" implementationClass="com.jetbrains.python.PyIdIndexer"/>
@@ -17,6 +17,8 @@
dynamic="true"/>
<extensionPoint qualifiedName="Pythonid.typeHintProvider" interface="com.jetbrains.python.codeInsight.typing.PyTypeHintProvider" dynamic="true"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
@@ -179,6 +179,10 @@ public class PyTargetExpressionImpl extends PyBaseElementImpl<PyTargetExpression
final PyExpression assignedValue = ((PyAssignmentExpression)parent).getAssignedValue();
return assignedValue == null ? null : context.getType(assignedValue);
}
if (parent instanceof PyImportElement importElement) {
final PyReferenceExpression importedRef = importElement.getImportReferenceExpression();
return importedRef == null ? null : context.getType(importedRef);
}
if (parent instanceof PyGlobalStatement || parent instanceof PyNonlocalStatement) {
PyResolveContext resolveContext = PyResolveContext.defaultContext(context);
List<PyType> collect = StreamEx.of(getReference(resolveContext).multiResolve(false))
@@ -0,0 +1,109 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.psi.resolve
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.Key
import com.intellij.psi.util.CachedValue
import org.jetbrains.annotations.ApiStatus
/**
* Specification for checking package availability in a Python environment.
*
* Uses a combined strategy:
* 1. First checks the package manager cache (fast, non-blocking)
* 2. Falls back to PSI resolution on disk (cached)
*
* Create instances as constants and pass them to [isPackageAvailable]:
* ```kotlin
* private val PYTEST_SPEC = PackageAvailabilitySpec("pytest", "pytest.fixture")
*
* fun isPytestInstalled(module: Module) = module.isPackageAvailable(PYTEST_SPEC)
* ```
*
* Multiple FQNs can be provided for packages that expose symbols at different paths across versions:
* ```kotlin
* private val BLACK_SPEC = PackageAvailabilitySpec("black", "black.Mode", "black.mode.Mode")
* ```
*
* @param packageName the package name as it appears in pip/package manager (e.g., "pytest", "Django", "djangorestframework")
* @param fqns the fully-qualified names to resolve for PSI fallback (e.g., "pytest.fixture", "django.conf.settings").
* Resolution succeeds if any of the FQNs resolves.
*/
@ApiStatus.Internal
class PackageAvailabilitySpec(
val packageName: String,
vararg fqns: String,
) {
val fqns: List<String> = fqns.toList()
@ApiStatus.Internal
val cacheKey: Key<CachedValue<Boolean>> = Key.create("$PREFIX$packageName:${fqns.joinToString(",")}")
internal companion object {
@ApiStatus.Internal
const val PREFIX = "isPackageAvailable:"
}
}
fun Key<*>.isPackageAvailableKey(): Boolean = this.toString().startsWith(PackageAvailabilitySpec.PREFIX)
/**
* Checks if a package is available in the module's Python environment.
*
* Uses a combined strategy:
* 1. First checks package manager cache (fast)
* 2. Falls back to PSI resolution if not found in package manager
*
* @param spec the package availability specification
* @return true if the package is available, false otherwise
*/
@ApiStatus.Internal
fun isPackageAvailable(module: Module, spec: PackageAvailabilitySpec): Boolean {
return PackageAvailabilityService.getInstance().isPackageAvailable(module, spec)
}
/**
* Checks if a package is available in the SDK's Python environment.
*
* Uses a combined strategy:
* 1. First checks package manager cache (fast)
* 2. Falls back to PSI resolution if not found in package manager
*
* @param spec the package availability specification
* @return true if the package is available, false otherwise
*/
@ApiStatus.Internal
fun isPackageAvailable(project: Project, sdk: Sdk, spec: PackageAvailabilitySpec): Boolean {
return PackageAvailabilityService.getInstance().isPackageAvailable(project, sdk, spec)
}
/**
* Service for checking package availability in Python environments.
*
* Uses a combined strategy:
* 1. First checks package manager cache (fast, non-blocking)
* 2. Falls back to PSI resolution on disk (cached)
*
* This service is implemented in a higher-level module (python.community.impl) that has access
* to [com.jetbrains.python.packaging.management.PythonPackageManager].
*/
@ApiStatus.Internal
interface PackageAvailabilityService {
/**
* Checks if a package is available in the module's Python environment.
*/
fun isPackageAvailable(module: Module, spec: PackageAvailabilitySpec): Boolean
/**
* Checks if a package is available in the SDK's Python environment.
*/
fun isPackageAvailable(project: Project, sdk: Sdk, spec: PackageAvailabilitySpec): Boolean
companion object {
@JvmStatic
fun getInstance(): PackageAvailabilityService = ApplicationManager.getApplication().getService(PackageAvailabilityService::class.java)
}
}
@@ -32,6 +32,7 @@ data class PyQualifiedNameResolveContextImpl(
private val relativeLevel: Int = -1,
private val withoutRoots: Boolean = false,
private val withoutForeign: Boolean = false,
// withMembers means that re-imports from __init__.py file will be handled correctly.
private val withMembers: Boolean = false,
private val withPlainDirectories: Boolean = false,
private val withoutStubs: Boolean = false,
@@ -491,3 +491,5 @@ private enum class Priority {
NAMESPACE_PACKAGE, // namespace package but may contain several entries in resolve result
SKELETON // generated skeletons have lowest priority but are always included in the resolve result as a fallback
}
@@ -6,26 +6,24 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.PlatformCoreDataKeys
import com.intellij.openapi.actionSystem.Presentation
import com.intellij.openapi.module.ModuleManager.Companion.getInstance
import com.jetbrains.python.packaging.management.PythonPackageManager
import com.jetbrains.python.packaging.management.hasInstalledPackageSnapshot
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
import com.jetbrains.python.psi.resolve.PackageAvailabilitySpec
import com.jetbrains.python.psi.resolve.isPackageAvailable
/**
* User : catherine
*/
object RestPythonUtil {
private val SPHINX_MARKER = PackageAvailabilitySpec("Sphinx", "sphinx.application.Sphinx")
@JvmStatic
fun updateSphinxQuickStartRequiredAction(e: AnActionEvent): Presentation {
val presentation = e.presentation
val project = e.getData(CommonDataKeys.PROJECT) ?: return presentation
val module = e.getData(PlatformCoreDataKeys.MODULE)
?: getInstance(project).modules.firstOrNull()
?: e.getData(CommonDataKeys.PROJECT)?.let { getInstance(it).modules.firstOrNull() }
?: return presentation
val sdk = PythonSdkUtil.findPythonSdk(module) ?: return presentation
val packageManager = PythonPackageManager.forSdk(project, sdk)
presentation.isEnabled = packageManager.hasInstalledPackageSnapshot("Sphinx")
presentation.isEnabled = isPackageAvailable(module, SPHINX_MARKER)
return presentation
}
}
@@ -5,7 +5,6 @@ import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.SdkType
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.util.io.Decompressor
import com.jetbrains.python.PyNames
import com.jetbrains.python.PythonBinary
import kotlinx.coroutines.Dispatchers
@@ -105,29 +104,7 @@ abstract class PyEnvironmentProvider<S : PyEnvironmentSpec<S>>(
* @throws IllegalArgumentException if archive format is not supported
*/
protected fun unpackArchive(archiveFile: Path, targetDir: Path, prefixToStrip: String? = null) {
val fileName = archiveFile.fileName.toString()
when {
fileName.endsWith(".tar.gz", ignoreCase = true) || fileName.endsWith(".tgz", ignoreCase = true) -> {
val decompressor = Decompressor.Tar(archiveFile)
.removePrefixPath("python")
if (prefixToStrip != null) {
decompressor.removePrefixPath(prefixToStrip)
}
decompressor.extract(targetDir)
}
fileName.endsWith(".zip", ignoreCase = true) -> {
// Use withZipExtensions() for proper handling of ZIP metadata (directory flags, symlinks, etc.)
val decompressor = Decompressor.Zip(archiveFile).withZipExtensions()
.removePrefixPath("python")
if (prefixToStrip != null) {
decompressor.removePrefixPath(prefixToStrip)
}
decompressor.extract(targetDir)
}
else -> {
error("Unsupported archive format: $fileName. Expected .tar.gz, .tgz, or .zip")
}
}
com.intellij.python.test.env.core.unpackArchive(archiveFile, targetDir, prefixToStrip)
}
/**
@@ -1,6 +1,7 @@
package com.intellij.python.test.env.core
import com.intellij.openapi.diagnostic.Logger
import com.intellij.util.io.Decompressor
import com.intellij.util.system.OS
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -102,4 +103,39 @@ fun markExecutable(logger: Logger, executable: Path) {
)
)
}
}
/**
* Extract archive using Decompressor framework.
* Supports tar.gz, .tgz, and .zip formats.
*
* @param archiveFile Path to the archive file
* @param targetDir Directory where archive contents will be extracted
* @param prefixToStrip Path prefix to strip during extraction (e.g., "uv-x86_64-unknown-linux-gnu" for UV archives)
* @throws IllegalStateException if archive format is not supported
*/
@ApiStatus.Internal
fun unpackArchive(archiveFile: Path, targetDir: Path, prefixToStrip: String? = null) {
val fileName = archiveFile.fileName.toString()
when {
fileName.endsWith(".tar.gz", ignoreCase = true) || fileName.endsWith(".tgz", ignoreCase = true) -> {
val decompressor = Decompressor.Tar(archiveFile)
.removePrefixPath("python")
if (prefixToStrip != null) {
decompressor.removePrefixPath(prefixToStrip)
}
decompressor.extract(targetDir)
}
fileName.endsWith(".zip", ignoreCase = true) -> {
val decompressor = Decompressor.Zip(archiveFile).withZipExtensions()
.removePrefixPath("python")
if (prefixToStrip != null) {
decompressor.removePrefixPath(prefixToStrip)
}
decompressor.extract(targetDir)
}
else -> {
error("Unsupported archive format: $fileName. Expected .tar.gz, .tgz, or .zip")
}
}
}
@@ -58,6 +58,7 @@ jvm_library(
"//python/python-test-env/conda:conda_test_lib",
"//python/python-venv:venv",
"//python/python-venv:venv_test_lib",
"//python/python-test-env/uv:uv_test_lib",
"//python/testFramework",
"//libraries/junit5",
"//platform/testFramework/common",
@@ -28,6 +28,7 @@
<orderEntry type="module" module-name="intellij.platform.execution" />
<orderEntry type="module" module-name="intellij.python.test.env.conda" scope="TEST" />
<orderEntry type="module" module-name="intellij.python.venv" scope="TEST" />
<orderEntry type="module" module-name="intellij.python.test.env.uv" scope="TEST" />
<orderEntry type="module" module-name="intellij.python.community.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.libraries.junit5" />
<orderEntry type="module" module-name="intellij.platform.testFramework.common" scope="TEST" />
@@ -15,6 +15,8 @@ import org.junit.jupiter.api.extension.ExtensionContext
import org.opentest4j.TestAbortedException
import kotlin.io.path.pathString
const val LATEST_UV_VERSION = "0.9.26"
/**
* Extension that ensures Uv tool is available and configured.
*
@@ -50,7 +52,7 @@ class RequiresUvExtension : BeforeAllCallback, BeforeEachCallback {
private fun createUvEnvironment(context: ExtensionContext): PyEnvironment {
val factory = getOrCreatePyEnvironmentFactory(context)
val envSpec = uvEnvironment("0.9.21") {
val envSpec = uvEnvironment(LATEST_UV_VERSION) {
pythonVersion = LATEST_PYTHON_VERSION
}
@@ -0,0 +1,73 @@
// 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.python.test.env.junit5
import com.intellij.openapi.application.edtWriteAction
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.python.junit5Tests.framework.env.SdkFixture
import com.intellij.python.test.env.core.PyEnvironment
import com.intellij.python.test.env.uv.getOrDownloadUvExecutable
import com.intellij.testFramework.junit5.fixture.TestFixture
import com.intellij.testFramework.junit5.fixture.testFixture
import com.jetbrains.python.getOrThrow
import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.skeleton.PySkeletonUtil
import com.jetbrains.python.tools.createUvPipVenvSdk
import com.jetbrains.python.venvReader.VirtualEnvReader
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.nio.file.Path
import kotlin.io.path.pathString
import kotlin.time.Duration.Companion.minutes
/**
* Create virtual env using UV and setting uv as a Package manager via ui pip. If [addToSdkTable] then also added to the project jdk table.
*
* Similar to [pyVenvFixture] but uses `uv venv` instead of virtualenv helper.
* UV is significantly faster for venv creation and package installation.
* UV will be downloaded automatically if not present.
*
*/
fun TestFixture<SdkFixture<PyEnvironment>>.pyUvVenvFixture(
addToSdkTable: Boolean,
moduleFixture: TestFixture<Module>,
): TestFixture<Sdk> = testFixture {
val env = this@pyUvVenvFixture.init().env
val module = moduleFixture.init()
val baseDirPath = module.baseDir?.toNioPath()
requireNotNull(baseDirPath) { "Module $module has no base dir" }
val venvDir = baseDirPath.resolve(".venv")
// Get or download UV executable
val uvExecutable = getOrDownloadUvExecutable(LATEST_UV_VERSION)
// Create venv using UV
runExecutableWithProgress(
uvExecutable, venvDir.parent, 10.minutes, emptyMap(),
"venv", venvDir.pathString, "--python", env.pythonPath.pathString
).getOrThrow()
// Find Python in created venv
val venvPython = withContext(Dispatchers.IO) {
VirtualEnvReader().findPythonInPythonRoot(venvDir)
} ?: error("Python executable not found in UV venv: $venvDir")
val venvSdk = withContext(Dispatchers.IO) { createUvPipVenvSdk(venvPython, baseDirPath) }
if (addToSdkTable) {
venvSdk.persist()
module.pythonSdk = venvSdk
venvSdk.setAssociationToModule(module)
}
// workaround interesting behavior of VFS_STRUCTURAL_MODIFICATIONS
PySkeletonUtil.getSitePackagesDirectory(venvSdk)?.getChildren()
initialized(venvSdk) {
edtWriteAction {
ProjectJdkTable.getInstance().removeJdk(venvSdk)
}
}
}
@@ -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.python.test.env.uv
import com.intellij.openapi.diagnostic.Logger
import com.intellij.python.test.env.core.PyEnvDownloadCache
import com.intellij.python.test.env.core.extractIfNecessary
import com.intellij.python.test.env.core.markExecutable
import com.intellij.python.test.env.core.unpackArchive
import com.intellij.util.system.CpuArch
import com.intellij.util.system.OS
import org.jetbrains.annotations.ApiStatus
import java.nio.file.Path
private const val UV_GITHUB_RELEASE_URL = "https://github.com/astral-sh/uv/releases"
private val LOG = Logger.getInstance("com.intellij.python.test.env.uv")
/**
* Downloads and sets up UV binary for the specified version.
* If UV is already downloaded for this version, returns the cached executable.
*
* @param uvVersion The UV version to download (e.g., "0.5.11")
* @return Path to the UV executable
*/
@ApiStatus.Internal
suspend fun getOrDownloadUvExecutable(uvVersion: String): Path {
val logger = LOG
val downloadUrl = getUvDownloadUrl(uvVersion)
val archiveFileName = downloadUrl.substringAfterLast('/')
val archiveBaseName = archiveFileName.substringBeforeLast(".tar.gz").substringBeforeLast(".zip")
val uvDir = PyEnvDownloadCache.cacheDirectory().resolve("uv").resolve(archiveBaseName)
val uvExecutable = uvDir.resolve(if (OS.CURRENT == OS.Windows) "uv.exe" else "uv")
extractIfNecessary(uvDir, logger) { target ->
logger.info("Downloading UV archive: $archiveFileName")
val cachedArchive = PyEnvDownloadCache.getOrDownload(downloadUrl, archiveFileName)
logger.info("UV archive downloaded: $cachedArchive")
logger.info("Extracting UV binary (stripping prefix: $archiveBaseName)")
unpackArchive(cachedArchive, target, prefixToStrip = archiveBaseName)
markExecutable(logger, uvExecutable)
}
return uvExecutable
}
private fun getUvDownloadUrl(version: String): String {
val arch = when {
CpuArch.isArm64() -> "aarch64"
CpuArch.isIntel64() -> "x86_64"
else -> "x86_64"
}
val platform = when (OS.CURRENT) {
OS.Windows -> "$arch-pc-windows-msvc"
OS.macOS -> "$arch-apple-darwin"
OS.Linux -> "$arch-unknown-linux-gnu"
else -> throw IllegalStateException("Unsupported OS: ${OS.CURRENT}")
}
return "$UV_GITHUB_RELEASE_URL/download/$version/uv-$platform.tar.gz"
}
@@ -3,8 +3,6 @@ package com.intellij.python.test.env.uv
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.python.test.env.core.*
import com.intellij.python.test.env.core.extractIfNecessary
import com.intellij.util.system.CpuArch
import com.intellij.util.system.OS
import com.jetbrains.python.PythonBinary
import kotlinx.coroutines.Dispatchers
@@ -64,10 +62,6 @@ fun uvEnvironment(uvVersion: String, block: UvPyEnvironmentSpec.() -> Unit): UvP
@ApiStatus.Internal
class UvPyEnvironmentProvider : PyEnvironmentProvider<UvPyEnvironmentSpec>("uv") {
private companion object {
const val UV_GITHUB_RELEASE_URL = "https://github.com/astral-sh/uv/releases"
}
override suspend fun setupEnvironment(context: Context, spec: UvPyEnvironmentSpec): PyEnvironment {
val logger = thisLogger()
val targetPath = nextEnvPath(context.workingDir)
@@ -109,34 +103,10 @@ class UvPyEnvironmentProvider : PyEnvironmentProvider<UvPyEnvironmentSpec>("uv")
/**
* Downloads and unpacks UV binary for the current OS to cache directory.
* Directory name includes archive name to handle different OS/architecture variants.
* Returns path to UV executable.
*/
private suspend fun downloadAndSetupUv(spec: UvPyEnvironmentSpec): Path {
val logger = thisLogger()
// Determine platform-specific download URL
val downloadUrl = getUvDownloadUrl(spec.uvVersion)
val archiveFileName = downloadUrl.substringAfterLast('/')
// Use archive name (without extension) as directory name to handle different variants
val archiveBaseName = archiveFileName.substringBeforeLast(".tar.gz").substringBeforeLast(".zip")
val uvDir = PyEnvDownloadCache.cacheDirectory().resolve("uv").resolve(archiveBaseName)
val uvExecutable = uvDir.resolve(if (OS.CURRENT == OS.Windows) "uv.exe" else "uv")
extractIfNecessary(uvDir, logger) { target ->
logger.info("Downloading UV archive: $archiveFileName")
val cachedArchive = PyEnvDownloadCache.getOrDownload(downloadUrl, archiveFileName)
logger.info("UV archive downloaded: $cachedArchive")
// Extract UV binary directly from cached archive
// UV archives are structured as: uv-<platform>/uv or uv-<platform>/uv.exe
// We need to strip the first directory component
val archivePrefix = archiveBaseName // e.g., "uv-x86_64-unknown-linux-gnu"
logger.info("Extracting UV binary (stripping prefix: $archivePrefix)")
unpackArchive(cachedArchive, target, prefixToStrip = archivePrefix)
markExecutable(logger, uvExecutable)
}
return uvExecutable
return getOrDownloadUvExecutable(spec.uvVersion)
}
/**
@@ -185,26 +155,6 @@ class UvPyEnvironmentProvider : PyEnvironmentProvider<UvPyEnvironmentSpec>("uv")
}
}
private fun getUvDownloadUrl(version: String): String {
val arch = when {
CpuArch.isArm64() -> "aarch64"
CpuArch.isIntel64() -> "x86_64"
else -> "x86_64"
}
val platform = when (OS.CURRENT) {
OS.Windows -> "$arch-pc-windows-msvc"
OS.macOS -> "$arch-apple-darwin"
OS.Linux -> "$arch-unknown-linux-gnu"
else -> throw IllegalStateException("Unsupported OS: ${OS.CURRENT}")
}
return "$UV_GITHUB_RELEASE_URL/download/$version/uv-$platform.tar.gz"
}
/**
* UV Python environment implementation
*/
}
class UvPyEnvironment(
@@ -12,13 +12,12 @@ import com.intellij.openapi.util.Version
import com.intellij.openapi.vfs.VirtualFile
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonFileType
import com.jetbrains.python.packaging.management.PythonPackageManager
import com.jetbrains.python.packaging.management.hasInstalledPackageSnapshot
import com.jetbrains.python.psi.resolve.PackageAvailabilitySpec
import com.jetbrains.python.psi.resolve.isPackageAvailable
import com.jetbrains.python.pathValidation.PlatformAndRoot
import com.jetbrains.python.pathValidation.ValidationRequest
import com.jetbrains.python.pathValidation.validateExecutableFile
import com.jetbrains.python.pyi.PyiFileType
import com.jetbrains.python.util.runWithModalBlockingOrInBackground
import org.jetbrains.annotations.SystemDependent
import java.nio.file.Path
@@ -27,6 +26,7 @@ class BlackFormatterUtil {
val LOG: Logger = thisLogger()
const val PACKAGE_NAME: String = "black"
private val PACKAGE_SPEC: PackageAvailabilitySpec = PackageAvailabilitySpec("black", "black.Mode", "black.mode.Mode")
val MINIMAL_LINE_RANGES_COMPATIBLE_VERSION: Version = Version(23, 11, 0)
@@ -35,12 +35,8 @@ class BlackFormatterUtil {
}
fun isBlackFormatterInstalledOnProjectSdk(project: Project, sdk: Sdk?): Boolean {
val packageManager = sdk?.let {
runWithModalBlockingOrInBackground(project, "Updating packages info…") {
PythonPackageManager.forSdk(project, sdk)
}
}
return packageManager?.hasInstalledPackageSnapshot(PACKAGE_NAME) ?: false
if (sdk == null) return false
return isPackageAvailable(project, sdk, PACKAGE_SPEC)
}
fun detectBlackExecutable(): Path? {
@@ -168,7 +168,8 @@ public class PyIntegratedToolsConfigurable implements SearchableConfigurable {
var factory = myModel.getSelected();
if (factory != null && !factory.isFrameworkInstalled(myProject, sdk)) {
return new ValidationResult(PyBundle.message("runcfg.testing.no.test.framework", factory.getName()),
createQuickFix(sdk, facetErrorPanel, factory.getPackageRequired()));
// isFrameworkInstalled() == false => getPackageSpec() != null
createQuickFix(sdk, facetErrorPanel, Objects.requireNonNull(factory.getPackageSpec()).getPackageName()));
}
}
return ValidationResult.OK;
@@ -33,9 +33,8 @@ internal class HatchPackageManager(project: Project, sdk: Sdk) : PipPythonPackag
}
internal class HatchPackageManagerProvider : PythonPackageManagerProvider {
override suspend fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager? = when {
override fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager? = when {
sdk.isHatch -> HatchPackageManager(project, sdk)
else -> null
}
}

Some files were not shown because too many files have changed in this diff Show More