mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Merge ktij/vd/KTIJ-37261/kt-master-merge-2026-01-26 into master
GitOrigin-RevId: 2f351d64ccc9180dacc0a1ab519ea8176dc39d9f
This commit is contained in:
@@ -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:
|
||||
|
||||
Generated
+35
-3
@@ -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.
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
+47
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
+27
-14
@@ -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"
|
||||
|
||||
+2
-2
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
+39
-11
@@ -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
|
||||
|
||||
+7
-3
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+39
-25
@@ -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
|
||||
|
||||
+1
-1
@@ -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) {
|
||||
|
||||
+2
-1
@@ -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);
|
||||
}
|
||||
|
||||
+38
-45
@@ -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{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-11
@@ -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
|
||||
|
||||
+2
-1
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -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>)
|
||||
}
|
||||
|
||||
+25
-7
@@ -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)
|
||||
}
|
||||
}
|
||||
+10
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
+10
@@ -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)
|
||||
|
||||
+9
@@ -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
|
||||
+8
@@ -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
|
||||
+16
@@ -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
|
||||
|
||||
+3
-3
@@ -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) =
|
||||
|
||||
+4
-3
@@ -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
-2
@@ -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
|
||||
|
||||
+4
-4
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-10
@@ -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
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
+1
@@ -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
-8
@@ -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
|
||||
}
|
||||
}
|
||||
+1
-24
@@ -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" />
|
||||
|
||||
Vendored
+3
-1
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+73
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -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"
|
||||
}
|
||||
+1
-51
@@ -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
Reference in New Issue
Block a user