Merge roman.golyshev/kt-master-sync-20-10-2025 into master

GitOrigin-RevId: f3e302cf2fbb2cd9261a7f999e2e8277827d5a9d
This commit is contained in:
Patronus
2025-10-20 19:30:28 +00:00
committed by intellij-monorepo-bot
268 changed files with 3811 additions and 2067 deletions
+5 -5
View File
@@ -1,18 +1,18 @@
<component name="libraryTable">
<library name="kotlinx-serialization-cbor" type="repository">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-serialization-cbor-jvm:1.8.1">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-serialization-cbor-jvm:1.9.0">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-cbor-jvm/1.8.1/kotlinx-serialization-cbor-jvm-1.8.1.jar">
<sha256sum>604c4130ca7a0e979a449cf550087a5c2c586485e8af01dcf24b03b7b287306d</sha256sum>
<artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-cbor-jvm/1.9.0/kotlinx-serialization-cbor-jvm-1.9.0.jar">
<sha256sum>e45f32f0d80e1164593cfcf9f1907ae39871084c6aa08e49acc2e8e22e1d93ce</sha256sum>
</artifact>
</verification>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-cbor-jvm/1.8.1/kotlinx-serialization-cbor-jvm-1.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-cbor-jvm/1.9.0/kotlinx-serialization-cbor-jvm-1.9.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-cbor-jvm/1.8.1/kotlinx-serialization-cbor-jvm-1.8.1-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-cbor-jvm/1.9.0/kotlinx-serialization-cbor-jvm-1.9.0-sources.jar!/" />
</SOURCES>
</library>
</component>
+5 -5
View File
@@ -1,18 +1,18 @@
<component name="libraryTable">
<library name="kotlinx-serialization-core" type="repository">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.8.1">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.9.0">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.8.1/kotlinx-serialization-core-jvm-1.8.1.jar">
<sha256sum>3565b6d4d789bf70683c45566944287fc1d8dc75c23d98bd87d01059cc76f2b3</sha256sum>
<artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.9.0/kotlinx-serialization-core-jvm-1.9.0.jar">
<sha256sum>1f0afa172110e45a7231ef1b44ae8fd84c1ebaff96f3fc3ad68ef8c48120b59c</sha256sum>
</artifact>
</verification>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.8.1/kotlinx-serialization-core-jvm-1.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.9.0/kotlinx-serialization-core-jvm-1.9.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.8.1/kotlinx-serialization-core-jvm-1.8.1-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.9.0/kotlinx-serialization-core-jvm-1.9.0-sources.jar!/" />
</SOURCES>
</library>
</component>
+5 -5
View File
@@ -1,18 +1,18 @@
<component name="libraryTable">
<library name="kotlinx-serialization-json" type="repository">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.8.1">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.9.0">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-json-jvm/1.8.1/kotlinx-serialization-json-jvm-1.8.1.jar">
<sha256sum>8769e5647557e3700919c32d508f5c5dad53c5d8234cd10846354fbcff14aa24</sha256sum>
<artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-json-jvm/1.9.0/kotlinx-serialization-json-jvm-1.9.0.jar">
<sha256sum>d94cc34cae39246a1af74fda63f9c4812ce12216ef641d5fa3bbbb539a6922d8</sha256sum>
</artifact>
</verification>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-json-jvm/1.8.1/kotlinx-serialization-json-jvm-1.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-json-jvm/1.9.0/kotlinx-serialization-json-jvm-1.9.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-json-jvm/1.8.1/kotlinx-serialization-json-jvm-1.8.1-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-json-jvm/1.9.0/kotlinx-serialization-json-jvm-1.9.0-sources.jar!/" />
</SOURCES>
</library>
</component>
+5 -5
View File
@@ -1,18 +1,18 @@
<component name="libraryTable">
<library name="kotlinx-serialization-json-io" type="repository">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-serialization-json-io-jvm:1.8.1">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-serialization-json-io-jvm:1.9.0">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-json-io-jvm/1.8.1/kotlinx-serialization-json-io-jvm-1.8.1.jar">
<sha256sum>ab5ec8ea48815197c46a7df9f418a62791fe91cda5023b941ce4de872d810cc1</sha256sum>
<artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-json-io-jvm/1.9.0/kotlinx-serialization-json-io-jvm-1.9.0.jar">
<sha256sum>98abb132b03e11b80318d45350ec9ebbb5453d26505e81331bf2eea7143b6fbe</sha256sum>
</artifact>
</verification>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-json-io-jvm/1.8.1/kotlinx-serialization-json-io-jvm-1.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-json-io-jvm/1.9.0/kotlinx-serialization-json-io-jvm-1.9.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-json-io-jvm/1.8.1/kotlinx-serialization-json-io-jvm-1.8.1-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-json-io-jvm/1.9.0/kotlinx-serialization-json-io-jvm-1.9.0-sources.jar!/" />
</SOURCES>
</library>
</component>
+5 -5
View File
@@ -1,18 +1,18 @@
<component name="libraryTable">
<library name="kotlinx-serialization-protobuf" type="repository">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-serialization-protobuf-jvm:1.8.1">
<properties include-transitive-deps="false" maven-id="org.jetbrains.kotlinx:kotlinx-serialization-protobuf-jvm:1.9.0">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-protobuf-jvm/1.8.1/kotlinx-serialization-protobuf-jvm-1.8.1.jar">
<sha256sum>fbd174f72ea82364baa112069f2d7b361758f6b68bd81d2ac51b02c66a48e924</sha256sum>
<artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-protobuf-jvm/1.9.0/kotlinx-serialization-protobuf-jvm-1.9.0.jar">
<sha256sum>555035205ad1a21254b49fd6b5ddbe6ad1abb5c7baba244258bd758f80687bca</sha256sum>
</artifact>
</verification>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-protobuf-jvm/1.8.1/kotlinx-serialization-protobuf-jvm-1.8.1.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-protobuf-jvm/1.9.0/kotlinx-serialization-protobuf-jvm-1.9.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-protobuf-jvm/1.8.1/kotlinx-serialization-protobuf-jvm-1.8.1-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlinx/kotlinx-serialization-protobuf-jvm/1.9.0/kotlinx-serialization-protobuf-jvm-1.9.0-sources.jar!/" />
</SOURCES>
</library>
</component>
@@ -3,8 +3,6 @@ package org.jetbrains.jps.dependency.impl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.*;
import org.jetbrains.jps.dependency.java.SubclassesIndex;
import org.jetbrains.jps.dependency.kotlin.TypealiasesIndex;
import org.jetbrains.jps.util.Iterators;
import java.util.Collections;
@@ -26,10 +24,8 @@ public final class DeltaImpl extends GraphImpl implements Delta {
private final Set<NodeSource> myBaseSources;
private final Set<NodeSource> myDeletedSources;
public DeltaImpl(Iterable<NodeSource> baseSources, Iterable<NodeSource> deletedSources) {
super(Containers.MEMORY_CONTAINER_FACTORY);
addIndex(new SubclassesIndex(Containers.MEMORY_CONTAINER_FACTORY));
addIndex(new TypealiasesIndex(Containers.MEMORY_CONTAINER_FACTORY)); // todo: make registration 'pluggable', e.g. via DifferentiateStrategy
public DeltaImpl(Iterable<NodeSource> baseSources, Iterable<NodeSource> deletedSources, IndexFactory indexFactory) {
super(Containers.MEMORY_CONTAINER_FACTORY, indexFactory);
myBaseSources = Collections.unmodifiableSet(baseSources instanceof Set? (Set<? extends NodeSource>)baseSources : Iterators.collect(baseSources, new HashSet<>()));
myDeletedSources = Collections.unmodifiableSet(deletedSources instanceof Set? (Set<? extends NodeSource>)deletedSources : Iterators.collect(deletedSources, new HashSet<>()));
}
@@ -6,9 +6,7 @@ import org.jetbrains.jps.dependency.*;
import org.jetbrains.jps.dependency.diff.DiffCapable;
import org.jetbrains.jps.dependency.diff.Difference;
import org.jetbrains.jps.dependency.java.GeneralJvmDifferentiateStrategy;
import org.jetbrains.jps.dependency.java.SubclassesIndex;
import org.jetbrains.jps.dependency.kotlin.KotlinSourceOnlyDifferentiateStrategy;
import org.jetbrains.jps.dependency.kotlin.TypealiasesIndex;
import java.util.*;
import java.util.function.Function;
@@ -19,31 +17,24 @@ import static org.jetbrains.jps.util.Iterators.*;
public final class DependencyGraphImpl extends GraphImpl implements DependencyGraph {
private static final List<DifferentiateStrategy> ourDifferentiateStrategies = List.of(new KotlinSourceOnlyDifferentiateStrategy(), new GeneralJvmDifferentiateStrategy());
private final Set<String> myRegisteredIndices;
public DependencyGraphImpl(MapletFactory containerFactory) {
super(containerFactory);
try {
addIndex(new SubclassesIndex(containerFactory));
addIndex(new TypealiasesIndex(containerFactory)); // todo: make registration 'pluggable', e.g. via DifferentiateStrategy
myRegisteredIndices = Collections.unmodifiableSet(collect(map(getIndices(), index -> index.getName()), new HashSet<>()));
}
catch (RuntimeException e) {
closeIgnoreErrors();
throw e;
}
this(containerFactory, IndexFactory.mandatoryIndices());
}
/**
* Note that IndexFactory must register all mandatory indices that {@link IndexFactory#mandatoryIndices()} provides
*/
public DependencyGraphImpl(MapletFactory containerFactory, IndexFactory idxFactory) {
super(containerFactory, idxFactory);
}
@Override
public Delta createDelta(Iterable<NodeSource> compiledSources, Iterable<NodeSource> deletedSources, boolean isSourceOnly) {
Delta delta = isSourceOnly? new SourceOnlyDelta(myRegisteredIndices, compiledSources, deletedSources) : new DeltaImpl(compiledSources, deletedSources);
Set<String> deltaIndices = collect(map(delta.getIndices(), index -> index.getName()), new HashSet<>());
if (!myRegisteredIndices.equals(deltaIndices)) {
throw new RuntimeException("Graph delta should contain the same set of indices as the base graph\n\tCurrent graph indices: " + myRegisteredIndices + "\n\tCurrent Delta indices: " + deltaIndices);
if (isSourceOnly) {
return new SourceOnlyDelta(map(getIndices(), BackDependencyIndex::getName), compiledSources, deletedSources);
}
return delta;
return new DeltaImpl(compiledSources, deletedSources, getIndexFactory());
}
@Override
@@ -6,12 +6,17 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.dependency.*;
import org.jetbrains.jps.dependency.java.JvmNodeReferenceID;
import org.jetbrains.jps.dependency.java.SubclassesIndex;
import org.jetbrains.jps.dependency.kotlin.TypealiasesIndex;
import org.jetbrains.jps.util.Iterators;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import java.util.function.Function;
import static org.jetbrains.jps.util.Iterators.*;
// this is a base implementation for shared functionality in both DependencyGraph and Delta
@ApiStatus.Internal
@@ -21,11 +26,57 @@ public abstract class GraphImpl implements Graph {
protected final MultiMaplet<ReferenceID, NodeSource> myNodeToSourcesMap;
protected final MultiMaplet<NodeSource, Node<?, ?>> mySourceToNodesMap;
private final MapletFactory myContainerFactory;
private final IndexFactory myIndexFactory;
private static final Set<String> ourMandatoryIndexNames = Set.of(
NodeDependenciesIndex.NAME, SubclassesIndex.NAME, TypealiasesIndex.NAME
);
protected GraphImpl(@NotNull MapletFactory cFactory) {
public interface IndexFactory {
Iterable<BackDependencyIndex> createIndices(@NotNull MapletFactory cFactory);
/**
* An index factory creating set of mandatory indices that must be present in all graph instances
*/
static IndexFactory mandatoryIndices() {
return containerFactory -> List.of(
new NodeDependenciesIndex(containerFactory),
new SubclassesIndex(containerFactory),
new TypealiasesIndex(containerFactory)
);
}
/**
*
* @param extension indices
* @return a combined index factory that creates all mandatory indices plus those specified
*/
static IndexFactory create(Function<MapletFactory, BackDependencyIndex>... extIndices) {
IndexFactory mandatory = mandatoryIndices();
return containerFactory -> collect(
map(Arrays.asList(extIndices), extIndex -> extIndex.apply(containerFactory)),
collect(mandatory.createIndices(containerFactory), new ArrayList<>())
);
}
}
protected GraphImpl(@NotNull MapletFactory cFactory, IndexFactory indexFactory) {
myContainerFactory = cFactory;
myIndexFactory = indexFactory;
try {
addIndex(myDependencyIndex = new NodeDependenciesIndex(cFactory));
Set<String> registered = new HashSet<>();
for (BackDependencyIndex index : indexFactory.createIndices(cFactory)) {
myIndices.add(index);
registered.add(index.getName());
}
if (!registered.containsAll(ourMandatoryIndexNames)) {
throw new RuntimeException("Dependency Graph must contain following mandatory indices: " + ourMandatoryIndexNames + "\n\tCurrent registered indices: " + registered);
}
myDependencyIndex = find(myIndices, idx -> NodeDependenciesIndex.NAME.equals(idx.getName()));
// important: if multiple implementations of NodeSource are available, change to generic graph element externalizer
Externalizer<NodeSource> srcExternalizer = Externalizer.forGraphElement(PathSource::new, NodeSource[]::new);
@@ -38,8 +89,8 @@ public abstract class GraphImpl implements Graph {
}
}
protected final void addIndex(BackDependencyIndex index) {
myIndices.add(index);
protected final IndexFactory getIndexFactory() {
return myIndexFactory;
}
@Override
@@ -0,0 +1,35 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.jps.dependency.kotlin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jps.dependency.MapletFactory;
import org.jetbrains.jps.dependency.Node;
import org.jetbrains.jps.dependency.ReferenceID;
import org.jetbrains.jps.dependency.impl.BackDependencyIndexImpl;
import org.jetbrains.jps.dependency.java.JvmNodeReferenceID;
import org.jetbrains.jps.dependency.java.LookupNameUsage;
import org.jetbrains.jps.util.Iterators;
public final class LookupsIndex extends BackDependencyIndexImpl {
public static final String NAME = "lookups";
public LookupsIndex(@NotNull MapletFactory cFactory) {
super(NAME, cFactory);
}
@Override
public Iterable<ReferenceID> getIndexedDependencies(@NotNull Node<?, ?> node) {
return Iterators.map(
Iterators.filter(node.getUsages(), usage -> usage instanceof LookupNameUsage),
usage -> {
LookupNameUsage lookupNameUsage = (LookupNameUsage)usage;
String ownerName = lookupNameUsage.getElementOwner().getNodeName();
String name = lookupNameUsage.getName();
return ownerName == null || ownerName.isEmpty()
? new JvmNodeReferenceID(name)
: new JvmNodeReferenceID(ownerName + "/" + name);
});
}
}
@@ -36,6 +36,9 @@ public interface BuildContext extends DiagnosticSink {
@Nullable
Path getAbiOutputZip();
@Nullable
Path getKotlinCriStoragePath();
NodeSourceSnapshot getSources();
NodeSourceSnapshot getBinaryDependencies();
@@ -13,6 +13,7 @@ public interface DataPaths {
String OLD_DEPS_DIR_NAME = "past-deps";
String ABI_JAR_SUFFIX = ".abi.jar";
String DATA_DIR_NAME_SUFFIX = "-ic";
String KOTLIN_CRI_STORAGE_SUFFIX = "-kotlinCriStorage";
String KOTLIN_MODULE_EXTENSION = ".kotlin_module";
@@ -1,9 +1,7 @@
// 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.tools.build.bazel.jvmIncBuilder;
import com.intellij.tools.build.bazel.jvmIncBuilder.impl.CompositeZipOutputBuilder;
import com.intellij.tools.build.bazel.jvmIncBuilder.impl.Utils;
import com.intellij.tools.build.bazel.jvmIncBuilder.impl.ZipOutputBuilderImpl;
import com.intellij.tools.build.bazel.jvmIncBuilder.impl.*;
import com.intellij.tools.build.bazel.jvmIncBuilder.impl.forms.FormBinding;
import com.intellij.tools.build.bazel.jvmIncBuilder.impl.graph.PersistentMVStoreMapletFactory;
import com.intellij.tools.build.bazel.jvmIncBuilder.instrumentation.InstrumentationClassFinder;
@@ -15,6 +13,7 @@ import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.dependency.DependencyGraph;
import org.jetbrains.jps.dependency.GraphConfiguration;
import org.jetbrains.jps.dependency.impl.DependencyGraphImpl;
import org.jetbrains.jps.dependency.kotlin.LookupsIndex;
import java.io.*;
import java.net.MalformedURLException;
@@ -34,6 +33,7 @@ public class StorageManager implements CloseableExt {
private CompositeZipOutputBuilder myComposite;
private InstrumentationClassFinder myInstrumentationClassFinder;
private FormBinding myFormBinding;
private boolean isKotlinCriDataGenerationEnabled;
private final MVStore myDataSwapStore;
@@ -45,6 +45,7 @@ public class StorageManager implements CloseableExt {
.cacheSize(8)
.open();
myDataSwapStore.setVersionsToKeep(0);
isKotlinCriDataGenerationEnabled = myContext.getKotlinCriStoragePath() != null;
}
public void cleanBuildState() throws IOException {
@@ -106,14 +107,29 @@ public class StorageManager implements CloseableExt {
@NotNull
public GraphConfiguration getGraphConfiguration() throws IOException {
GraphConfiguration config = myGraphConfig;
if (config == null) {
DependencyGraphImpl graph = new DependencyGraphImpl(
new PersistentMVStoreMapletFactory(DataPaths.getDepGraphStoreFile(myContext).toString(), Math.min(8, Runtime.getRuntime().availableProcessors()))
if (myGraphConfig != null) {
return myGraphConfig;
}
DependencyGraphImpl graph = createDependencyGraph();
myGraphConfig = GraphConfiguration.create(graph, myContext.getPathMapper());
return myGraphConfig;
}
@NotNull
private DependencyGraphImpl createDependencyGraph() throws IOException {
var filePath = DataPaths.getDepGraphStoreFile(myContext).toString();
int maxBuilderThreads = Math.min(8, Runtime.getRuntime().availableProcessors());
var containerFactory = new PersistentMVStoreMapletFactory(filePath, maxBuilderThreads);
if (isKotlinCriDataGenerationEnabled) {
return new DependencyGraphImpl(
containerFactory,
DependencyGraphImpl.IndexFactory.create(LookupsIndex::new)
);
myGraphConfig = config = GraphConfiguration.create(graph, myContext.getPathMapper());
} else {
return new DependencyGraphImpl(containerFactory);
}
return config;
}
@NotNull
@@ -190,6 +206,7 @@ public class StorageManager implements CloseableExt {
GraphConfiguration config = myGraphConfig;
if (config != null) {
myGraphConfig = null;
writeKotlinCriData(config.getGraph(), saveChanges);
safeClose(config.getGraph(), saveChanges);
}
@@ -208,6 +225,31 @@ public class StorageManager implements CloseableExt {
}
}
private void writeKotlinCriData(DependencyGraph graph, Boolean saveChanges) {
if (!saveChanges || !isKotlinCriDataGenerationEnabled) return;
Path kotlinCriPath = myContext.getKotlinCriStoragePath();
if (kotlinCriPath == null) return;
if (!Files.exists(kotlinCriPath)) {
try { Files.createDirectories(kotlinCriPath); }
catch (IOException e) { myContext.report(Message.create(null, e)); }
}
KotlinCriUtilKt.prepareSerializedData(graph)
.forEach(
(name, content) -> {
try {
Files.write(kotlinCriPath.resolve(name), content,
StandardOpenOption.CREATE,
StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING);
}
catch (IOException e) {
myContext.report(Message.create(null, e));
}
}
);
}
private void safeClose(Closeable cl, boolean saveChanges) {
try {
if (cl instanceof CloseableExt) {
@@ -29,6 +29,7 @@ public class BuildContextImpl implements BuildContext {
private final Appendable myMessageSink;
private final @NotNull Path myOutJar;
private final @Nullable Path myAbiJar;
private final @Nullable Path myKotlinCriStoragePath;
private final Path myDataDir;
private final @NotNull NodeSourceSnapshot mySources;
@@ -60,6 +61,8 @@ public class BuildContextImpl implements BuildContext {
String abiPath = CLFlags.ABI_OUT.getOptionalScalarValue(flags);
myAbiJar = abiPath != null? baseDir.resolve(abiPath).normalize() : null;
myKotlinCriStoragePath = myOutJar.resolveSibling(truncateExtension(myOutJar.getFileName().toString()) + DataPaths.KOTLIN_CRI_STORAGE_SUFFIX);
myDataDir = myOutJar.resolveSibling(truncateExtension(myOutJar.getFileName().toString()) + DataPaths.DATA_DIR_NAME_SUFFIX);
myIsRebuild = CLFlags.NON_INCREMENTAL.isFlagSet(flags);
@@ -320,6 +323,11 @@ public class BuildContextImpl implements BuildContext {
return myAbiJar;
}
@Override
public @Nullable Path getKotlinCriStoragePath() {
return myKotlinCriStoragePath;
}
@Override
public @NotNull NodeSourceSnapshot getSources() {
return mySources;
@@ -0,0 +1,100 @@
// 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.tools.build.bazel.jvmIncBuilder.impl
import java.io.ByteArrayOutputStream
import java.io.DataOutputStream
import org.jetbrains.jps.dependency.BackDependencyIndex
import org.jetbrains.jps.dependency.DependencyGraph
import org.jetbrains.jps.dependency.ReferenceID
import org.jetbrains.jps.dependency.java.JvmNodeReferenceID
import org.jetbrains.jps.dependency.java.SubclassesIndex
import org.jetbrains.jps.dependency.kotlin.LookupsIndex
const val VERSION = 1
internal fun prepareSerializedData(graph: DependencyGraph): Map<String, ByteArray> {
val serializedDataResult = mutableMapOf<String, ByteArray>()
// process subtypes
val subclassesIndex: BackDependencyIndex? = graph.getIndex(SubclassesIndex.NAME)
val subclassesCount = subclassesIndex?.keys?.count() ?: 0
if (subclassesCount > 0) {
ByteArrayOutputStream().use { byteStream ->
DataOutputStream(byteStream).use { dataOut ->
dataOut.writeVersion()
dataOut.writeInt(subclassesCount)
subclassesIndex?.keys?.forEach { key ->
val dependencies = subclassesIndex.getDependencies(key).map { it.toFqName() }
dataOut.writeUTF(key.toFqName())
dataOut.writeInt(dependencies.size)
dependencies.forEach { dataOut.writeUTF(it) }
}
}
byteStream.toByteArray()
}.also { serializedDataResult["subtypes"] = it }
}
// process lookups
val lookupsIndex: BackDependencyIndex? = graph.getIndex(LookupsIndex.NAME)
val lookupsCount = lookupsIndex?.keys?.count() ?: 0
val lookupsAccumulator = mutableMapOf<String, Int>()
if (lookupsCount > 0) {
ByteArrayOutputStream().use { byteStream ->
DataOutputStream(byteStream).use { dataOut ->
dataOut.writeVersion()
dataOut.writeInt(lookupsCount)
lookupsIndex?.keys?.forEach { key ->
val dependencies = lookupsIndex.getDependencies(key)
.mapNotNull { (it as? JvmNodeReferenceID)?.nodeName }
dataOut.writeUTF(key.toFqName())
dataOut.writeInt(dependencies.count())
dependencies.forEach {
dataOut.writeInt(it.addFilePathIfNeeded(lookupsAccumulator)) }
}
}
byteStream.toByteArray()
}.also { serializedDataResult["lookups"] = it }
if (lookupsAccumulator.isNotEmpty()) {
ByteArrayOutputStream().use { byteStream ->
DataOutputStream(byteStream).use { dataOut ->
dataOut.writeVersion()
dataOut.writeInt(lookupsAccumulator.size)
lookupsAccumulator.forEach { (path, fileId) ->
dataOut.writeInt(fileId)
dataOut.writeUTF(path)
}
}
byteStream.toByteArray()
}.also { serializedDataResult["fileIdToPath"] = it }
}
}
return serializedDataResult
}
/**
* Converts the current `ReferenceID` to its fully qualified name (FqName) representation.
*
* Example transformation:
* Input: `com/intellij/AppKt$foo$3$1`
* Output: `com.intellij.AppKt.foo`
*
* @return The fully qualified name as a `String`.
*/
private fun ReferenceID.toFqName(): String {
return (this as? JvmNodeReferenceID)?.nodeName
?.replace('/', '.')
?.replace(DOLLAR_DIGITS_SUFFIX_REGEX, "")
?.replace('$', '.')
?: this.toString()
}
private val DOLLAR_DIGITS_SUFFIX_REGEX = Regex("(?:\\$\\d+)+$")
private fun String.addFilePathIfNeeded(fileIdToPathEntryAccumulator: MutableMap<String, Int>): Int {
return fileIdToPathEntryAccumulator.computeIfAbsent(this) { fileIdToPathEntryAccumulator.size + 1 }
}
private fun DataOutputStream.writeVersion() = writeInt(VERSION)
@@ -12,6 +12,7 @@ import org.jetbrains.jps.dependency.Delta;
import org.jetbrains.jps.dependency.Graph;
import org.jetbrains.jps.dependency.NodeSource;
import org.jetbrains.jps.dependency.impl.DeltaImpl;
import org.jetbrains.jps.dependency.impl.GraphImpl;
import org.jetbrains.jps.dependency.impl.PathSource;
import org.jetbrains.jps.dependency.java.JvmClassNodeBuilder;
import org.jetbrains.jps.util.Pair;
@@ -41,7 +42,7 @@ public final class LibraryGraphLoader {
private static Pair<NodeSourceSnapshot, Graph> loadReadonlyLibraryGraph(Path jarPath) throws IOException {
try (var is = new BufferedInputStream(Files.newInputStream(jarPath))) {
return loadReadonlyLibraryGraph(new DeltaImpl(Set.of(), Set.of()) /*depGraph.createDelta(Set.of(), Set.of(), false)*/, ClassDataZipEntry.fromSteam(is));
return loadReadonlyLibraryGraph(new DeltaImpl(Set.of(), Set.of(), GraphImpl.IndexFactory.mandatoryIndices()) /*depGraph.createDelta(Set.of(), Set.of(), false)*/, ClassDataZipEntry.fromSteam(is));
}
}
@@ -0,0 +1,4 @@
out/
build/
.idea/
.gradle/
@@ -108,6 +108,7 @@
<keymapExtension implementation="com.intellij.debugger.actions.DebuggerKeymapExtension"/>
<xdebugger.settings implementation="com.intellij.debugger.settings.JavaDebuggerSettings"/>
<registryKey key="execution.java.always.debug" defaultValue="false" description="Always run java processes with the debug agent"/>
<xdebugger.breakpointGroupingRule implementation="com.intellij.debugger.ui.XBreakpointGroupingByPackageRule"/>
<xdebugger.breakpointGroupingRule implementation="com.intellij.debugger.ui.XBreakpointGroupingByClassRule"/>
@@ -219,6 +220,8 @@
description="The timeout between the hit run-to-cursor breakpoint in another thread/coroutine and pause the debugger"/>
<registryKey key="debugger.run.to.cursor.waited.threads.limit" defaultValue="1"
description="How many of threads allow to wait on the hit run-to-cursor breakpoint for the desired hit"/>"/>
<registryKey key="debugger.log.jdi.in.unit.tests" defaultValue="false"
description="Enable full JDI logging under DebugProcessImpl.LOG.debug for unit tests"/>
</extensions>
<actions>
@@ -190,6 +190,9 @@ public abstract class DebuggerUtilsEx extends DebuggerUtils {
catch (PatternSyntaxException e) {
LOG.debug(e);
}
catch (Exception e) {
LOG.error("Exception while matching ClassFilter \"" + classFilter.getPattern() + "\" with the input \"" + qName + "\"", e);
}
return false;
}
@@ -365,5 +365,8 @@
<referenceInjector implementation="com.intellij.microservices.jvm.url.HttpUrlReferenceInjector"/>
<referenceInjector implementation="com.intellij.microservices.jvm.url.WSUrlReferenceInjector"/>
<referenceInjector implementation="com.intellij.microservices.jvm.url.HttpMethodReferenceInjector"/>
<registryKey key="batch.inspections.ignored.jvm.languages" defaultValue="Groovy,Scala"
description="Disable UAST processing for JVM languages; a comma separated list of UAST capable language ids."/>
</extensions>
</idea-plugin>
@@ -26,6 +26,7 @@ import com.intellij.psi.*;
import com.intellij.psi.util.*;
import com.intellij.util.JavaPsiConstructorUtil;
import com.intellij.util.ThreeState;
import com.intellij.util.containers.ConcurrentFactoryMap;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.ExpressionUtils;
import org.jetbrains.annotations.Contract;
@@ -33,9 +34,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ConcurrentMap;
import static com.intellij.codeInspection.dataFlow.DfaUtil.hasImplicitImpureSuperCall;
@@ -272,41 +271,14 @@ public final class CommonDataflow {
public static @Nullable DataflowResult getDataflowResult(@NotNull PsiElement context) {
PsiElement body = DfaUtil.getDataflowContext(context);
if (body == null) return null;
ConcurrentHashMap<PsiElement, DataflowResult> fileMap =
ConcurrentMap<PsiElement, DataflowResult> fileMap =
CachedValuesManager.getCachedValue(body.getContainingFile(), () ->
CachedValueProvider.Result.create(new ConcurrentHashMap<>(), PsiModificationTracker.MODIFICATION_COUNT));
class ManagedCompute implements ForkJoinPool.ManagedBlocker {
DataflowResult myResult;
@Override
public boolean block() {
myResult = fileMap.computeIfAbsent(body, CommonDataflow::runDFA);
return true;
}
@Override
public boolean isReleasable() {
myResult = fileMap.get(body);
return myResult != null;
}
DataflowResult getResult() {
return myResult == null || myResult.myResult != RunnerResult.OK ? null : myResult;
}
}
ManagedCompute managedCompute = new ManagedCompute();
try {
ForkJoinPool.managedBlock(managedCompute);
}
catch (RejectedExecutionException ex) {
// Too many FJP threads: execute anyway in current thread
managedCompute.block();
}
catch (InterruptedException ex) {
// Should not happen
throw new AssertionError(ex);
}
return managedCompute.getResult();
CachedValueProvider.Result.create(ConcurrentFactoryMap.createMap(e -> {
DataflowResult result = runDFA(e);
return result.myResult != RunnerResult.OK ? null : result;
}), PsiModificationTracker.MODIFICATION_COUNT));
return fileMap.get(body);
}
/**
@@ -678,10 +678,6 @@
implementationClass="com.intellij.codeInsight.hint.NextParameterAfterCompletionHandler"/>
<compiler.updateResourcesBuildContributor implementation="com.intellij.compiler.impl.JavaResourcesBuildContributor"/>
<!--<expectedTypesProvider implementation="com.intellij.codeInsight.JavaExpectedTypesProvider"/>-->
<projectService serviceImplementation="com.intellij.execution.testDiscovery.JavaAutoRunManager"/>
<registryKey defaultValue="true" description="Trigger autotest on delay instead of build events" key="trigger.autotest.on.delay"/>
@@ -811,6 +807,10 @@
<applicationService serviceInterface="com.intellij.execution.vmOptions.VMOptionsService"
serviceImplementation="com.intellij.execution.vmOptions.VMOptionsServiceImpl"/>
<registryKey key="ide.jvm.run.marker" defaultValue="false" description="Enable JVM-lang-based run line markers"/>
<registryKey key="ide.jvm.command.line.clear.all.environments" defaultValue="true"
description="Clear all environments when running a JVM command line application"/>
<runDashboardCustomizer implementation="com.intellij.execution.CommonJavaRunDashboardCustomizer" id="commonJavaCustomizer"/>
<warmupConfigurator implementation="com.intellij.ide.warmup.JdkWarmupConfigurator"/>
@@ -2785,12 +2785,19 @@
<registryKey key="java.completion.methods.use.tags"
defaultValue="true"
description="Use additional tags for methods for completion"/>
<registryKey key="ide.java.completion.suggest.static.after.instance"
defaultValue="false"
description="Suggest static methods/fields after instance qualifier in Java completion"/>
<registryKey key="java.create.compact.source.file.separately"
defaultValue="true"
description="Show 'Create java compact file' separately from all other classes"/>
<virtualFileCustomDataProvider implementation="com.intellij.ide.JavaCodeStyleSettingsCustomDataSynchronizer"/>
<registryKey key="compiler.external.javac.keep.alive.timeout" defaultValue="300000"
description="If not used for the specified period of time or longer, the IDE will shut down the cached javac compilation process"/>
<registryKey key="compiler.build.can.use.eel" defaultValue="true" description="Allow using Eel API to run build processes"/>
</extensions>
<extensions defaultExtensionNs="org.jetbrains">
@@ -24,6 +24,7 @@ import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.testFramework.BombedProgressIndicator;
import com.intellij.testFramework.SkipSlowTestLocally;
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.ref.GCWatcher;
import org.jetbrains.annotations.NotNull;
@@ -82,7 +83,7 @@ public class ConcurrentIndexTest extends JavaCodeInsightFixtureTestCase {
}));
}
for (Future<?> future : futuresToWait) future.get();
ConcurrencyUtil.getAll(futuresToWait);
}
}
@@ -149,7 +150,7 @@ public class ConcurrentIndexTest extends JavaCodeInsightFixtureTestCase {
}
for (Future<?> future : futuresToWait) future.get();
ConcurrencyUtil.getAll(futuresToWait);
}
}
@@ -209,7 +210,7 @@ public class ConcurrentIndexTest extends JavaCodeInsightFixtureTestCase {
}));
}
for (Future<?> future : futuresToWait) future.get();
ConcurrencyUtil.getAll(futuresToWait);
}
}
@@ -253,12 +254,10 @@ public class ConcurrentIndexTest extends JavaCodeInsightFixtureTestCase {
public void run(@NotNull ProgressIndicator indicator) {
ReadAction.run(() -> {
assertNotNull(JavaPsiFacade.getInstance(project).findClass("Foo", GlobalSearchScope.allScope(project)));
});
});
}
}));
for (Future<?> future : futures) {
future.get();
}
ConcurrencyUtil.getAll(futures);
}
}
}
@@ -7,6 +7,7 @@ import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.NioFiles;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.ZipUtil;
import org.jetbrains.annotations.ApiStatus;
@@ -91,9 +92,7 @@ public final class JpsCompilationOutputLoader implements JpsOutputLoader<List<Ou
myContext.sendDescriptionStatusMessage(JpsBuildBundle.message("progress.text.extracting.downloaded.results"));
List<Future<?>> futureList = ContainerUtil.map(outputLoadResults, loadResult ->
EXECUTOR_SERVICE.submit(new UnzipOutputTask(result, loadResult, myContext)));
for (Future<?> future : futureList) {
future.get();
}
ConcurrencyUtil.getAll(futureList);
myTmpFolderToModuleName = result;
LOG.info("Unzip compilation output took: " + (System.currentTimeMillis() - start));
return LoaderStatus.COMPLETE;
@@ -9,6 +9,7 @@ import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.platform.jps.model.resolver.JpsDependencyResolverConfiguration;
import com.intellij.platform.jps.model.resolver.JpsDependencyResolverConfigurationService;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.SmartList;
import com.intellij.util.SystemProperties;
import com.intellij.util.containers.CollectionFactory;
@@ -226,7 +227,7 @@ public final class DependencyResolvingBuilder extends ModuleLevelBuilder {
ExecutorService executorService = Executors.newFixedThreadPool(parallelism);
try {
List<Future<?>> futures = ContainerUtil.map(libs, lib -> executorService.submit(() -> resolveAction.accept(lib)));
for (Future<?> future : futures) future.get();
ConcurrencyUtil.getAll(futures);
}
finally {
executorService.shutdown();
+10 -10
View File
@@ -4741,37 +4741,37 @@ jvm_import(
jvm_import(
name = "kotlinx-serialization-cbor",
jar = "@org_jetbrains_kotlinx-kotlinx-serialization-cbor-jvm-1_8_1_http//file",
source_jar = "@org_jetbrains_kotlinx-kotlinx-serialization-cbor-jvm-1_8_1_http//file",
jar = "@org_jetbrains_kotlinx-kotlinx-serialization-cbor-jvm-1_9_0_http//file",
source_jar = "@org_jetbrains_kotlinx-kotlinx-serialization-cbor-jvm-1_9_0_http//file",
visibility = ["//visibility:public"]
)
jvm_import(
name = "kotlinx-serialization-core",
jar = "@org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1_http//file",
source_jar = "@org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1_http//file",
jar = "@org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_9_0_http//file",
source_jar = "@org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_9_0_http//file",
exported_compiler_plugins = ["@lib//:kotlin-serialization-plugin"],
visibility = ["//visibility:public"]
)
jvm_import(
name = "kotlinx-serialization-json",
jar = "@org_jetbrains_kotlinx-kotlinx-serialization-json-jvm-1_8_1_http//file",
source_jar = "@org_jetbrains_kotlinx-kotlinx-serialization-json-jvm-1_8_1_http//file",
jar = "@org_jetbrains_kotlinx-kotlinx-serialization-json-jvm-1_9_0_http//file",
source_jar = "@org_jetbrains_kotlinx-kotlinx-serialization-json-jvm-1_9_0_http//file",
visibility = ["//visibility:public"]
)
jvm_import(
name = "kotlinx-serialization-json-io",
jar = "@org_jetbrains_kotlinx-kotlinx-serialization-json-io-jvm-1_8_1_http//file",
source_jar = "@org_jetbrains_kotlinx-kotlinx-serialization-json-io-jvm-1_8_1_http//file",
jar = "@org_jetbrains_kotlinx-kotlinx-serialization-json-io-jvm-1_9_0_http//file",
source_jar = "@org_jetbrains_kotlinx-kotlinx-serialization-json-io-jvm-1_9_0_http//file",
visibility = ["//visibility:public"]
)
jvm_import(
name = "kotlinx-serialization-protobuf",
jar = "@org_jetbrains_kotlinx-kotlinx-serialization-protobuf-jvm-1_8_1_http//file",
source_jar = "@org_jetbrains_kotlinx-kotlinx-serialization-protobuf-jvm-1_8_1_http//file",
jar = "@org_jetbrains_kotlinx-kotlinx-serialization-protobuf-jvm-1_9_0_http//file",
source_jar = "@org_jetbrains_kotlinx-kotlinx-serialization-protobuf-jvm-1_9_0_http//file",
visibility = ["//visibility:public"]
)
+54 -40
View File
@@ -7084,73 +7084,73 @@ http_file(
)
http_file(
name = "org_jetbrains_kotlinx-kotlinx-serialization-cbor-jvm-1_8_1_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-cbor-jvm/1.8.1/kotlinx-serialization-cbor-jvm-1.8.1.jar",
sha256 = "604c4130ca7a0e979a449cf550087a5c2c586485e8af01dcf24b03b7b287306d",
downloaded_file_path = "kotlinx-serialization-cbor-jvm-1.8.1.jar"
name = "org_jetbrains_kotlinx-kotlinx-serialization-cbor-jvm-1_9_0_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-cbor-jvm/1.9.0/kotlinx-serialization-cbor-jvm-1.9.0.jar",
sha256 = "e45f32f0d80e1164593cfcf9f1907ae39871084c6aa08e49acc2e8e22e1d93ce",
downloaded_file_path = "kotlinx-serialization-cbor-jvm-1.9.0.jar"
)
http_file(
name = "org_jetbrains_kotlinx-kotlinx-serialization-cbor-jvm-1_8_1-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-cbor-jvm/1.8.1/kotlinx-serialization-cbor-jvm-1.8.1-sources.jar",
sha256 = "5764d9dd773e48dca2ad2376005281b09bc4a9d38b2512f82421cae43eb163b2",
downloaded_file_path = "kotlinx-serialization-cbor-jvm-1.8.1-sources.jar"
name = "org_jetbrains_kotlinx-kotlinx-serialization-cbor-jvm-1_9_0-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-cbor-jvm/1.9.0/kotlinx-serialization-cbor-jvm-1.9.0-sources.jar",
sha256 = "aaf83c4c7e24060608bff3f7279b0f543b21ed1544103bdfebbcb88adb7021cd",
downloaded_file_path = "kotlinx-serialization-cbor-jvm-1.9.0-sources.jar"
)
http_file(
name = "org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.8.1/kotlinx-serialization-core-jvm-1.8.1.jar",
sha256 = "3565b6d4d789bf70683c45566944287fc1d8dc75c23d98bd87d01059cc76f2b3",
downloaded_file_path = "kotlinx-serialization-core-jvm-1.8.1.jar"
name = "org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_9_0_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.9.0/kotlinx-serialization-core-jvm-1.9.0.jar",
sha256 = "1f0afa172110e45a7231ef1b44ae8fd84c1ebaff96f3fc3ad68ef8c48120b59c",
downloaded_file_path = "kotlinx-serialization-core-jvm-1.9.0.jar"
)
http_file(
name = "org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.8.1/kotlinx-serialization-core-jvm-1.8.1-sources.jar",
sha256 = "d4a7221a0c2ccb47bc736340344b256506cf8a5a49882883f8f56385ae69a1bc",
downloaded_file_path = "kotlinx-serialization-core-jvm-1.8.1-sources.jar"
name = "org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_9_0-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.9.0/kotlinx-serialization-core-jvm-1.9.0-sources.jar",
sha256 = "72ad8ca04529d29cb144f845858f650ad8f06ca3a43c637967ca88b733c06f19",
downloaded_file_path = "kotlinx-serialization-core-jvm-1.9.0-sources.jar"
)
http_file(
name = "org_jetbrains_kotlinx-kotlinx-serialization-json-jvm-1_8_1_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-json-jvm/1.8.1/kotlinx-serialization-json-jvm-1.8.1.jar",
sha256 = "8769e5647557e3700919c32d508f5c5dad53c5d8234cd10846354fbcff14aa24",
downloaded_file_path = "kotlinx-serialization-json-jvm-1.8.1.jar"
name = "org_jetbrains_kotlinx-kotlinx-serialization-json-jvm-1_9_0_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-json-jvm/1.9.0/kotlinx-serialization-json-jvm-1.9.0.jar",
sha256 = "d94cc34cae39246a1af74fda63f9c4812ce12216ef641d5fa3bbbb539a6922d8",
downloaded_file_path = "kotlinx-serialization-json-jvm-1.9.0.jar"
)
http_file(
name = "org_jetbrains_kotlinx-kotlinx-serialization-json-jvm-1_8_1-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-json-jvm/1.8.1/kotlinx-serialization-json-jvm-1.8.1-sources.jar",
sha256 = "5c76c02a6bc319d307a73b2bb4e77d1a0d3c1a25b78e559f662e2eb8ed29cdad",
downloaded_file_path = "kotlinx-serialization-json-jvm-1.8.1-sources.jar"
name = "org_jetbrains_kotlinx-kotlinx-serialization-json-jvm-1_9_0-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-json-jvm/1.9.0/kotlinx-serialization-json-jvm-1.9.0-sources.jar",
sha256 = "6c231ddcd95b1023ffb72cfd98023a035f666e46b4479d68eff83cc30892396c",
downloaded_file_path = "kotlinx-serialization-json-jvm-1.9.0-sources.jar"
)
http_file(
name = "org_jetbrains_kotlinx-kotlinx-serialization-json-io-jvm-1_8_1_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-json-io-jvm/1.8.1/kotlinx-serialization-json-io-jvm-1.8.1.jar",
sha256 = "ab5ec8ea48815197c46a7df9f418a62791fe91cda5023b941ce4de872d810cc1",
downloaded_file_path = "kotlinx-serialization-json-io-jvm-1.8.1.jar"
name = "org_jetbrains_kotlinx-kotlinx-serialization-json-io-jvm-1_9_0_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-json-io-jvm/1.9.0/kotlinx-serialization-json-io-jvm-1.9.0.jar",
sha256 = "98abb132b03e11b80318d45350ec9ebbb5453d26505e81331bf2eea7143b6fbe",
downloaded_file_path = "kotlinx-serialization-json-io-jvm-1.9.0.jar"
)
http_file(
name = "org_jetbrains_kotlinx-kotlinx-serialization-json-io-jvm-1_8_1-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-json-io-jvm/1.8.1/kotlinx-serialization-json-io-jvm-1.8.1-sources.jar",
sha256 = "d2576ea868d4654376dbbb103c53144a3574dc4e5e9a37eee7844dc00342418e",
downloaded_file_path = "kotlinx-serialization-json-io-jvm-1.8.1-sources.jar"
name = "org_jetbrains_kotlinx-kotlinx-serialization-json-io-jvm-1_9_0-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-json-io-jvm/1.9.0/kotlinx-serialization-json-io-jvm-1.9.0-sources.jar",
sha256 = "60a176e88a074452f03e7f359f33bed4ca734e24e008c50677468fd253af9e98",
downloaded_file_path = "kotlinx-serialization-json-io-jvm-1.9.0-sources.jar"
)
http_file(
name = "org_jetbrains_kotlinx-kotlinx-serialization-protobuf-jvm-1_8_1_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-protobuf-jvm/1.8.1/kotlinx-serialization-protobuf-jvm-1.8.1.jar",
sha256 = "fbd174f72ea82364baa112069f2d7b361758f6b68bd81d2ac51b02c66a48e924",
downloaded_file_path = "kotlinx-serialization-protobuf-jvm-1.8.1.jar"
name = "org_jetbrains_kotlinx-kotlinx-serialization-protobuf-jvm-1_9_0_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-protobuf-jvm/1.9.0/kotlinx-serialization-protobuf-jvm-1.9.0.jar",
sha256 = "555035205ad1a21254b49fd6b5ddbe6ad1abb5c7baba244258bd758f80687bca",
downloaded_file_path = "kotlinx-serialization-protobuf-jvm-1.9.0.jar"
)
http_file(
name = "org_jetbrains_kotlinx-kotlinx-serialization-protobuf-jvm-1_8_1-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-protobuf-jvm/1.8.1/kotlinx-serialization-protobuf-jvm-1.8.1-sources.jar",
sha256 = "e4a4de5cff003c53d5d3c5b979de4b68207e4c3b861a92f490f0ea88e2cd5330",
downloaded_file_path = "kotlinx-serialization-protobuf-jvm-1.8.1-sources.jar"
name = "org_jetbrains_kotlinx-kotlinx-serialization-protobuf-jvm-1_9_0-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-protobuf-jvm/1.9.0/kotlinx-serialization-protobuf-jvm-1.9.0-sources.jar",
sha256 = "63d88633e89e5cc37d84ff21e57a73ad28710b3ff143fd13b16694dfbd0c73f9",
downloaded_file_path = "kotlinx-serialization-protobuf-jvm-1.9.0-sources.jar"
)
http_file(
@@ -8728,6 +8728,20 @@ http_file(
downloaded_file_path = "ktor-client-encoding-jvm-3.1.3-sources.jar"
)
http_file(
name = "org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.8.1/kotlinx-serialization-core-jvm-1.8.1.jar",
sha256 = "3565b6d4d789bf70683c45566944287fc1d8dc75c23d98bd87d01059cc76f2b3",
downloaded_file_path = "kotlinx-serialization-core-jvm-1.8.1.jar"
)
http_file(
name = "org_jetbrains_kotlinx-kotlinx-serialization-core-jvm-1_8_1-sources_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.8.1/kotlinx-serialization-core-jvm-1.8.1-sources.jar",
sha256 = "d4a7221a0c2ccb47bc736340344b256506cf8a5a49882883f8f56385ae69a1bc",
downloaded_file_path = "kotlinx-serialization-core-jvm-1.8.1-sources.jar"
)
http_file(
name = "com_miglayout-miglayout-swing-11_4_2_http",
url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/com/miglayout/miglayout-swing/11.4.2/miglayout-swing-11.4.2.jar",
@@ -12,7 +12,7 @@ import kotlin.io.path.pathString
import kotlin.io.path.readText
private const val DEFAULT_PLUGIN_VERIFIER_VERSION = "1.381"
private const val DEFAULT_PLUGIN_VERIFIER_VERSION = "1.396"
suspend fun createPluginVerifier(
pluginVerifierVersion: String = DEFAULT_PLUGIN_VERIFIER_VERSION,
@@ -14,6 +14,7 @@ import org.jetbrains.intellij.build.telemetry.TraceManager.spanBuilder
import org.jetbrains.intellij.build.telemetry.use
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.exists
internal const val BUILT_IN_HELP_MODULE_NAME = "intellij.builtInHelp"
@@ -88,25 +89,63 @@ private fun pluginXml(buildContext: BuildContext, version: String): String {
*/
private val helpIndexerMutex = Mutex()
/* Offline help plugins include a separate set of help topics for each of the supported languages.
This is a map of language code to descriptors that define resources associated with that language.
*/
private data class LanguageResourcesDescriptor(
val isRequired: Boolean = false,
val resPath: String,
val resList: List<String> = listOf("topics", "images", "search"),
)
private val supportedLanguages = mapOf(
//Localized resources don't include images
Pair("zh-cn", LanguageResourcesDescriptor(resPath = "zh-cn/", resList = listOf("topics", "search"))),
Pair("en-us", LanguageResourcesDescriptor(isRequired = true, resPath = ""))
)
private suspend fun buildResourcesForHelpPlugin(resourceRoot: Path, classPath: List<String>, assetJar: Path, context: CompilationContext) {
spanBuilder("index help topics").use {
helpIndexerMutex.withLock {
runJavaForIntellijModule(
context = context, mainClass = "com.jetbrains.builtInHelp.indexer.HelpIndexer",
args = listOf(
resourceRoot.resolve("search").toString(),
resourceRoot.resolve("topics").toString(),
),
jvmArgs = emptyList(),
classPath = classPath,
)
supportedLanguages.forEach { (lang, descriptor) ->
val topicPath = resourceRoot.resolve("${descriptor.resPath}topics")
if (topicPath.exists())
runJavaForIntellijModule(
context = context, mainClass = "com.jetbrains.builtInHelp.indexer.HelpIndexer",
args = listOf(
resourceRoot.resolve("${descriptor.resPath}search").toString(),
topicPath.toString(),
),
jvmArgs = emptyList(),
classPath = classPath,
)
else
Span.current().addEvent("skip \"${lang}\" for offline help plugin because it was not supplied. ")
}
}
writeNewZipWithoutIndex(file = assetJar, compress = true) { zipCreator ->
val archiver = ZipArchiver()
archiver.setRootDir(resourceRoot)
archiveDir(startDir = resourceRoot.resolve("topics"), addFile = { archiver.addFile(it, zipCreator) })
archiveDir(startDir = resourceRoot.resolve("images"), addFile = { archiver.addFile(it, zipCreator) })
archiveDir(startDir = resourceRoot.resolve("search"), addFile = { archiver.addFile(it, zipCreator) })
supportedLanguages.forEach { (lang, descriptor) ->
val langRootDir = resourceRoot.resolve(descriptor.resPath)
if (langRootDir.exists()) {
Span.current().addEvent("adding \"${lang}\" to the resulting ZIP.")
descriptor.resList.forEach { resDir ->
archiveDir(
startDir = langRootDir.resolve(resDir),
addFile = { archiver.addFile(it, zipCreator) })
}
}
else
Span.current().addEvent("skip adding \"${lang}\" to the resulting ZIP because it was not supplied.")
}
}
}
}
@@ -288,7 +288,7 @@ internal suspend fun createPlatformLayout(projectLibrariesUsedByPlugins: SortedS
for (item in productPluginContentModules) {
val iterator = filteredExplicit.iterator()
while (iterator.hasNext()) {
if (item.moduleName == iterator.next().moduleName) {
if (item.moduleName == iterator.next().moduleName && !PRODUCT_MODULE_IMPL_COMPOSITION.values.any { it.contains(item.moduleName) }) {
// todo - error instead of warn
Span.current().addEvent("product module MUST NOT BE explicitly specified: ${item.moduleName}")
iterator.remove()
@@ -710,10 +710,12 @@ private suspend fun collectAndEmbedProductModules(root: Element, xIncludePathRes
moduleSet = moduleSet,
)
)
PRODUCT_MODULE_IMPL_COMPOSITION.get(moduleName)?.let {
it.mapTo(result) { subModuleName ->
ModuleItem(moduleName = subModuleName, relativeOutputFile = relativeOutFile, reason = ModuleIncludeReasons.PRODUCT_MODULES, moduleSet = moduleSet)
}
PRODUCT_MODULE_IMPL_COMPOSITION.get(moduleName)?.let { list ->
list
.filter { !context.productProperties.productLayout.productImplementationModules.contains(it) }
.mapTo(result) { subModuleName ->
ModuleItem(moduleName = subModuleName, relativeOutputFile = relativeOutFile, reason = ModuleIncludeReasons.PRODUCT_MODULES, moduleSet = moduleSet)
}
}
check(moduleElement.content.isEmpty())
@@ -173,11 +173,13 @@ internal class BuildTreeView(private val project: Project, parentScope: Coroutin
is BuildNodesUpdate -> {
timeDiff = event.currentTimestamp - System.currentTimeMillis()
val nodeInfos = event.nodes
var needsNavigationContextUpdate = false
if (nodeInfos.isEmpty()) {
LOG.debug("Clearing nodes")
durationUpdater.reset()
nodeMap.clear()
rootNode.removeChildren()
needsNavigationContextUpdate = true
}
else {
nodeInfos.forEach { nodeInfo ->
@@ -195,16 +197,23 @@ internal class BuildTreeView(private val project: Project, parentScope: Coroutin
nodeMap[nodeInfo.id] = newNode
if (parentNode.addChild(newNode)) {
maybeExpand(TreePathUtil.toTreePath(parentNode))
needsNavigationContextUpdate = needsNavigationContextUpdate ||
parentNode.childCount == 1 || // adding first child might change parent's navigatable status
newNode.occurrenceNavigatable != null
}
}
else {
LOG.debug { "Updating node (id=${nodeInfo.id})" }
val wasNavigatable = node.isNavigatable
durationUpdater.onNodeUpdated(node.content, nodeInfo)
node.content = nodeInfo
needsNavigationContextUpdate = needsNavigationContextUpdate || node.childCount != 0 || node.isNavigatable != wasNavigatable
}
}
}
updateNavigationContext()
if (needsNavigationContextUpdate) {
updateNavigationContext()
}
}
is BuildTreeExposeRequest -> {
val nodeId = event.nodeId
@@ -348,11 +357,15 @@ internal class BuildTreeView(private val project: Project, parentScope: Coroutin
}
private var cachedVisibleChildren: MutableList<MyNode>? = null
private var cachedIndex = -1
private var cachedFilteringState: BuildTreeFilteringState? = null
val occurrenceNavigatable: NavigatableId?
get() = if (content.hasProblems && childCount == 0) content.navigatables.firstOrNull() else null
val isNavigatable: Boolean
get() = isVisible() && occurrenceNavigatable != null
fun addChild(node: MyNode): Boolean {
assert(node.parent == null)
node.parent = this
@@ -363,7 +376,9 @@ internal class BuildTreeView(private val project: Project, parentScope: Coroutin
clearCacheIfInvalid()
if (node.isVisible()) {
cachedVisibleChildren?.add(node)
treeModel.nodesWereInserted(this, intArrayOf(childCount - 1))
val newIndex = childCount - 1
node.cachedIndex = newIndex
treeModel.nodesWereInserted(this, intArrayOf(newIndex))
return true
}
return false
@@ -392,7 +407,7 @@ internal class BuildTreeView(private val project: Project, parentScope: Coroutin
}
}
else if (wasVisible) {
parent.cachedVisibleChildren?.removeAt(nodeIndex)
parent.clearCache()
treeModel.nodesWereRemoved(parent, intArrayOf(nodeIndex), arrayOf(this))
}
else {
@@ -414,6 +429,7 @@ internal class BuildTreeView(private val project: Project, parentScope: Coroutin
private fun clearCache() {
cachedVisibleChildren = null
cachedFilteringState = null
}
private fun clearCacheIfInvalid() {
@@ -431,17 +447,26 @@ internal class BuildTreeView(private val project: Project, parentScope: Coroutin
}
fun getVisibleChildren(): List<MyNode> {
val allChildren = children ?: return emptyList()
if (children == null) return emptyList()
val cached = cachedVisibleChildren
val currentFilteringState = filteringState
if (cached != null && cachedFilteringState == currentFilteringState) {
if (cached != null && cachedFilteringState == filteringState) {
return cached
}
cachedFilteringState = currentFilteringState
return rebuildCache()
}
private fun rebuildCache(): List<MyNode> {
cachedFilteringState = filteringState
val result = mutableListOf<MyNode>()
allChildren.forEach {
if ((it as MyNode).isVisible()) {
var index = 0
children.forEach {
val child = it as MyNode
if (child.isVisible()) {
result.add(it)
child.cachedIndex = index++
}
else {
child.cachedIndex = -1
}
}
cachedVisibleChildren = result
@@ -461,7 +486,11 @@ internal class BuildTreeView(private val project: Project, parentScope: Coroutin
}
override fun getIndex(node: TreeNode): Int {
return if ((node as? MyNode)?.isVisible() == true) getVisibleChildren().indexOf(node) else -1
if (node !is MyNode || children == null || node.parent !== this) return -1
if (cachedFilteringState != filteringState) {
rebuildCache()
}
return node.cachedIndex
}
override fun children(): Enumeration<TreeNode> {
@@ -17,7 +17,7 @@ val useBackgroundWriteAction: Boolean = System.getProperty("idea.background.writ
* See IJPL-199557
*/
@ApiStatus.Internal
val wrapHighLevelFunctionsInWriteIntent: Boolean = System.getProperty("idea.wrap.high.level.functions.in.write.intent", "true").toBoolean()
val wrapHighLevelFunctionsInWriteIntent: Boolean = System.getProperty("idea.wrap.high.level.functions.in.write.intent", "false").toBoolean()
/**
* - `false` means that [backgroundWriteAction] will block the thread during lock acquisition
@@ -1,4 +1,4 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.openapi.vfs;
import com.intellij.openapi.progress.ProgressManager;
@@ -72,9 +72,6 @@ public interface AsyncFileListener {
@Nullable
ChangeApplier prepareChange(@NotNull List<? extends @NotNull VFileEvent> events);
/**
* Consider using {@link ChangeApplierBackgroundable} to allow running your listener in background threads
*/
interface ChangeApplier {
/**
* This method is called in write action before the VFS events are delivered and applied, and allows
@@ -29,6 +29,7 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.ex.util.EditorScrollingPositionKeeper;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.DumbAwareAction;
@@ -40,6 +41,7 @@ import com.intellij.ui.DirtyUI;
import com.intellij.util.concurrency.annotations.RequiresEdt;
import com.intellij.util.concurrency.annotations.RequiresWriteLock;
import com.intellij.util.containers.ContainerUtil;
import kotlin.enums.EnumEntries;
import org.jetbrains.annotations.*;
import javax.swing.*;
@@ -67,6 +69,8 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
protected boolean aligningViewModeSupported;
private final EditorScrollingPositionKeeper @NotNull [] myPositionKeepers = new EditorScrollingPositionKeeper[Side.getEntries().size()];
public SimpleDiffViewer(@NotNull DiffContext context, @NotNull DiffRequest request) {
super(context, (ContentDiffRequest)request);
@@ -83,8 +87,15 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
myTextDiffProvider = DiffUtil.createTextDiffProvider(getProject(), getRequest(), getTextSettings(), this::rediff, this);
for (Side side : Side.values()) {
DiffUtil.installLineConvertor(getEditor(side), getContent(side), myFoldingModel, side.getIndex());
EnumEntries<@NotNull Side> sides = Side.getEntries();
for (int i = 0; i < sides.size(); i++) {
Side side = sides.get(i);
EditorEx editor = getEditor(side);
EditorScrollingPositionKeeper positionKeeper = new EditorScrollingPositionKeeper(editor);
Disposer.register(this, positionKeeper);
myPositionKeepers[i] = positionKeeper;
DiffUtil.installLineConvertor(editor, getContent(side), myFoldingModel, side.getIndex());
}
}
@@ -248,7 +259,8 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
List<SimpleDiffChange> nonSkipped = changes != null ? ContainerUtil.filter(changes, it -> !it.isSkipped()) : null;
FoldingModelSupport.Data foldingState = myFoldingModel.createState(nonSkipped, getFoldingModelSettings());
return () -> {
return () -> runPreservingScrollingPosition(() -> {
myFoldingModel.updateContext(myRequest, getFoldingModelSettings());
clearDiffPresentation();
@@ -271,7 +283,19 @@ public class SimpleDiffViewer extends TwosideTextDiffViewer {
myContentPanel.repaintDivider();
myStatusPanel.update();
};
});
}
private void runPreservingScrollingPosition(@NotNull Runnable action) {
for (EditorScrollingPositionKeeper keeper : myPositionKeepers) {
keeper.savePosition();
}
action.run();
for (EditorScrollingPositionKeeper keeper : myPositionKeepers) {
keeper.restorePosition(true);
}
}
protected @NotNull Runnable applyNotification(final @Nullable JComponent notification) {
@@ -64,9 +64,7 @@ class EelLocalExecPosixApi(
val (cache, interactive) = when (opts.mode) {
EelExecPosixApi.PosixEnvironmentVariablesOptions.Mode.MINIMAL -> {
return service<CoroutineScopeService>().coroutineScope.async {
EnvironmentUtil.getEnvironmentMap()
}
return CompletableDeferred(EnvironmentUtil.getSystemEnv())
}
EelExecPosixApi.PosixEnvironmentVariablesOptions.Mode.LOGIN_NON_INTERACTIVE -> {
@@ -200,9 +198,7 @@ class EelLocalExecWindowsApi : EelExecWindowsApi, LocalEelExecApi {
override val descriptor: EelDescriptor = LocalEelDescriptor
override fun environmentVariables(opts: EelExecApi.EnvironmentVariablesOptions): Deferred<Map<String, String>> =
service<CoroutineScopeService>().coroutineScope.async {
EnvironmentUtil.getEnvironmentMap()
}
CompletableDeferred(EnvironmentUtil.getEnvironmentMap())
override suspend fun findExeFilesInPath(binaryName: String): List<EelPath> =
findExeFilesInPath(binaryName, LOG)
@@ -51,9 +51,12 @@ internal class BackendRunDashboardManagerState(private val project: Project) {
sharedSettings.value = RunDashboardSettingsDto(openRunningConfigInTab)
}
fun fireStatusUpdated(backendService: RunDashboardService) {
sharedStatuses.tryEmit(ServiceStatusDto(backendService.uuid,
RunDashboardRunConfigurationStatus.getStatus(backendService.descriptor).id))
fun fireStatusUpdated(backendService: RunDashboardService, persistedStatus: RunDashboardRunConfigurationStatus?) {
val effectiveStatus = when {
backendService.descriptor == null && persistedStatus != null -> persistedStatus
else -> RunDashboardRunConfigurationStatus.getStatus(backendService.descriptor)
}
sharedStatuses.tryEmit(ServiceStatusDto(backendService.uuid, effectiveStatus.id))
}
fun getStatuses(): Flow<ServiceStatusDto> {
@@ -485,7 +485,7 @@ public final class RunDashboardManagerImpl implements RunDashboardManager, Persi
@Override
public void updateDashboard(boolean withStructure) {
for (RunDashboardService backendService : getRunConfigurations()) {
mySharedState.fireStatusUpdated(backendService);
mySharedState.fireStatusUpdated(backendService, getPersistedStatus(backendService.getConfigurationSettings().getConfiguration()));
var applicableCustomizers = getCustomizers(backendService.getConfigurationSettings(), backendService.getDescriptor());
if (applicableCustomizers.isEmpty()) continue;
@@ -623,3 +623,4 @@ shared.target.presentation.label=[shared]
notification.content.editor.autoreparse.delay.has.been.restored=Editor autoreparse delay has been restored to the default value {0}ms. 0 value is associated with potential UI freezes and not recommended.
action.open.settings.text=Open Settings
show.a.code.vision.inlay.inlay.when.no.results.are.available=Show a Code Vision inlay inlay when no results are available
@@ -1723,8 +1723,6 @@ c:com.intellij.codeInsight.codeVision.ui.model.TextCodeVisionEntry
- f:getText():java.lang.String
f:com.intellij.codeInsight.codeVision.ui.model.ZombieCodeVisionEntry
- com.intellij.codeInsight.codeVision.CodeVisionEntry
- <init>(java.lang.String,java.lang.String,java.lang.String,javax.swing.Icon,java.lang.Integer,java.lang.String):V
- b:<init>(java.lang.String,java.lang.String,java.lang.String,javax.swing.Icon,java.lang.Integer,java.lang.String,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:getCount():java.lang.Integer
- f:getText():java.lang.String
f:com.intellij.codeInsight.codeVision.ui.model.richText.RichString
+4
View File
@@ -114,6 +114,10 @@ com.intellij.codeInsight.codeVision.settings.CodeVisionGroupSettingProvider
- getGroupName():java.lang.String
com.intellij.codeInsight.codeVision.ui.model.CodeVisionPredefinedActionEntry
- a:onClick(com.intellij.openapi.editor.Editor):V
f:com.intellij.codeInsight.codeVision.ui.model.ZombieCodeVisionEntry
- <init>(java.lang.String,java.lang.String,java.lang.String,javax.swing.Icon,java.lang.Integer,java.lang.String,Z):V
- b:<init>(java.lang.String,java.lang.String,java.lang.String,javax.swing.Icon,java.lang.Integer,java.lang.String,Z,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- f:getShouldBeDelimited():Z
com.intellij.codeInsight.codeVision.ui.renderers.painters.ICodeVisionEntryBasePainter
- a:paint(com.intellij.openapi.editor.Editor,com.intellij.openapi.editor.markup.TextAttributes,java.awt.Graphics,java.lang.Object,java.awt.Point,com.intellij.codeInsight.codeVision.ui.model.RangeCodeVisionModel$InlayState,Z,com.intellij.codeInsight.codeVision.CodeVisionEntry):V
- a:size(com.intellij.openapi.editor.Editor,com.intellij.codeInsight.codeVision.ui.model.RangeCodeVisionModel$InlayState,java.lang.Object):java.awt.Dimension
@@ -22,6 +22,7 @@ import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.dsl.builder.*
import com.intellij.ui.dsl.listCellRenderer.textListCellRenderer
import com.intellij.util.PlatformUtils
import org.jetbrains.annotations.ApiStatus
import javax.swing.DefaultComboBoxModel
// @formatter:off
@@ -49,7 +50,8 @@ private val myRenderedDocCheckBox get() = CheckboxDescriptor
private val myUseEditorFontInInlays get() = CheckboxDescriptor(ApplicationBundle.message("use.editor.font.for.inlays"), model::isUseEditorFontInInlays, model::setUseEditorFontInInlays)
// @formatter:on
internal class EditorAppearanceConfigurable : BoundCompositeSearchableConfigurable<UnnamedConfigurable>(
@ApiStatus.Internal
class EditorAppearanceConfigurable : BoundCompositeSearchableConfigurable<UnnamedConfigurable>(
ApplicationBundle.message("tab.editor.settings.appearance"),
"reference.settingsdialog.IDE.editor.appearance",
"editor.preferences.appearance"
@@ -3,6 +3,7 @@ package com.intellij.application.options.editor;
import com.intellij.openapi.options.ConfigurableEP;
import com.intellij.openapi.options.UnnamedConfigurable;
import org.jetbrains.annotations.ApiStatus;
/**
* To provide additional options in Editor | Appearance section register implementation of
@@ -14,5 +15,6 @@ import com.intellij.openapi.options.UnnamedConfigurable;
* <p>
* A new instance of the specified class will be created each time then the Settings dialog is opened
*/
final class EditorAppearanceConfigurableEP extends ConfigurableEP<UnnamedConfigurable> {
@ApiStatus.Internal
public final class EditorAppearanceConfigurableEP extends ConfigurableEP<UnnamedConfigurable> {
}
@@ -0,0 +1,21 @@
// 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.codeInsight.codeVision.ui.model
import com.intellij.codeInsight.codeVision.CodeVisionEntry
import com.intellij.lang.LangBundle
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
class ZeroWidthCodeVisionEntry(
providerId: String
) : CodeVisionEntry(
providerId,
icon = null,
longPresentation = LangBundle.message("show.a.code.vision.inlay.inlay.when.no.results.are.available"),
tooltip = "",
extraActions = emptyList()
) {
init {
showInMorePopup = false
}
}
@@ -3,6 +3,7 @@ package com.intellij.codeInsight.codeVision.ui.model
import com.intellij.codeInsight.codeVision.CodeVisionEntry
import com.intellij.openapi.util.NlsContexts
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import javax.swing.Icon
@@ -13,6 +14,7 @@ class ZombieCodeVisionEntry(
icon: Icon?,
val count: Int?,
val text: String = longPresentation,
val shouldBeDelimited: Boolean = true
) : CodeVisionEntry(providerId, icon, longPresentation, tooltip, emptyList()) {
override fun toString() = "ZombieCodeVisionEntry('$text')"
@@ -86,7 +86,7 @@ open class CodeVisionListPainter(
painter.paint(editor, textAttributes, g, it, Point(x, y), state, it == hoveredEntry, hoveredEntry)
x += size.width
if (painter.shouldBeDelimited() && (index < value.visibleLens.size - 1 || hovered)) {
if (painter.shouldBeDelimited(it) && (index < value.visibleLens.size - 1 || hovered)) {
delimiterPainter.paint(editor, textAttributes, g, Point(x, y), state, false)
x += delimiterWidth
}
@@ -26,5 +26,5 @@ interface ICodeVisionEntryBasePainter<T> : ICodeVisionPainter {
): Dimension
@ApiStatus.Internal
fun shouldBeDelimited(): Boolean = true
fun shouldBeDelimited(entry: T): Boolean = true
}
@@ -4,15 +4,22 @@ package com.intellij.codeInsight.codeVision.ui.renderers.providers
import com.intellij.codeInsight.codeVision.CodeVisionEntry
import com.intellij.codeInsight.codeVision.ui.model.AdditionalCodeVisionEntry
import com.intellij.codeInsight.codeVision.ui.model.CounterCodeVisionEntry
import com.intellij.codeInsight.codeVision.ui.model.RangeCodeVisionModel
import com.intellij.codeInsight.codeVision.ui.model.RichTextCodeVisionEntry
import com.intellij.codeInsight.codeVision.ui.model.TextCodeVisionEntry
import com.intellij.codeInsight.codeVision.ui.model.ZeroWidthCodeVisionEntry
import com.intellij.codeInsight.codeVision.ui.model.ZombieCodeVisionEntry
import com.intellij.codeInsight.codeVision.ui.renderers.painters.CodeVisionRichTextPainter
import com.intellij.codeInsight.codeVision.ui.renderers.painters.CodeVisionVisionTextPainter
import com.intellij.codeInsight.codeVision.ui.renderers.painters.DefaultCodeVisionPainter
import com.intellij.codeInsight.codeVision.ui.renderers.painters.ICodeVisionEntryBasePainter
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.util.ClassExtension
import com.intellij.openapi.util.registry.Registry
import java.awt.Dimension
import java.awt.Graphics
import java.awt.Point
private val INSTANCE = CodeVisionPainterProviders()
@@ -50,7 +57,35 @@ private class ZombieCodeVisionEntryPainter : DefaultCodeVisionPainter<ZombieCode
val debug = Registry.`is`("cache.markup.debug")
if (debug) "${result}?" else result
})
)
) {
override fun shouldBeDelimited(entry: ZombieCodeVisionEntry): Boolean {
return entry.shouldBeDelimited
}
}
private class ZeroWidthCodeVisionEntryPainter : ICodeVisionEntryBasePainter<ZeroWidthCodeVisionEntry> {
override fun paint(
editor: Editor,
textAttributes: TextAttributes,
g: Graphics,
value: ZeroWidthCodeVisionEntry,
point: Point,
state: RangeCodeVisionModel.InlayState,
hovered: Boolean,
hoveredEntry: CodeVisionEntry?
) {
/* no-op */
}
override fun size(editor: Editor, state: RangeCodeVisionModel.InlayState, value: ZeroWidthCodeVisionEntry): Dimension {
return Dimension(0, 0)
}
override fun shouldBeDelimited(entry: ZeroWidthCodeVisionEntry): Boolean {
return false
}
}
internal fun CodeVisionEntry.painter(): ICodeVisionEntryBasePainter<CodeVisionEntry> {
return INSTANCE.getPainter(this@painter)
@@ -53,6 +53,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import java.awt.Container
import java.util.Collections
import java.util.concurrent.CancellationException
open class TrafficLightRenderer private constructor(
@@ -652,9 +653,12 @@ open class TrafficLightRenderer private constructor(
): TrafficLightRendererInfo {
val viewProvider = psiFile.getViewProvider()
val languages = viewProvider.getLanguages()
val settingMap = HashMap<Language, FileHighlightingSetting>(languages.size)
val settingMap = LinkedHashMap<Language, FileHighlightingSetting>(languages.size)
val settings = HighlightingSettingsPerFile.getInstance(project)
for (psiRoot in viewProvider.getAllFiles()) {
val roots = viewProvider.getAllFiles().toMutableList()
// leave the first item (the base language) in place, show all others in a stable order
roots.subList(1, roots.size).sortBy { file -> file.language.displayName }
for (psiRoot in roots) {
val setting = settings.getHighlightingSettingForRoot(psiRoot)
settingMap[psiRoot.getLanguage()] = setting
}
@@ -662,7 +666,7 @@ open class TrafficLightRenderer private constructor(
val virtualFile = psiFile.getVirtualFile()!!
val inLib = fileIndex.isInLibrary(virtualFile) && !fileIndex.isInContent(virtualFile)
val shouldHighlight = ProblemHighlightFilter.shouldHighlightFile(psiFile)
return TrafficLightRendererInfo(java.util.Map.copyOf(settingMap), inLib, shouldHighlight)
return TrafficLightRendererInfo(Collections.unmodifiableMap(settingMap), inLib, shouldHighlight)
}
private fun toPercent(progress: Double, finished: Boolean): Int {
@@ -4,6 +4,7 @@ package com.intellij.codeInsight.daemon.impl.grave
import com.intellij.codeInsight.codeVision.CodeVisionEntry
import com.intellij.codeInsight.codeVision.ui.model.CounterCodeVisionEntry
import com.intellij.codeInsight.codeVision.ui.model.ZombieCodeVisionEntry
import com.intellij.codeInsight.codeVision.ui.renderers.providers.painter
import com.intellij.codeInsight.daemon.impl.readGutterIcon
import com.intellij.codeInsight.daemon.impl.writeGutterIcon
import com.intellij.openapi.editor.impl.zombie.LimbedNecromancy
@@ -35,6 +36,7 @@ internal object CodeVisionNecromancy : LimbedNecromancy<CodeVisionZombie, CodeVi
writeUTF(grave, limb.providerId)
writeGutterIcon(grave, limb.icon)
writeCount(grave, limb)
if (limb.shouldBeDelimited) writeINT(grave, 1) else writeINT(grave, 0)
}
override fun exhumeLimb(grave: DataInput): CodeVisionLimb {
@@ -45,7 +47,8 @@ internal object CodeVisionNecromancy : LimbedNecromancy<CodeVisionZombie, CodeVi
val provId: String = readUTF(grave)
val icon: Icon? = readGutterIcon(grave)
val count: Int? = readCount(grave)
return CodeVisionLimb(startOffset, endOffset, provId, presentation, tooltip, icon, count)
val shouldBeDelimited = readINT(grave) == 1
return CodeVisionLimb(startOffset, endOffset, provId, presentation, tooltip, icon, count, shouldBeDelimited)
}
override fun formZombie(limbs: List<CodeVisionLimb>): CodeVisionZombie {
@@ -74,6 +77,7 @@ internal data class CodeVisionLimb(
val tooltip: String,
val icon: Icon?,
val count: Int?,
val shouldBeDelimited: Boolean
) {
constructor(pair: Pair<TextRange, CodeVisionEntry>) : this(
pair.first.startOffset,
@@ -83,9 +87,10 @@ internal data class CodeVisionLimb(
pair.second.tooltip,
pair.second.icon,
(pair.second as? CounterCodeVisionEntry)?.count,
pair.second.painter().shouldBeDelimited(pair.second)
)
fun asEntry(): ZombieCodeVisionEntry {
return ZombieCodeVisionEntry(providerId, longPresentation, tooltip, icon, count)
return ZombieCodeVisionEntry(providerId, longPresentation, tooltip, icon, count, shouldBeDelimited = shouldBeDelimited)
}
}
@@ -1,4 +1,4 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// 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.execution.filters;
import com.intellij.execution.impl.ConsoleViewImpl;
@@ -45,7 +45,7 @@ public class TextConsoleBuilderImpl extends TextConsoleBuilder {
protected @NotNull ConsoleView createConsole() {
var result = new ConsoleViewImpl(myProject, myScope, myViewer, myUsePredefinedMessageFilter);
result.setUseOwnModalityForUpdates$intellij_platform_lang_impl(useOwnModalityStateForUpdates);
result.setUseOwnModalityForUpdates(useOwnModalityStateForUpdates);
return result;
}
@@ -85,6 +85,7 @@ import com.intellij.util.containers.ContainerUtil
import com.intellij.util.text.CharArrayUtil
import com.intellij.util.ui.EDT
import com.intellij.util.ui.UIUtil
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.TestOnly
import org.jetbrains.annotations.VisibleForTesting
@@ -576,7 +577,8 @@ open class ConsoleViewImpl protected constructor(
}
}
internal var useOwnModalityForUpdates: Boolean = false
@ApiStatus.Internal
var useOwnModalityForUpdates: Boolean = false
protected open val stateForUpdate: ModalityState?
get() = if (useOwnModalityForUpdates) ModalityState.stateForComponent(this) else null
@@ -49,7 +49,7 @@ public class StringComboboxEditor extends EditorComboBoxEditor {
}
@Override
protected void onEditorCreate(final EditorEx editor) {
protected void onEditorCreate(final @NotNull EditorEx editor) {
Disposer.register(((EditorImpl)editor).getDisposable(), new Disposable() {
@Override
public void dispose() {
@@ -0,0 +1,120 @@
// 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.accessibility
import com.intellij.execution.process.OSProcessUtil
import com.intellij.internal.statistic.beans.MetricEvent
import com.intellij.internal.statistic.eventLog.EventLogGroup
import com.intellij.internal.statistic.eventLog.events.EventFields
import com.intellij.internal.statistic.service.fus.collectors.ApplicationUsagesCollector
import com.intellij.openapi.util.SystemInfoRt
import com.intellij.ui.mac.foundation.Foundation
import com.intellij.ui.mac.foundation.Foundation.NSAutoreleasePool
import com.intellij.ui.mac.foundation.ID
import java.awt.Toolkit
internal class AccessibilityToolsStateCollector : ApplicationUsagesCollector() {
private enum class ScreenReader {
NVDA, JAWS, VoiceOver, Orca
}
private enum class ScreenMagnifier {
WindowsMagnifier, MacOSZoom
}
private enum class VoiceControl {
WindowsVoiceAccess, MacOSVoiceControl
}
private val GROUP = EventLogGroup("accessibility.tools.state", 1, "FUS",
"Statistics about the usage of third-party accessibility tools and settings")
private val SCREEN_READER = GROUP.registerEvent(
"screen.reader",
EventFields.Enum<ScreenReader>("name", "Name of the screen reader tool"),
"Screen reader is running in the system")
private val SCREEN_MAGNIFIER = GROUP.registerEvent(
"screen.magnifier",
EventFields.Enum<ScreenMagnifier>("name", "Name of the screen magnifier tool"),
"Screen magnifier is running in the system")
private val VOICE_CONTROL = GROUP.registerEvent(
"voice.control",
EventFields.Enum<VoiceControl>("name", "Name of the voice control tool"),
"Voice control is running in the system")
private val OS_HIGH_CONTRAST = GROUP.registerEvent(
"os.high.contrast",
EventFields.Enabled,
"High Contrast mode is enabled in the OS settings")
override fun getGroup(): EventLogGroup = GROUP
override fun getMetrics(): Set<MetricEvent> {
val set = mutableSetOf<MetricEvent>()
when {
SystemInfoRt.isWindows -> {
val processList = OSProcessUtil.getProcessList()
for (process in processList) {
when (process.executableName.lowercase()) {
"nvda.exe" -> {
set.add(SCREEN_READER.metric(ScreenReader.NVDA))
}
"jfw.exe" -> {
set.add(SCREEN_READER.metric(ScreenReader.JAWS))
}
"magnify.exe" -> {
set.add(SCREEN_MAGNIFIER.metric(ScreenMagnifier.WindowsMagnifier))
}
"voiceaccess.exe" -> {
set.add(VOICE_CONTROL.metric(VoiceControl.WindowsVoiceAccess))
}
}
}
if (Toolkit.getDefaultToolkit().getDesktopProperty("win.highContrast.on") == true) {
set.add(OS_HIGH_CONTRAST.metric(true))
}
}
SystemInfoRt.isMac -> {
val pool = NSAutoreleasePool()
var universalAccess: ID? = null
var accessibility: ID? = null
try {
universalAccess = Foundation.invoke(
Foundation.invoke("NSUserDefaults", "alloc"),
"initWithSuiteName:",
Foundation.nsString("com.apple.universalaccess")
)
if (Foundation.invoke(universalAccess, "boolForKey:", Foundation.nsString("voiceOverOnOffKey")).booleanValue()) {
set.add(SCREEN_READER.metric(ScreenReader.VoiceOver))
}
if (Foundation.invoke(universalAccess, "boolForKey:", Foundation.nsString("closeViewZoomedIn")).booleanValue()) {
set.add(SCREEN_MAGNIFIER.metric(ScreenMagnifier.MacOSZoom))
}
if (Foundation.invoke(universalAccess, "boolForKey:", Foundation.nsString("increaseContrast")).booleanValue()) {
set.add(OS_HIGH_CONTRAST.metric(true))
}
accessibility = Foundation.invoke(
Foundation.invoke("NSUserDefaults", "alloc"),
"initWithSuiteName:",
Foundation.nsString("com.apple.Accessibility")
)
if (Foundation.invoke(accessibility, "boolForKey:", Foundation.nsString("CommandAndControlEnabled")).booleanValue()) {
set.add(VOICE_CONTROL.metric(VoiceControl.MacOSVoiceControl))
}
}
finally {
if (universalAccess != null) {
Foundation.cfRelease(universalAccess)
}
if (accessibility != null) {
Foundation.cfRelease(accessibility)
}
pool.drain()
}
}
}
return set
}
}
@@ -259,6 +259,12 @@ public final class PluginManagerConfigurable
}
}
public void scheduleApply() {
if (myPanel != null) {
myPanel.scheduleApply();
}
}
@Override
public void reset() {
if (myPanel != null) {
@@ -47,6 +47,8 @@ import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.openapi.util.text.HtmlChunk;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.impl.welcomeScreen.PluginsTabFactory;
import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenEventCollector;
import com.intellij.ui.GotItTooltip;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.ui.components.JBScrollPane;
@@ -1979,6 +1981,19 @@ public final class PluginManagerConfigurablePanel implements Disposable {
return myPluginModelFacade.getModel().isModified();
}
public void scheduleApply() {
ApplicationManager.getApplication().invokeLater(() -> {
try {
apply();
WelcomeScreenEventCollector.logPluginsModified();
InstalledPluginsState.getInstance().runShutdownCallback();
}
catch (ConfigurationException exception) {
Logger.getInstance(PluginsTabFactory.class).error(exception);
}
}, ModalityState.nonModal());
}
public void apply() throws ConfigurationException {
if (myPluginsAutoUpdateEnabled != null) {
UpdateOptions state = UpdateSettings.getInstance().getState();
@@ -745,14 +745,23 @@ class MarketplaceRequests(private val coroutineScope: CoroutineScope) : PluginIn
fun getCompatibleUpdateByModule(module: String): PluginId? {
try {
val data = objectMapper.writeValueAsString(CompatibleUpdateForModuleRequest(module))
val url = URI(MarketplaceUrls.getSearchPluginsUpdatesUrl())
@Suppress("DEPRECATION")
return HttpRequests.post(MarketplaceUrls.getSearchCompatibleUpdatesUrl(), HttpRequests.JSON_CONTENT_TYPE)
val query = buildString {
append("build=${ApplicationInfoImpl.orFromPluginCompatibleBuild(null)}")
append("&os=${buildEncodedOsParameter()}")
append("&arch=${buildEncodedArchParameter()}")
append("&module=${URLEncoder.encode(module, StandardCharsets.UTF_8)}")
}
val urlString = url.withQuery(query).toString()
return HttpRequests.request(urlString)
.accept(HttpRequests.JSON_CONTENT_TYPE)
.setHeadersViaTuner()
.productNameAsUserAgent()
.throwStatusCodeException(false)
.connect {
it.write(data)
objectMapper.readValue(it.inputStream, object : TypeReference<List<IdeCompatibleUpdate>>() {})
}.firstOrNull()
?.pluginId
@@ -966,23 +975,6 @@ private data class CompatibleUpdateRequest(
)
}
private data class CompatibleUpdateForModuleRequest(
val module: String,
val build: String,
val os: String = OS.CURRENT.name,
val arch: String = CpuArch.CURRENT.name,
) {
@JvmOverloads
constructor(
module: String,
buildNumber: BuildNumber? = null,
) : this(
module,
ApplicationInfoImpl.orFromPluginCompatibleBuild(buildNumber),
)
}
private fun Logger.infoOrDebug(
message: String,
throwable: Throwable,
@@ -56,9 +56,6 @@ object MarketplaceUrls {
"${getPluginManagerUrl()}/api/search/aggregation/$field"
).addParameters(mapOf("build" to IDE_BUILD_FOR_REQUEST))
@Deprecated("Use getSearchPluginsUpdatesUrl() instead")
fun getSearchCompatibleUpdatesUrl(): String = Urls.newFromEncoded("${getPluginManagerUrl()}/api/search/compatibleUpdates").toExternalForm()
fun getSearchPluginsUpdatesUrl(): String = Urls.newFromEncoded("${getPluginManagerUrl()}/api/search/updates/compatible").toExternalForm()
fun getSearchNearestUpdate(): String = Urls.newFromEncoded("${getPluginManagerUrl()}/api/search/updates/nearest").toExternalForm()
@@ -5,7 +5,6 @@ import com.intellij.ide.IdeBundle
import com.intellij.ide.ui.TargetUIType
import com.intellij.ide.ui.ThemeListProvider
import com.intellij.openapi.editor.colors.Groups
import com.intellij.openapi.util.registry.Registry
import com.intellij.ui.ExperimentalUI
private class ThemeListProviderImpl : ThemeListProvider {
@@ -41,13 +40,7 @@ private class ThemeListProviderImpl : ThemeListProvider {
else customThemes.add(info)
}
if (Registry.`is`("llm.riderNext.enabled", false)) {
uiThemeProviderListManager.getThemeListForTargetUI(TargetUIType.NEXT).forEach { info ->
if (!info.isThemeFromPlugin) nextUiThemes.add(info)
else customThemes.add(info)
}
}
else if (ExperimentalUI.isNewUI()) {
if (ExperimentalUI.isNewUI()) {
uiThemeProviderListManager.getThemeListForTargetUI(TargetUIType.ISLANDS).forEach { info ->
if (!info.isThemeFromPlugin) islandsUiThemes.add(info)
else customThemes.add(info)
@@ -21,7 +21,6 @@ import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.IconLoader.getDarkIcon
import com.intellij.openapi.util.IconLoader.getDisabledIcon
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.SystemInfoRt
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.wm.IdeFocusManager
@@ -42,6 +41,8 @@ import com.intellij.util.SingleAlarm
import com.intellij.util.concurrency.EdtScheduler
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.StartupUiUtil
import com.intellij.util.ui.moveToFitChildPopupX
import kotlinx.coroutines.Job
import java.awt.*
import java.awt.event.AWTEventListener
@@ -367,6 +368,26 @@ class ActionMenu constructor(
}
}
override fun getPopupMenuOrigin(): Point {
val result = super.getPopupMenuOrigin()
if (!StartupUiUtil.isWaylandToolkit() || parent !is JPopupMenu) return result
correctPopupMenuPositionForWayland(result)
return result
}
private fun correctPopupMenuPositionForWayland(result: Point) {
val popupMenu = popupMenu
var popupMenuSize = popupMenu.size
if (popupMenuSize.width <= 0) { // not shown yet
popupMenuSize = popupMenu.preferredSize
}
val popupMenuBounds = Rectangle(result, popupMenuSize)
moveToFitChildPopupX(popupMenuBounds, this)
result.x = popupMenuBounds.x
result.y = popupMenuBounds.y
}
fun clearItems() {
if (MacMenuSettings.isSystemMenu && isMainMenuPlace) {
for (menuComponent in getMenuComponents()) {
@@ -15,7 +15,6 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
import com.intellij.openapi.util.registry.Registry
import com.intellij.ui.ExperimentalUI
import java.lang.ref.WeakReference
@@ -39,8 +38,7 @@ internal class IslandsFeedback : ProjectActivity {
if (!ApplicationManager.getApplication().isUnitTestMode &&
!ApplicationManager.getApplication().isHeadlessEnvironment &&
!AppMode.isRemoteDevHost() &&
!Registry.`is`("llm.riderNext.enabled", false) && ExperimentalUI.isNewUI()) {
!AppMode.isRemoteDevHost() && ExperimentalUI.isNewUI()) {
handleFeedback(project)
}
@@ -23,7 +23,6 @@ import com.intellij.openapi.ui.OnePixelDivider
import com.intellij.openapi.ui.Splittable
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.SystemInfoRt
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.wm.*
import com.intellij.openapi.wm.ex.ToolWindowManagerEx
import com.intellij.openapi.wm.impl.*
@@ -66,7 +65,7 @@ import javax.swing.SwingUtilities
import javax.swing.border.Border
internal class IslandsUICustomization : InternalUICustomization() {
private val isIslandsAvailable = !Registry.`is`("llm.riderNext.enabled", false) && ExperimentalUI.isNewUI()
private val isIslandsAvailable = ExperimentalUI.isNewUI()
private var isManyIslandEnabledCache: Boolean? = null
@@ -1136,7 +1136,7 @@ fun CoroutineScope.runInitProjectActivities(project: Project) {
(project.serviceAsync<StartupManager>() as StartupManagerImpl).initProject()
}
launch(CoroutineName("projectOpened event executing") + Dispatchers.UiWithModelAccess) {
launch(CoroutineName("projectOpened event executing") + Dispatchers.EDT) {
@Suppress("DEPRECATION", "removal")
ApplicationManager.getApplication().messageBus.syncPublisher(ProjectManager.TOPIC).projectOpened(project)
}
@@ -1148,7 +1148,7 @@ fun CoroutineScope.runInitProjectActivities(project: Project) {
return
}
launch(CoroutineName("projectOpened component executing") + Dispatchers.UiWithModelAccess) {
launch(CoroutineName("projectOpened component executing") + Dispatchers.EDT) {
for (component in projectComponents) {
runCatching {
val componentActivity = StartUpMeasurer.startActivity(component.javaClass.name, ActivityCategory.PROJECT_OPEN_HANDLER)
@@ -414,9 +414,14 @@ class InfoAndProgressPanel internal constructor(
): BalloonHandler {
@Suppress("HardCodedStringLiteral")
val htmlContent = htmlBody.replace("\n", "<br>")
val icon = icon ?: if (type == MessageType.INFO) {
null
} else {
type.defaultIcon
}
val balloon = JBPopupFactory.getInstance()
.createHtmlTextBalloonBuilder(htmlContent,
null,
icon,
type.titleForeground,
type.popupBackground,
listener)
@@ -1252,6 +1257,7 @@ private class CounterLabel : JPanel() {
label = createLabel()
layout = BorderLayout()
add(label, BorderLayout.CENTER)
isOpaque = false
setNumber(0, isProgressVisible =false, isPopupShowing = false)
}
@@ -86,16 +86,7 @@ public final class PluginsTabFactory implements WelcomeTabFactory {
if (!configurable.isModified()) {
return;
}
ApplicationManager.getApplication().invokeLater(() -> {
try {
configurable.apply();
WelcomeScreenEventCollector.logPluginsModified();
InstalledPluginsState.getInstance().runShutdownCallback();
}
catch (ConfigurationException exception) {
Logger.getInstance(PluginsTabFactory.class).error(exception);
}
}, ModalityState.nonModal());
configurable.scheduleApply();
}
});
@@ -34,7 +34,7 @@ public class EditorComboBoxEditor implements ComboBoxEditor{
myTextField.setName(NAME);
}
protected void onEditorCreate(EditorEx editor) {}
protected void onEditorCreate(@NotNull EditorEx editor) {}
@Override
public void selectAll() {
@@ -7,28 +7,64 @@ import com.jetbrains.JBR;
import org.intellij.lang.annotations.JdkConstants;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.MouseInputListener;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import static java.awt.Cursor.*;
import static com.intellij.ui.WindowMouseListenerSupportKt.createWindowMouseListenerSupport;
@ApiStatus.Internal
public abstract class WindowMouseListener extends MouseAdapter implements MouseInputListener {
private static final boolean moveAfterMouseRelease = StartupUiUtil.isWaylandToolkit();
protected final Component myContent;
@JdkConstants.CursorType int myCursorType;
private Point myLocation;
private Rectangle viewBounds;
private int mouseButton;
private boolean wasDragged;
private boolean leftMouseButtonOnly = false;
protected final WindowMouseListenerSupport support = createWindowMouseListenerSupport(new SourceAdapter());
private class SourceAdapter implements WindowMouseListenerSource {
@Override
public @NotNull Component getContent(@NotNull MouseEvent event) {
return WindowMouseListener.this.getContent(event);
}
@Override
public @Nullable Component getView(@NotNull Component event) {
return WindowMouseListener.this.getView(event);
}
@Override
public boolean isDisabled(@NotNull Component view) {
return WindowMouseListener.this.isDisabled(view);
}
@Override
public int getCursorType(@Nullable Component view, @Nullable Point location) {
return WindowMouseListener.this.getCursorType(view, location);
}
@Override
public void setCursor(@NotNull Component content, @Nullable Cursor cursor) {
WindowMouseListener.this.setCursor(content, cursor);
}
@Override
public void updateBounds(@NotNull Rectangle bounds, @NotNull Component view, int dx, int dy) {
WindowMouseListener.this.updateBounds(bounds, view, dx, dy);
}
@Override
public void notifyMoved() {
WindowMouseListener.this.notifyMoved();
}
@Override
public void notifyResized() {
WindowMouseListener.this.notifyResized();
}
}
/**
* @param content the window content to find a window, or {@code null} to use a component from a mouse event eeeqweq3r
* @param content the window content to find a window, or {@code null} to use a component from a mouse event
*/
WindowMouseListener(Component content) {
myContent = content;
@@ -42,6 +78,11 @@ public abstract class WindowMouseListener extends MouseAdapter implements MouseI
@JdkConstants.CursorType
abstract int getCursorType(Component view, Point location);
@JdkConstants.CursorType
int getCursorType() {
return support.getCursorType();
}
/**
* @param bounds the component bounds, which should be updated
* @param view the component to move/resize
@@ -51,32 +92,32 @@ public abstract class WindowMouseListener extends MouseAdapter implements MouseI
abstract void updateBounds(Rectangle bounds, Component view, int dx, int dy);
public void setLeftMouseButtonOnly(boolean leftMouseButtonOnly) {
this.leftMouseButtonOnly = leftMouseButtonOnly;
support.setLeftMouseButtonOnly(leftMouseButtonOnly);
}
@Override
public void mouseMoved(MouseEvent event) {
update(event, false);
support.update(event, false);
}
@Override
public void mousePressed(MouseEvent event) {
update(event, true);
support.update(event, true);
}
@Override
public void mouseDragged(MouseEvent event) {
process(event, true);
support.process(event, true);
}
@Override
public void mouseReleased(MouseEvent event) {
process(event, false);
support.process(event, false);
}
@Override
public void mouseClicked(MouseEvent event) {
process(event, false);
support.process(event, false);
}
/**
@@ -92,88 +133,6 @@ public abstract class WindowMouseListener extends MouseAdapter implements MouseI
return false;
}
/**
* Updates a cursor and starts moving/resizing if the {@code start} is specified.
*/
private void update(MouseEvent event, boolean start) {
if (event.isConsumed() || (start && leftMouseButtonOnly && !SwingUtilities.isLeftMouseButton(event))) {
return;
}
if (start) wasDragged = false; // reset dragged state when mouse pressed
if (myLocation == null) {
Component content = getContent(event);
Component view = getView(content);
if (view != null) {
myCursorType = isDisabled(view) ? CUSTOM_CURSOR : getCursorType(view, event.getLocationOnScreen());
setCursor(content, getPredefinedCursor(myCursorType == CUSTOM_CURSOR ? DEFAULT_CURSOR : myCursorType));
if (start && myCursorType != CUSTOM_CURSOR) {
mouseButton = event.getButton();
myLocation = event.getLocationOnScreen();
viewBounds = view.getBounds();
event.consume();
}
}
}
}
/**
* Processes moving/resizing and stops it if not {@code mouseMove}.
*/
private void process(MouseEvent event, boolean mouseMove) {
if (event.isConsumed()) return;
if (mouseMove) wasDragged = true; // set dragged state when mouse dragged
if (myLocation != null && viewBounds != null) {
Component content = getContent(event);
Component view = getView(content);
if (mouseMove && myCursorType == DEFAULT_CURSOR && jbrMoveSupported(view)) {
// Enter in move mode only after mouse move, so double click is supported
JBR.getWindowMove().startMovingTogetherWithMouse((Window)view, mouseButton);
myLocation = null;
viewBounds = null;
return;
}
if (view != null) {
Rectangle bounds = new Rectangle(viewBounds);
int dx = event.getXOnScreen() - myLocation.x;
int dy = event.getYOnScreen() - myLocation.y;
if (myCursorType == DEFAULT_CURSOR && view instanceof Frame) {
int state = ((Frame)view).getExtendedState();
if (isStateSet(Frame.MAXIMIZED_HORIZ, state)) dx = 0;
if (isStateSet(Frame.MAXIMIZED_VERT, state)) dy = 0;
}
updateBounds(bounds, view, dx, dy);
Rectangle viewBounds = view.getBounds();
if (!bounds.equals(viewBounds)) {
boolean moved = bounds.x != viewBounds.x || bounds.y != viewBounds.y;
boolean resized = bounds.width != viewBounds.width || bounds.height != viewBounds.height;
boolean reallyMoveWindow = !moveAfterMouseRelease || !mouseMove;
if ((moved && reallyMoveWindow) || resized) {
view.reshape(bounds.x, bounds.y, bounds.width, bounds.height);
view.invalidate();
view.validate();
view.repaint();
if (moved) notifyMoved();
if (resized) notifyResized();
}
}
}
if (!mouseMove) {
setCursor(content, getPredefinedCursor(DEFAULT_CURSOR));
myLocation = null;
if (wasDragged) {
viewBounds = null; // no mouse clicked when mouse released after mouse dragged
}
}
event.consume();
}
else if (!mouseMove && viewBounds != null) {
viewBounds = null; // consume mouse clicked for consumed mouse released if no mouse dragged
event.consume();
}
}
/**
* Returns a window content which is used to find corresponding window and to set a cursor.
* By default, it returns a component from the specified mouse event if the content is not specified.
@@ -206,7 +165,7 @@ public abstract class WindowMouseListener extends MouseAdapter implements MouseI
* Returns {@code true} if a window is now moving/resizing.
*/
public boolean isBusy() {
return myLocation != null;
return support.isBusy();
}
protected void notifyMoved() { }
@@ -0,0 +1,301 @@
// 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.ui
import com.intellij.util.ui.StartupUiUtil
import com.jetbrains.JBR
import org.intellij.lang.annotations.JdkConstants
import org.jetbrains.annotations.ApiStatus
import java.awt.*
import java.awt.event.MouseEvent
import javax.swing.SwingUtilities
@ApiStatus.Internal
internal interface WindowMouseListenerSource {
fun getContent(event: MouseEvent): Component
fun getView(event: Component): Component?
fun isDisabled(view: Component): Boolean
@JdkConstants.CursorType
fun getCursorType(view: Component?, location: Point?): Int
fun setCursor(content: Component, cursor: Cursor?)
fun updateBounds(bounds: Rectangle, view: Component, dx: Int, dy: Int)
fun notifyMoved()
fun notifyResized()
}
internal fun createWindowMouseListenerSupport(source: WindowMouseListenerSource): WindowMouseListenerSupport {
return if (StartupUiUtil.isWaylandToolkit()) {
WaylandWindowMouseListenerSupport(source)
}
else {
RegularWindowMouseListenerSupport(source)
}
}
internal sealed class WindowMouseListenerSupport(private val source: WindowMouseListenerSource) {
@get:JdkConstants.CursorType
var cursorType: Int = 0
private set
private var currentSession: WindowListenerSession? = null
var leftMouseButtonOnly = false
val isBusy: Boolean
get() = currentSession != null
protected abstract fun moveAfterMouseRelease(): Boolean
protected abstract fun jbrMoveSupported(component: Component?): Boolean
/**
* Updates a cursor and starts moving/resizing if the `start` is specified.
*/
fun update(event: MouseEvent, start: Boolean) {
if (event.isConsumed || (start && leftMouseButtonOnly && !SwingUtilities.isLeftMouseButton(event))) {
return
}
if (!isBusy) {
val content = source.getContent(event)
val view = source.getView(content)
if (view != null) {
beforeUpdate(event, view)
updateCursor(event, content, view)
if (start && cursorType != Cursor.CUSTOM_CURSOR) {
start(event, view)
event.consume()
}
}
}
}
protected open fun beforeUpdate(event: MouseEvent, view: Component) { }
private fun updateCursor(event: MouseEvent, content: Component, view: Component) {
cursorType = if (source.isDisabled(view)) Cursor.CUSTOM_CURSOR else source.getCursorType(view, event.locationOnScreen)
source.setCursor(content, Cursor.getPredefinedCursor(if (cursorType == Cursor.CUSTOM_CURSOR) Cursor.DEFAULT_CURSOR else cursorType))
}
private fun start(event: MouseEvent, view: Component) {
currentSession = WindowListenerSession(
event.locationOnScreen,
view.bounds,
event.getButton(),
)
onStarted(event, view)
}
protected open fun onStarted(event: MouseEvent, view: Component) { }
/**
* Processes moving/resizing and stops it if not `mouseMove`.
*/
fun process(event: MouseEvent, mouseMove: Boolean) {
// Some points:
// 1. Stop events should be handled even if they're already consumed, so the session finishes correctly.
// 2. Non-drag unconsumed events should be handled as drag events anyway to support the "move on mouse release" mode.
if (!event.isConsumed) {
handleDragEvent(event, mouseMove)
}
if (!mouseMove) {
handleStopEvent(event)
}
}
@JdkConstants.CursorType
open fun getResizeCursor(top: Int, left: Int, bottom: Int, right: Int, resizeArea: Insets): Int {
// Wayland doesn't allow to change window's location programmatically,
// so resizing from top/left shall be forbidden for now.
if (top < resizeArea.top) {
if (left < resizeArea.left * 2) return Cursor.NW_RESIZE_CURSOR
if (right < resizeArea.right * 2) return Cursor.NE_RESIZE_CURSOR
return Cursor.N_RESIZE_CURSOR
}
if (bottom < resizeArea.bottom) {
if (left < resizeArea.left * 2) return Cursor.SW_RESIZE_CURSOR
if (right < resizeArea.right * 2) return Cursor.SE_RESIZE_CURSOR
return Cursor.S_RESIZE_CURSOR
}
if (left < resizeArea.left) {
if (top < resizeArea.top * 2) return Cursor.NW_RESIZE_CURSOR
if (bottom < resizeArea.bottom * 2) return Cursor.SW_RESIZE_CURSOR
return Cursor.W_RESIZE_CURSOR
}
if (right < resizeArea.right) {
if (top < resizeArea.top * 2) return Cursor.NE_RESIZE_CURSOR
if (bottom < resizeArea.bottom * 2) return Cursor.SE_RESIZE_CURSOR
return Cursor.E_RESIZE_CURSOR
}
return Cursor.CUSTOM_CURSOR
}
private fun handleDragEvent(event: MouseEvent, mouseMove: Boolean) {
val session = currentSession ?: return
if (mouseMove) {
onDraggingStarted(session)
}
val content = source.getContent(event)
val view = source.getView(content)
// Enter in move mode only after mouse move, so double click is supported
if (mouseMove && cursorType == Cursor.DEFAULT_CURSOR && jbrMoveSupported(view)) {
enterJbrMoveMode(session, view)
return
}
if (view != null) {
val bounds = Rectangle(session.viewBounds)
val delta = computeOffsetFromInitialLocation(session, event)
if (cursorType == Cursor.DEFAULT_CURSOR && view is Frame) {
val state = view.extendedState
if (WindowMouseListener.isStateSet(Frame.MAXIMIZED_HORIZ, state)) delta.x = 0
if (WindowMouseListener.isStateSet(Frame.MAXIMIZED_VERT, state)) delta.y = 0
}
source.updateBounds(bounds, view, delta.x, delta.y)
val currentViewBounds = view.bounds
if (bounds != currentViewBounds) {
val moved = bounds.x != currentViewBounds.x || bounds.y != currentViewBounds.y
val resized = bounds.width != currentViewBounds.width || bounds.height != currentViewBounds.height
val reallyMoveWindow = !moveAfterMouseRelease() || !mouseMove
if ((moved && reallyMoveWindow) || resized) {
setViewBounds(view, bounds, moved, resized)
}
}
}
event.consume()
}
protected abstract fun onDraggingStarted(session: WindowListenerSession)
protected open fun computeOffsetFromInitialLocation(session: WindowListenerSession, event: MouseEvent): Point {
val location = session.location
val dx: Int = event.xOnScreen - location.x
val dy: Int = event.yOnScreen - location.y
return Point(dx, dy)
}
private fun setViewBounds(view: Component, bounds: Rectangle, moved: Boolean, resized: Boolean) {
view.reshape(bounds.x, bounds.y, bounds.width, bounds.height)
view.invalidate()
view.validate()
view.repaint()
if (moved) source.notifyMoved()
if (resized) source.notifyResized()
}
private fun handleStopEvent(event: MouseEvent) {
val session = currentSession ?: return
resetCursor(source.getContent(event))
if (session.expectMouseReleased && event.id == MouseEvent.MOUSE_RELEASED) {
session.expectMouseReleased = false
event.consume()
}
if (session.expectMouseClicked && event.id == MouseEvent.MOUSE_CLICKED) {
session.expectMouseClicked = false
event.consume()
}
if (!session.expectMouseReleased && !session.expectMouseClicked) {
stop()
}
}
private fun resetCursor(content: Component) {
source.setCursor(content, Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR))
}
private fun stop() {
currentSession = null
}
private fun enterJbrMoveMode(session: WindowListenerSession, view: Component?) {
JBR.getWindowMove().startMovingTogetherWithMouse(view as Window, session.mouseButton)
stop()
}
protected data class WindowListenerSession(
val location: Point,
val viewBounds: Rectangle,
val mouseButton: Int,
var expectMouseReleased: Boolean = true,
var expectMouseClicked: Boolean = true,
)
}
private class RegularWindowMouseListenerSupport(source: WindowMouseListenerSource) : WindowMouseListenerSupport(source) {
override fun moveAfterMouseRelease(): Boolean = false
override fun onDraggingStarted(session: WindowListenerSession) {
session.expectMouseClicked = false // after dragging starts, there will be only one "released" event
}
override fun jbrMoveSupported(component: Component?): Boolean {
return (component is Frame || component is Dialog) && JBR.isWindowMoveSupported()
}
}
private class WaylandWindowMouseListenerSupport(source: WindowMouseListenerSource) : WindowMouseListenerSupport(source) {
private var isTruePopup = false
private var dx = 0
private var dy = 0
override fun beforeUpdate(event: MouseEvent, view: Component) {
isTruePopup = view is Window && view.type == Window.Type.POPUP
}
override fun onStarted(event: MouseEvent, view: Component) {
dx = 0
dy = 0
if (isRelativeMovementMode()) {
@Suppress("UsePropertyAccessSyntax")
JBR.getRelativePointerMovement().getAccumulatedMouseDeltaAndReset()
}
}
override fun onDraggingStarted(session: WindowListenerSession) { } // on Wayland, whether dragging has started or not, both "released" and "clicked" events will arrive
override fun getResizeCursor(top: Int, left: Int, bottom: Int, right: Int, resizeArea: Insets): Int {
if (isRelativeMovementMode()) return super.getResizeCursor(top, left, bottom, right, resizeArea)
// Wayland doesn't allow to change window's location programmatically,
// so resizing from top/left shall be forbidden for now.
if (bottom < resizeArea.bottom) {
if (right < resizeArea.right * 2) return Cursor.SE_RESIZE_CURSOR
return Cursor.S_RESIZE_CURSOR
}
if (right < resizeArea.right) {
if (bottom < resizeArea.bottom * 2) return Cursor.SE_RESIZE_CURSOR
return Cursor.E_RESIZE_CURSOR
}
return Cursor.CUSTOM_CURSOR
}
override fun computeOffsetFromInitialLocation(session: WindowListenerSession, event: MouseEvent): Point {
if (isRelativeMovementMode()) {
@Suppress("UsePropertyAccessSyntax")
val delta = JBR.getRelativePointerMovement().getAccumulatedMouseDeltaAndReset()
dx += delta.x
dy += delta.y
return Point(dx, dy)
}
else {
return super.computeOffsetFromInitialLocation(session, event)
}
}
private fun isRelativeMovementMode(): Boolean = isTruePopup && JBR.isRelativePointerMovementSupported()
override fun moveAfterMouseRelease(): Boolean = !isRelativeMovementMode()
override fun jbrMoveSupported(component: Component?): Boolean {
return component is Window && component.type != Window.Type.POPUP
}
}
@@ -87,8 +87,8 @@ public class WindowResizeListener extends WindowMouseListener {
if (myCorner != null && right < myCorner.getIconWidth() && bottom < myCorner.getIconHeight()) {
return DEFAULT_CURSOR;
}
Insets expected = getResizeBorder(view);
if (expected != null) {
Insets resizeArea = getResizeBorder(view);
if (resizeArea != null) {
if (view instanceof Frame) {
int state = ((Frame)view).getExtendedState();
if (isStateSet(Frame.MAXIMIZED_HORIZ, state)) {
@@ -101,28 +101,7 @@ public class WindowResizeListener extends WindowMouseListener {
}
}
// Wayland doesn't allow to change window's location programmatically,
// so resizing from top/left shall be forbidden for now.
if (!isWayland && top < expected.top) {
if (left < expected.left * 2) return NW_RESIZE_CURSOR;
if (right < expected.right * 2) return NE_RESIZE_CURSOR;
return N_RESIZE_CURSOR;
}
if (bottom < expected.bottom) {
if (!isWayland && left < expected.left * 2) return SW_RESIZE_CURSOR;
if (right < expected.right * 2) return SE_RESIZE_CURSOR;
return S_RESIZE_CURSOR;
}
if (!isWayland && left < expected.left) {
if (top < expected.top * 2 ) return NW_RESIZE_CURSOR;
if (bottom < expected.bottom * 2) return SW_RESIZE_CURSOR;
return W_RESIZE_CURSOR;
}
if (right < expected.right) {
if (!isWayland && top < expected.top * 2) return NE_RESIZE_CURSOR;
if (bottom < expected.bottom * 2) return SE_RESIZE_CURSOR;
return E_RESIZE_CURSOR;
}
return support.getResizeCursor(top, left, bottom, right, resizeArea);
}
return CUSTOM_CURSOR;
}
@@ -130,6 +109,7 @@ public class WindowResizeListener extends WindowMouseListener {
@Override
void updateBounds(Rectangle bounds, Component view, int dx, int dy) {
Dimension minimum = view.getMinimumSize();
var myCursorType = getCursorType();
if (myCursorType == NE_RESIZE_CURSOR || myCursorType == E_RESIZE_CURSOR || myCursorType == SE_RESIZE_CURSOR || myCursorType == DEFAULT_CURSOR) {
bounds.width += fixMinSize(dx, bounds.width, minimum.width);
}
@@ -1401,7 +1401,10 @@ public class AbstractPopup implements JBPopup, ScreenAreaConsumer, AlignedPopup
WindowRoundedCornersManager.setRoundedCorners(window, roundedCornerParams);
}
if (myNormalWindowLevel && !window.isDisplayable()) {
if (shouldUseTrueWaylandPopups()) {
window.setType(Window.Type.POPUP);
}
else if (myNormalWindowLevel && !window.isDisplayable()) {
window.setType(Window.Type.NORMAL);
}
myWindow = window;
@@ -1526,19 +1529,33 @@ public class AbstractPopup implements JBPopup, ScreenAreaConsumer, AlignedPopup
}
private static Point getLocationRelativeToParent(Rectangle bounds, Window popupParent) {
Rectangle newBounds = new Rectangle(bounds);
// The Wayland server may refuse to show a popup whose top-left corner is located outside the parent toplevel's bounds.
var toplevelParent = ComponentUtil.findUltimateParent(popupParent);
var toplevelParentLocation = toplevelParent.getLocationOnScreen();
if (LOG.isDebugEnabled()) {
LOG.debug(
"The top-level parent is located at " + toplevelParentLocation +
" and has the size " + toplevelParent.getSize()
);
}
Rectangle okBounds = new Rectangle(toplevelParentLocation.x, toplevelParentLocation.y,
toplevelParent.getWidth() + newBounds.width, toplevelParent.getHeight() + newBounds.height);
ScreenUtil.moveToFit(newBounds, okBounds, new Insets(0, 0, 1, 1));
if (LOG.isDebugEnabled()) {
LOG.debug("The bounds after fitting into the top-level parent: " + newBounds);
}
// The "bounds" are "screen" coordinates, which in Wayland means that they
// are relative to the nearest toplevel (Window) in the hierarchy.
// But popups in Wayland are expected to be located relative to popup's "parent" (a toplevel or another popup).
// We need to adjust "bounds" to be relative to the parent.
Rectangle newBounds = new Rectangle(bounds);
Point parentLocation = popupParent.getLocationOnScreen();
newBounds.x -= parentLocation.x;
newBounds.y -= parentLocation.y;
// The Wayland server may refuse to show a popup whose top-left corner is located outside the parent toplevel's bounds.
// TODO: Need to fit into the nearest toplevel, not popupParent that may be another popup.
Rectangle okBounds = new Rectangle(0, 0,
popupParent.getWidth() + newBounds.width, popupParent.getHeight() + newBounds.height);
ScreenUtil.moveToFit(newBounds, okBounds, new Insets(0, 0, 1, 1));
if (LOG.isDebugEnabled()) {
LOG.debug("The direct popup parent is located at " + parentLocation);
LOG.debug("The bounds after converting to the parent coordinate system: " + newBounds);
}
return newBounds.getLocation();
}
@@ -2833,6 +2850,7 @@ public class AbstractPopup implements JBPopup, ScreenAreaConsumer, AlignedPopup
}
private @Nullable Point getStoredLocation() {
if (shouldUseTrueWaylandPopups()) return null; // not supported at the moment
if (myDimensionServiceKey == null) return null;
return getWindowStateService(getProjectDependingOnKey(myDimensionServiceKey)).getLocation(myDimensionServiceKey);
}
@@ -2909,4 +2927,8 @@ public class AbstractPopup implements JBPopup, ScreenAreaConsumer, AlignedPopup
}
return event.isConsumed();
}
private static boolean shouldUseTrueWaylandPopups() {
return StartupUiUtil.isWaylandToolkit() && Registry.is("wayland.true.popups", false);
}
}
@@ -546,6 +546,13 @@ public class PopupFactoryImpl extends JBPopupFactory {
if (rootPane != null) {
return rootPane;
}
// https://youtrack.jetbrains.com/issue/DPA-3312 Unexpected component for dataContext: org.jetbrains.skiko.SkiaLayer$1
// The focus owner may be a non-JComponent, but it resides inside a wrapping JComponent parent
Container parent = component == null ? null : component.getParent();
if (parent instanceof JComponent c) {
return c;
}
else {
String componentClass = component == null ? null : component.getClass().getName();
throw new AssertionError("Unexpected component for " + errorInfoSupplier.get() + ": " + componentClass);
@@ -33,7 +33,9 @@ import com.intellij.util.ObjectUtils;
import com.intellij.util.SlowOperations;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.JBInsets;
import com.intellij.util.ui.StartupUiUtil;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.WaylandUtilKt;
import com.intellij.util.ui.accessibility.ScreenReader;
import kotlin.Unit;
import org.jetbrains.annotations.ApiStatus;
@@ -628,7 +630,15 @@ public class ListPopupImpl extends WizardPopup implements ListPopup, NextStepHan
if (!UiInterceptors.tryIntercept(myChild)) {
// Intercept child popup in tests because it is impossible to calculate location on screen there
myChild.show(container, container.getLocationOnScreen().x + container.getWidth() - STEP_X_PADDING, y, true);
var childLocation = new Point(container.getLocationOnScreen().x + container.getWidth() - STEP_X_PADDING, y);
if (StartupUiUtil.isWaylandToolkit()) {
SwingUtilities.convertPointFromScreen(childLocation, container);
var childBounds = new Rectangle(childLocation, myChild.getPreferredContentSize());
WaylandUtilKt.moveToFitChildPopupX(childBounds, container);
childLocation = childBounds.getLocation();
SwingUtilities.convertPointToScreen(childLocation, container);
}
myChild.show(container, childLocation.x, childLocation.y, true);
}
setIndexForShowingChild(myList.getSelectedIndex());
myMouseMovementTracker.reset();
@@ -793,6 +793,8 @@
implementationClass="com.intellij.codeInsight.codeVision.ui.renderers.providers.RichTextCodeVisionEntryPainter"/>
<codeVisionPainterProvider forClass="com.intellij.codeInsight.codeVision.ui.model.ZombieCodeVisionEntry"
implementationClass="com.intellij.codeInsight.codeVision.ui.renderers.providers.ZombieCodeVisionEntryPainter"/>
<codeVisionPainterProvider forClass="com.intellij.codeInsight.codeVision.ui.model.ZeroWidthCodeVisionEntry"
implementationClass="com.intellij.codeInsight.codeVision.ui.renderers.providers.ZeroWidthCodeVisionEntryPainter"/>
<applicationService serviceInterface="com.intellij.codeInsight.codeVision.ui.renderers.CodeVisionListPainterFactory"
serviceImplementation="com.intellij.codeInsight.codeVision.ui.renderers.DefaultCodeVisionListPainterFactory"/>
@@ -1027,6 +1027,7 @@
<statistics.counterUsagesCollector implementationClass="com.intellij.codeInsight.navigation.actions.GTDUCollector"/>
<statistics.counterUsagesCollector implementationClass="com.intellij.codeInsight.navigation.actions.GoToDeclarationCollector"/>
<statistics.counterUsagesCollector implementationClass="com.intellij.accessibility.AccessibilityUsageTrackerCollector"/>
<statistics.applicationUsagesCollector implementation="com.intellij.accessibility.AccessibilityToolsStateCollector"/>
<statistics.counterUsagesCollector
implementationClass="com.intellij.ide.actions.searcheverywhere.SearchEverywhereFiltersStatisticsCollector"/>
<statistics.counterUsagesCollector
@@ -1804,6 +1805,7 @@
provider="com.intellij.ide.minimap.settings.MinimapConfigurableProvider"
key="settings.name" bundle="messages.MinimapBundle"/>
<registryKey key="popup.advertiser.speed.search" defaultValue="false" description="Shows speed search advertiser in popups"/>
<registryKey key="wayland.true.popups" defaultValue="false" description="Show popups in Wayland as true popups (allows remembering the location, but limits them to the IDE window)"/>
<actionPromoter implementation="com.intellij.ide.actions.speedSearch.SpeedSearchActionPromoter"/>
<fileEditorProvider id="learnIde" implementation="com.intellij.openapi.wm.impl.welcomeScreen.learnIde.editorTab.LearnIdeFileEditorProvider"/>
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.openapi.project.impl
import com.intellij.ide.impl.OpenProjectTask
@@ -6,6 +6,8 @@ import com.intellij.ide.impl.ProjectUtil
import com.intellij.ide.impl.ProjectUtilCore
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.project.ProjectManagerListener
import com.intellij.openapi.project.ex.ProjectManagerEx
import com.intellij.openapi.startup.InitProjectActivity
import com.intellij.testFramework.ExtensionTestUtil
@@ -16,6 +18,7 @@ import com.intellij.testFramework.fixtures.BareTestFixtureTestCase
import com.intellij.testFramework.rules.InMemoryFsRule
import com.intellij.testFramework.rules.TempDirectory
import com.intellij.testFramework.useProject
import com.intellij.util.application
import com.intellij.util.io.createDirectories
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
@@ -123,4 +126,19 @@ class ProjectOpeningTest : BareTestFixtureTestCase() {
val projectFilePath = project!!.useProject { it.projectFilePath }
assertThat(projectFilePath).isEqualTo(projectFile.invariantSeparatorsPathString)
}
@Suppress("removal")
@Test
fun `project listener is called under write-intent lock`() {
val listenerRunsUnderWriteIntent = AtomicBoolean(false)
val projectDir = tempDir.root.toPath()
application.messageBus.connect(testRootDisposable).subscribe(ProjectManager.TOPIC, object : ProjectManagerListener {
override fun projectOpened(project: Project) {
listenerRunsUnderWriteIntent.set(application.isWriteIntentLockAcquired)
}
})
val project = ProjectUtil.openOrImport(projectDir, OpenProjectTask())
project?.useProject { } // closing this project
assertThat(listenerRunsUnderWriteIntent.get()).isTrue()
}
}
@@ -14,6 +14,7 @@ import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFSImpl;
import com.intellij.testFramework.junit5.TestApplication;
import com.intellij.testFramework.utils.vfs.CheckVFSHealthExtension;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeAll;
@@ -312,9 +313,7 @@ public class PersistentFS_FindFilesTest {
try {
List<? extends Future<?>> futures = ContainerUtil.map(tasks, task -> pool.submit(task));
for (Future<?> future : futures) {
future.get();
}
ConcurrencyUtil.getAll(futures);
}
finally {
pool.shutdown();
@@ -7,6 +7,7 @@ import com.intellij.openapi.vfs.newvfs.persistent.FSRecords;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.IdeaTestFixture;
import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory;
import com.intellij.util.ConcurrencyUtil;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArrayList;
@@ -191,9 +192,7 @@ public final class FileNameCacheMicroBenchmark {
futures.add(ApplicationManager.getApplication().executeOnPooledThread(
() -> testIteration.doTest(finalI, ids, threadRandom, queryCount)));
}
for (Future<?> future : futures) {
future.get();
}
ConcurrencyUtil.getAll(futures);
return System.currentTimeMillis() - start;
}
@@ -107,7 +107,7 @@ class ThreadContextPropagationTest {
val service = EdtScheduledExecutorService.getInstance()
doScheduledExecutorServiceTest(service)
doPropagationTest {
service.schedule(it.runnable(), ModalityState.any(), 10, TimeUnit.MILLISECONDS)
service.schedule(it.runnable(), 10, TimeUnit.MILLISECONDS)
}
}
File diff suppressed because it is too large Load Diff
@@ -112,7 +112,8 @@ public final class EnvironmentUtil {
ourEnvGetter.set(loader);
}
private static Map<String, String> getSystemEnv() {
@ApiStatus.Internal
public static Map<String, String> getSystemEnv() {
if (OS.CURRENT == OS.Windows) {
return Collections.unmodifiableMap(CollectionFactory.createCaseInsensitiveStringMap(System.getenv()));
}
@@ -5,6 +5,7 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.platform.ijent.community.buildConstants.IjentBuildScriptsConstantsKt;
import com.intellij.testFramework.PlatformTestUtil;
import com.intellij.testFramework.rules.TempDirectory;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.ExceptionUtil;
import com.intellij.util.ThrowableConsumer;
import com.intellij.util.lang.ClassPath;
@@ -121,9 +122,7 @@ public class UrlClassLoaderTest {
}));
}
for (Future<?> future : futures) {
future.get();
}
ConcurrencyUtil.getAll(futures);
}
}
finally {
@@ -0,0 +1,91 @@
// 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.util.ui
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.fileLogger
import com.intellij.ui.ComponentUtil
import org.jetbrains.annotations.ApiStatus
import java.awt.Component
import java.awt.Rectangle
import java.awt.Window
import javax.swing.SwingUtilities
@ApiStatus.Internal
fun moveToFitChildPopupX(childBounds: Rectangle, parent: Component) {
if (!parent.isShowing) {
LOG.warn("Impossible to fit the child popup to the main window because the parent is not showing: $parent")
return
}
val childLocation = childBounds.location
LOG.debug { "The initial child bounds are $childBounds" }
SwingUtilities.convertPointToScreen(childLocation, parent)
LOG.debug { "The initial child location relative to the screen is $childLocation" }
val topLevelWindow = ComponentUtil.findUltimateParent(parent)
if (topLevelWindow !is Window) return
val topLevelBounds = Rectangle(topLevelWindow.locationOnScreen, topLevelWindow.size)
LOG.debug { "The top level bounds are $topLevelBounds" }
val parentLocation = parent.location
LOG.debug { "The relative parent location is $parentLocation" }
SwingUtilities.convertPointToScreen(parentLocation, parent.parent)
val parentBounds = Rectangle(parentLocation, parent.size)
LOG.debug { "The screen parent bounds are $parentBounds" }
childLocation.x = fitValue(
location = childLocation.x,
width = childBounds.width,
start1 = topLevelBounds.x,
end1 = parentBounds.x,
start2 = parentBounds.x + parentBounds.width,
end2 = topLevelBounds.x + topLevelBounds.width,
preferLess = childLocation.x < parentBounds.x + parentBounds.width / 2,
)
SwingUtilities.convertPointFromScreen(childLocation, parent)
childBounds.location = childLocation
LOG.debug { "The final result is $childBounds" }
}
private fun fitValue(location: Int, width: Int, start1: Int, end1: Int, start2: Int, end2: Int, preferLess: Boolean): Int {
LOG.debug { "The available intervals are $start1..$end1 and $start2..$end2, the popup size is $width" }
if (location >= start1 && location + width < end1) {
LOG.debug { "The initial location already fits within the first interval" }
return location
}
if (location >= start2 && location + width < end2) {
LOG.debug { "The initial location already fits within the second interval" }
return location
}
val space1 = end1 - start1
val space2 = end2 - start2
if (space1 >= width && space2 >= width) {
LOG.debug { "We have enough space on both sides, preferring the ${if (preferLess) "first" else "second"}" }
return if (preferLess) end1 - width else start2
}
else if (space1 >= width) {
val result = end1 - width
LOG.debug { "We have enough space the first side: $space1 >= $width, the result is $end1-$width=$result" }
return result
}
else if (space2 >= width) {
LOG.debug { "We have enough space the first side: $space2 >= $width, the result is $start2" }
return start2
}
else if (space1 > 0 && space1 > space2) {
LOG.debug { "We have more space the first side: $space1 > $space2, the result is $start1" }
return start1
}
else if (space2 > 0) {
val result = end2 - width
LOG.debug { "We have more space the second side: $space1 <= $space2, the result is $end2-$width=$result" }
return result
}
else {
LOG.debug { "We don't have any space at all, falling back to the initial value" }
return location
}
}
private val LOG = fileLogger()
+32 -43
View File
@@ -755,22 +755,6 @@ a:com.intellij.openapi.vcs.changes.ChangeViewDiffRequestProcessor$Wrapper
f:com.intellij.openapi.vcs.changes.ChangesPreprocess
- <init>():V
- s:preprocessChangesRemoveDeletedForDuplicateMoved(java.util.List):java.util.List
a:com.intellij.openapi.vcs.changes.ChangesTreeEditorDiffPreview
- com.intellij.openapi.vcs.changes.EditorTabDiffPreview
- <init>(com.intellij.openapi.vcs.changes.ui.ChangesTree,javax.swing.JComponent):V
- b:<init>(com.intellij.openapi.vcs.changes.ui.ChangesTree,javax.swing.JComponent,I,kotlin.jvm.internal.DefaultConstructorMarker):V
- pf:getTree():com.intellij.openapi.vcs.changes.ui.ChangesTree
- handleDoubleClick(java.awt.event.MouseEvent):Z
- handleEnterKey():Z
- p:handleEscapeKey():V
- p:handleNextDiffShortcut():V
- p:handleSingleClick():V
- p:isOpenPreviewWithNextDiffShortcut():Z
- p:isOpenPreviewWithSingleClick():Z
- p:isOpenPreviewWithSingleClickEnabled():Z
- p:isPreviewOnDoubleClick():Z
- p:isPreviewOnEnter():Z
- p:returnFocusToTree():V
com.intellij.openapi.vcs.changes.ChangesViewEx
- com.intellij.openapi.vcs.changes.ChangesViewI
- a:getCommitWorkflowHandler():com.intellij.vcs.commit.ChangesViewCommitWorkflowHandler
@@ -781,13 +765,12 @@ com.intellij.openapi.vcs.changes.ChangesViewI
- a:scheduleRefresh():V
- a:selectChanges(java.util.List):V
- a:selectFile(com.intellij.openapi.vfs.VirtualFile):V
- a:setBusy(Z):V
- a:setGrouping(java.lang.String):V
- a:updateProgressComponent(java.util.List):V
c:com.intellij.openapi.vcs.changes.ChangesViewManager
- com.intellij.openapi.Disposable
- com.intellij.openapi.vcs.changes.ChangesViewEx
- <init>(com.intellij.openapi.project.Project):V
- <init>(com.intellij.openapi.project.Project,kotlinx.coroutines.CoroutineScope):V
- closeEditorPreview(Z):V
- s:createTextStatusFactory(java.lang.String,Z):com.intellij.openapi.util.Factory
- diffPreviewChanged(Z):V
@@ -802,28 +785,8 @@ c:com.intellij.openapi.vcs.changes.ChangesViewManager
- scheduleRefresh(java.lang.Runnable):V
- selectChanges(java.util.List):V
- selectFile(com.intellij.openapi.vfs.VirtualFile):V
- setBusy(Z):V
- setGrouping(java.lang.String):V
- updateProgressComponent(java.util.List):V
f:com.intellij.openapi.vcs.changes.ChangesViewManager$ChangesViewToolWindowPanel
- com.intellij.openapi.ui.SimpleToolWindowPanel
- com.intellij.openapi.Disposable
- addListener(com.intellij.openapi.vcs.changes.ChangesViewManager$ChangesViewToolWindowPanel$Listener,com.intellij.openapi.Disposable):V
- dispose():V
- getActions(Z):java.util.List
- isAllowExcludeFromCommit():Z
- scheduleRefresh():V
- selectChanges(java.util.List):V
- selectFile(com.intellij.openapi.vfs.VirtualFile):V
- uiDataSnapshot(com.intellij.openapi.actionSystem.DataSink):V
- updateCommitWorkflow():V
com.intellij.openapi.vcs.changes.ChangesViewManager$ChangesViewToolWindowPanel$Listener
- java.util.EventListener
- a:allowExcludeFromCommitChanged():V
c:com.intellij.openapi.vcs.changes.ChangesViewManager$ContentPreloader
- com.intellij.openapi.vcs.changes.ui.ChangesViewContentProvider$Preloader
- <init>(com.intellij.openapi.project.Project):V
- preloadTabContent(com.intellij.ui.content.Content):V
c:com.intellij.openapi.vcs.changes.ChangesViewManager$ContentProvider
- com.intellij.openapi.vcs.changes.ui.ChangesViewContentProvider
- <init>(com.intellij.openapi.project.Project):V
@@ -2644,7 +2607,7 @@ c:com.intellij.openapi.vcs.changes.ui.TreeHandlerDiffRequestProcessor
- pf:selectChange(com.intellij.openapi.vcs.changes.ChangeViewDiffRequestProcessor$Wrapper):V
- p:showAllChangesForEmptySelection():Z
a:com.intellij.openapi.vcs.changes.ui.TreeHandlerEditorDiffPreview
- com.intellij.openapi.vcs.changes.ChangesTreeEditorDiffPreview
- com.intellij.openapi.vcs.changes.EditorTabDiffPreview
- sf:Companion:com.intellij.openapi.vcs.changes.ui.TreeHandlerEditorDiffPreview$Companion
- <init>(com.intellij.openapi.vcs.changes.ui.ChangesTree,com.intellij.openapi.vcs.changes.ui.ChangesTreeDiffPreviewHandler):V
- <init>(com.intellij.openapi.vcs.changes.ui.ChangesTree,javax.swing.JComponent,com.intellij.openapi.vcs.changes.ui.ChangesTreeDiffPreviewHandler):V
@@ -2655,7 +2618,19 @@ a:com.intellij.openapi.vcs.changes.ui.TreeHandlerEditorDiffPreview
- pf:getEditorTabName(com.intellij.diff.impl.DiffEditorViewer):java.lang.String
- a:getEditorTabName(com.intellij.openapi.vcs.changes.ChangeViewDiffRequestProcessor$Wrapper):java.lang.String
- pf:getHandler():com.intellij.openapi.vcs.changes.ui.ChangesTreeDiffPreviewHandler
- pf:getTree():com.intellij.openapi.vcs.changes.ui.ChangesTree
- handleDoubleClick(java.awt.event.MouseEvent):Z
- handleEnterKey():Z
- p:handleEscapeKey():V
- p:handleNextDiffShortcut():V
- p:handleSingleClick():V
- f:hasContent():Z
- p:isOpenPreviewWithNextDiffShortcut():Z
- p:isOpenPreviewWithSingleClick():Z
- p:isOpenPreviewWithSingleClickEnabled():Z
- p:isPreviewOnDoubleClick():Z
- p:isPreviewOnEnter():Z
- p:returnFocusToTree():V
f:com.intellij.openapi.vcs.changes.ui.TreeHandlerEditorDiffPreview$Companion
- f:createDefaultViewer(com.intellij.openapi.vcs.changes.ui.ChangesTree,com.intellij.openapi.vcs.changes.ui.ChangesTreeDiffPreviewHandler,java.lang.String):com.intellij.diff.impl.DiffEditorViewer
a:com.intellij.openapi.vcs.changes.ui.VcsToolWindowFactory
@@ -3969,24 +3944,37 @@ a:com.intellij.vcs.commit.ChangeListViewCommitPanel
- f:getEditedCommit():com.intellij.platform.vcs.impl.shared.commit.EditedCommitPresentation
- f:getIncludedChanges():java.util.List
- f:getIncludedUnversionedFiles():java.util.List
- f:getInclusionModel():com.intellij.openapi.vcs.changes.InclusionModel
- f:select(java.lang.Object):V
- f:selectFirst(java.util.Collection):V
- f:setCompletionContext(java.util.List):V
- f:setEditedCommit(com.intellij.platform.vcs.impl.shared.commit.EditedCommitPresentation):V
- f:setInclusionModel(com.intellij.openapi.vcs.changes.InclusionModel):V
- setInclusionModel(com.intellij.openapi.vcs.changes.InclusionModel):V
f:com.intellij.vcs.commit.ChangesViewCommitPanel
- com.intellij.vcs.commit.ChangeListViewCommitPanel
- com.intellij.vcs.commit.NonModalCommitPanel
- com.intellij.vcs.commit.ChangesViewCommitWorkflowUi
- activate():Z
- deactivate(Z):V
- dispose():V
- endExecution():V
- expand(java.lang.Object):V
- getCommitProgressUi():com.intellij.vcs.commit.CommitProgressUi
- getDisplayedChanges():java.util.List
- getDisplayedUnversionedFiles():java.util.List
- getEditedCommit():com.intellij.platform.vcs.impl.shared.commit.EditedCommitPresentation
- getIncludedChanges():java.util.List
- getIncludedUnversionedFiles():java.util.List
- isActive():Z
- refreshChangesViewBeforeCommit(kotlin.coroutines.Continuation):java.lang.Object
- select(java.lang.Object):V
- selectFirst(java.util.Collection):V
- setCompletionContext(java.util.List):V
- setEditedCommit(com.intellij.platform.vcs.impl.shared.commit.EditedCommitPresentation):V
- setInclusionModel(com.intellij.openapi.vcs.changes.InclusionModel):V
com.intellij.vcs.commit.ChangesViewCommitWorkflowUi
- com.intellij.vcs.commit.NonModalCommitWorkflowUi
- a:deactivate(Z):V
- a:endExecution():V
- a:expand(java.lang.Object):V
- a:getInclusionModel():com.intellij.openapi.vcs.changes.InclusionModel
- a:isActive():Z
- a:refreshChangesViewBeforeCommit(kotlin.coroutines.Continuation):java.lang.Object
- a:select(java.lang.Object):V
@@ -4093,6 +4081,7 @@ c:com.intellij.vcs.commit.CommitProgressPanel
- com.intellij.openapi.vcs.changes.InclusionListener
- com.intellij.vcs.commit.CommitProgressUi
- <init>(com.intellij.openapi.project.Project):V
- <init>(com.intellij.openapi.project.Project,com.intellij.vcs.commit.CommitWorkflowUi,com.intellij.ui.EditorTextComponent):V
- addCommitCheckFailure(com.intellij.vcs.commit.CommitCheckFailure):V
- p:buildErrorText():java.lang.String
- clearCommitCheckFailures():V
@@ -11,7 +11,8 @@
<ui.content.impl.toolWindowContentPostprocessor implementation="com.intellij.platform.vcs.impl.frontend.toolwindow.VcsToolWindowContentReplacer"/>
<frontendChangesViewContentProvider implementation="com.intellij.platform.vcs.impl.frontend.shelf.ShelveChangesViewContentProvider"/>
<frontendChangesViewContentProvider implementation="com.intellij.platform.vcs.impl.frontend.changes.LocalChangesViewContentProvider"/>
<frontend.splitComponentProvider implementation="com.intellij.platform.vcs.impl.frontend.changes.ChangesViewSplitComponentProvider"/>
<recentFiles.navigator implementation="com.intellij.platform.vcs.impl.frontend.diff.DiffRecentFilesNavigator"/>
<recentFiles.excluder implementation="com.intellij.platform.vcs.impl.frontend.diff.DiffRecentFilesExcluder"/>
@@ -0,0 +1,41 @@
// 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.platform.vcs.impl.frontend.changes
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.changes.*
import com.intellij.platform.vcs.impl.shared.changes.ChangeListsViewModel
import com.intellij.ui.split.SplitComponentBinding
import com.intellij.ui.split.SplitComponentProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import javax.swing.JComponent
internal class ChangesViewSplitComponentProvider : SplitComponentProvider<ChangesViewId> {
override val binding: SplitComponentBinding<ChangesViewId> = ChangesViewSplitComponentBinding
override fun createComponent(project: Project, scope: CoroutineScope, modelId: ChangesViewId): JComponent {
val tree = LocalChangesListView(project)
val panel = CommitChangesViewWithToolbarPanel(tree, scope)
panel.id = modelId
panel.initPanel(object : CommitChangesViewWithToolbarPanel.ModelProvider {
override fun getModelData(): CommitChangesViewWithToolbarPanel.ModelProvider.ModelData {
val changeLists = ChangeListsViewModel.getInstance(project).changeLists.value
return CommitChangesViewWithToolbarPanel.ModelProvider.ModelData(changeLists.lists,
emptyList(),
emptyList()) { true }
}
override fun synchronizeInclusion(changeLists: List<LocalChangeList>, unversionedFiles: List<FilePath>) {
}
})
scope.launch {
ChangeListsViewModel.getInstance(project).changeLists.collect {
panel.scheduleRefresh()
}
}
return panel
}
}
@@ -1,56 +0,0 @@
// 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.platform.vcs.impl.frontend.changes
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.changes.CommitChangesViewWithToolbarPanel
import com.intellij.openapi.vcs.changes.LocalChangeList
import com.intellij.openapi.vcs.changes.LocalChangesListView
import com.intellij.openapi.vcs.changes.ui.ChangesGroupingPolicyFactory
import com.intellij.openapi.vcs.changes.ui.TreeModelBuilder
import com.intellij.platform.vcs.impl.shared.RdLocalChanges
import com.intellij.platform.vcs.impl.shared.changes.ChangeListsViewModel
import com.intellij.ui.content.Content
import com.intellij.util.cancelOnDispose
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import org.jetbrains.annotations.NonNls
import javax.swing.tree.DefaultTreeModel
internal class LocalChangesViewContentProvider : FrontendChangesViewContentProvider {
override fun matchesTabName(tabName: @NonNls String): Boolean = tabName == "Local Changes" || tabName == "Commit"
override fun isAvailable(project: Project): Boolean = RdLocalChanges.isEnabled()
override fun initTabContent(project: Project, content: Content) {
val tree = LocalChangesListView(project)
val panel = CommitChangesViewWithToolbarPanel(tree, content)
panel.initPanel(object : CommitChangesViewWithToolbarPanel.ModelProvider {
override fun getModel(grouping: ChangesGroupingPolicyFactory): CommitChangesViewWithToolbarPanel.ModelProvider.ExtendedTreeModel {
val changeLists = ChangeListsViewModel.getInstance(project).changeLists.value
return CommitChangesViewWithToolbarPanel.ModelProvider.ExtendedTreeModel(
changeLists.lists, emptyList(), buildModel(project, grouping, changeLists)
)
}
override fun synchronizeInclusion(changeLists: List<LocalChangeList>, unversionedFiles: List<FilePath>) {
}
})
content.component = panel
project.service<ScopeProvider>().cs.launch {
ChangeListsViewModel.getInstance(project).changeLists.collect {
panel.scheduleRefresh()
}
}.cancelOnDispose(content)
}
private fun buildModel(project: Project, grouping: ChangesGroupingPolicyFactory, lists: ChangeListsViewModel.ChangeLists): DefaultTreeModel =
TreeModelBuilder(project, grouping).setChangeLists(lists.lists, false, null).build()
@Service(Service.Level.PROJECT)
internal class ScopeProvider(val cs: CoroutineScope)
}
@@ -0,0 +1,15 @@
// 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.openapi.vcs.changes
import com.intellij.platform.rpc.Id
import com.intellij.platform.rpc.UID
import com.intellij.ui.split.SplitComponentBinding
import kotlinx.serialization.Serializable
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
val ChangesViewSplitComponentBinding: SplitComponentBinding<ChangesViewId> = SplitComponentBinding("ChangesView", ::ChangesViewId)
@Serializable
@ApiStatus.Internal
data class ChangesViewId(override val uid: UID) : Id
@@ -21,7 +21,7 @@ import kotlin.properties.Delegates.observable
open class ChangesViewPanel(val changesView: ChangesListView) : BorderLayoutPanel() {
val toolbarActionGroup = DefaultActionGroup()
var isToolbarHorizontal: Boolean by observable(false) { _, oldValue, newValue ->
var isToolbarHorizontal: Boolean by observable(true) { _, oldValue, newValue ->
if (oldValue != newValue) {
addToolbar(newValue) // this also removes toolbar from previous parent
}
@@ -1,54 +1,41 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.openapi.vcs.changes
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.InitialVfsRefreshService
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.changes.ui.ChangeNodeDecorator
import com.intellij.openapi.vcs.changes.ui.ChangesBrowserUnversionedLoadingPendingNode
import com.intellij.openapi.vcs.changes.ui.ChangesGroupingPolicyFactory
import com.intellij.openapi.vcs.changes.ui.TreeModelBuilder
import com.intellij.platform.vcs.impl.shared.changes.ChangeListsViewModel
import com.intellij.platform.vcs.impl.shared.changes.TreeModelBuilderEx
import com.intellij.platform.vcs.impl.shared.commit.PartialCommitChangeNodeDecorator
import org.jetbrains.annotations.ApiStatus
import javax.swing.tree.DefaultTreeModel
// TODO make shared
private typealias DecoratorProvider = (ChangeNodeDecorator) -> ChangeNodeDecorator
@ApiStatus.Internal
object ChangesViewUtil {
private fun getChangeDecoratorProvider(project: Project, isAllowExcludeFromCommit: () -> Boolean): (ChangeNodeDecorator?) -> PartialCommitChangeNodeDecorator {
return { baseDecorator: ChangeNodeDecorator? -> PartialCommitChangeNodeDecorator(project, baseDecorator, isAllowExcludeFromCommit) }
}
private fun getChangeDecoratorProvider(project: Project, isAllowExcludeFromCommit: () -> Boolean): DecoratorProvider =
{ baseDecorator -> PartialCommitChangeNodeDecorator(project, baseDecorator, isAllowExcludeFromCommit) }
fun createTreeModel(
project: Project,
grouping: ChangesGroupingPolicyFactory,
changeLists: List<LocalChangeList>,
unversionedFiles: List<FilePath>,
showIgnoredFiles: Boolean,
ignoredFiles: List<FilePath>,
isAllowExcludeFromCommit: () -> Boolean
): DefaultTreeModel {
val changeListManager = ChangeListManagerImpl.getInstanceImpl(project)
val shouldShowUntrackedLoading = unversionedFiles.isEmpty() &&
!project.getService(InitialVfsRefreshService::class.java).isInitialVfsRefreshFinished() &&
changeListManager.isUnversionedInUpdateMode
val skipSingleDefaultChangeList = !changeListManager.areChangeListsEnabled()
val skipSingleDefaultChangeList = !ChangeListsViewModel.getInstance(project).areChangeListsEnabled.value
val treeModelBuilder = TreeModelBuilder(project, grouping)
.setChangeLists(changeLists, skipSingleDefaultChangeList, getChangeDecoratorProvider(project, isAllowExcludeFromCommit))
.also { TreeModelBuilderEx.getInstanceOrNull(project)?.modifyTreeModelBuilder(it) }
.setUnversioned(unversionedFiles)
if (showIgnoredFiles) {
val ignoredFilePaths: List<FilePath> = changeListManager.getIgnoredFilePaths()
treeModelBuilder.setIgnored(ignoredFilePaths)
}
if (shouldShowUntrackedLoading) {
treeModelBuilder.insertSubtreeRoot(ChangesBrowserUnversionedLoadingPendingNode())
}
treeModelBuilder.setIgnored(ignoredFiles)
for (extension in ChangesViewModifier.KEY.getExtensions(project)) {
try {
@@ -3,25 +3,19 @@ package com.intellij.openapi.vcs.changes
import com.intellij.ide.CommonActionsManager
import com.intellij.ide.ui.customization.CustomActionsSchema
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.UI
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.progress.checkCanceled
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.changes.ChangesViewModifier.ChangesViewModifierListener
import com.intellij.openapi.vcs.changes.ui.ChangesGroupingPolicyFactory
import com.intellij.openapi.vcs.changes.ui.ChangesListView
import com.intellij.openapi.vcs.changes.ui.ChangesTree
import com.intellij.openapi.vcs.changes.ui.TreeModelBuilder
import com.intellij.platform.diagnostic.telemetry.TelemetryManager
import com.intellij.platform.diagnostic.telemetry.helpers.use
import com.intellij.platform.util.coroutines.childScope
import com.intellij.platform.vcs.impl.shared.SingleTaskRunner
import com.intellij.platform.vcs.impl.shared.changes.ChangeListsViewModel
import com.intellij.platform.vcs.impl.shared.changes.ChangesViewSettings
@@ -30,57 +24,66 @@ import com.intellij.platform.vcs.impl.shared.telemetry.ChangesView
import com.intellij.platform.vcs.impl.shared.telemetry.VcsScope
import com.intellij.ui.ExperimentalUI.Companion.isNewUI
import com.intellij.util.application
import com.intellij.util.asDisposable
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.intellij.util.ui.EdtInvocationManager.invokeLaterIfNeeded
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collectLatest
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.CalledInAny
import java.lang.Runnable
import javax.swing.tree.DefaultTreeModel
import kotlin.time.Duration.Companion.milliseconds
private val REFRESH_DELAY = 100.milliseconds
@ApiStatus.Internal
class CommitChangesViewWithToolbarPanel(changesView: ChangesListView, parentDisposable: Disposable) : ChangesViewPanel(changesView), Disposable {
class CommitChangesViewWithToolbarPanel(
changesView: ChangesListView,
private val cs: CoroutineScope,
) : ChangesViewPanel(changesView) {
val project: Project get() = changesView.project
private val settings get() = ChangesViewSettings.getInstance(project)
private val scope = project.service<ScopeProvider>().cs.childScope("CommitChangesListWithToolbarPanel")
private val refresher = SingleTaskRunner(scope, 100.milliseconds) {
private val refresher = SingleTaskRunner(cs) {
refreshView()
}
private var modelProvider: ModelProvider? = null
private var disposed = false
var id: ChangesViewId? = null
set(value) {
if (field == null) field = value
else error("Id is already assigned")
}
init {
refresher.start()
Disposer.register(parentDisposable, this)
}
@CalledInAny
fun setBusy(busy: Boolean) {
invokeLaterIfNeeded { changesView.setPaintBusy(busy) }
cs.launch(Dispatchers.UI) {
refresher.getIdleFlow().collect { idle ->
changesView.setPaintBusy(!idle)
}
}
}
@RequiresEdt
fun initPanel(modelProvider: ModelProvider) {
checkNotNull(id)
this.modelProvider = modelProvider
scope.launch(Dispatchers.UI) {
cs.launch(Dispatchers.UI) {
ChangeListsViewModel.getInstance(project).changeListManagerState.collectLatest {
changesView.repaint()
}
}
scope.launch(Dispatchers.UI) {
cs.launch(Dispatchers.UI) {
PartialChangesHolder.getInstance(project).updates.collectLatest {
changesView.repaint()
}
}
changesView.isShowCheckboxes = true
changesView.installPopupHandler(
ActionManager.getInstance().getAction("ChangesViewPopupMenuShared") as ActionGroup
)
@@ -92,16 +95,16 @@ class CommitChangesViewWithToolbarPanel(changesView: ChangesListView, parentDisp
toolbarActionGroup.addAll(createChangesToolbarActions(changesView))
Initializer.EP_NAME.forEachExtensionSafe { it.init(scope, this) }
Initializer.EP_NAME.forEachExtensionSafe { it.init(cs, this) }
ChangesViewModifier.KEY.addChangeListener(project, { resetViewImmediatelyAndRefreshLater() }, this)
project.messageBus.connect(scope).subscribe(ChangesViewModifier.TOPIC, ChangesViewModifierListener { scheduleRefresh() })
ChangesViewModifier.KEY.addChangeListener(project, { resetViewImmediatelyAndRefreshLater() }, cs.asDisposable())
project.messageBus.connect(cs).subscribe(ChangesViewModifier.TOPIC, ChangesViewModifierListener { scheduleRefresh() })
scheduleRefresh()
}
@CalledInAny
fun scheduleRefresh() {
fun scheduleRefresh(){
scheduleRefresh(withDelay = true)
}
@@ -118,35 +121,43 @@ class CommitChangesViewWithToolbarPanel(changesView: ChangesListView, parentDisp
@CalledInAny
private fun scheduleRefresh(withDelay: Boolean, @RequiresBackgroundThread callback: Runnable? = null) {
setBusy(true)
if (withDelay) {
if (!withDelay && callback == null) {
refresher.request()
return
}
else {
refresher.requestNow()
}
scope.launch {
cs.launch {
if (withDelay) delay(REFRESH_DELAY)
refresher.request()
refresher.awaitNotBusy()
callback?.run()
setBusy(false)
}
}
@RequiresBackgroundThread
private suspend fun refreshView() {
if (disposed || !project.isInitialized || application.isUnitTestMode) return
if (!cs.isActive || !project.isInitialized || application.isUnitTestMode) return
val modelProvider = modelProvider ?: return
val model = TRACER.spanBuilder(ChangesView.ChangesViewRefreshBackground.name).use {
modelProvider.getModel(changesView.grouping)
val (modelData, model) = TRACER.spanBuilder(ChangesView.ChangesViewRefreshBackground.name).use {
val modelData = modelProvider.getModelData()
modelData to ChangesViewUtil.createTreeModel(
project,
changesView.grouping,
modelData.changeLists,
modelData.unversionedFiles,
modelData.ignoredFiles,
modelData.isAllowExcludeFromCommit,
)
}
checkCanceled()
withContext(Dispatchers.EDT) {
TRACER.spanBuilder(ChangesView.ChangesViewRefreshEdt.getName()).use {
changesView.updateTreeModel(model.treeModel, ChangesViewTreeStateStrategy())
changesView.updateTreeModel(model, ChangesViewTreeStateStrategy())
checkCanceled()
modelProvider.synchronizeInclusion(model.changeLists, model.unversionedFiles)
modelProvider.synchronizeInclusion(modelData.changeLists, modelData.unversionedFiles)
}
}
}
@@ -162,11 +173,6 @@ class CommitChangesViewWithToolbarPanel(changesView: ChangesListView, parentDisp
scheduleRefreshNow()
}
override fun dispose() {
scope.cancel()
disposed = true
}
private companion object {
private val TRACER
get() = TelemetryManager.getInstance().getTracer(VcsScope)
@@ -192,11 +198,14 @@ class CommitChangesViewWithToolbarPanel(changesView: ChangesListView, parentDisp
}
interface ModelProvider {
fun getModel(grouping: ChangesGroupingPolicyFactory): ExtendedTreeModel
fun getModelData(): ModelData
fun synchronizeInclusion(changeLists: List<LocalChangeList>, unversionedFiles: List<FilePath>)
class ExtendedTreeModel(val changeLists: List<LocalChangeList>, val unversionedFiles: List<FilePath>, val treeModel: DefaultTreeModel)
class ModelData(val changeLists: List<LocalChangeList>,
val unversionedFiles: List<FilePath>,
val ignoredFiles: List<FilePath>,
val isAllowExcludeFromCommit: () -> Boolean)
}
interface Initializer {
@@ -206,7 +215,4 @@ class CommitChangesViewWithToolbarPanel(changesView: ChangesListView, parentDisp
internal val EP_NAME = ExtensionPointName<Initializer>("com.intellij.vcs.commitChangesViewInitializer")
}
}
@Service(Service.Level.PROJECT)
internal class ScopeProvider(val cs: CoroutineScope)
}
}
@@ -6,10 +6,7 @@ import com.intellij.openapi.diagnostic.getOrHandleException
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.checkCanceled
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.*
import org.jetbrains.annotations.ApiStatus
import kotlin.time.Duration
@@ -21,47 +18,39 @@ import kotlin.time.Duration
@ApiStatus.Internal
class SingleTaskRunner(
cs: CoroutineScope,
private val delay: Duration = Duration.Companion.ZERO,
private val task: suspend () -> Unit,
) {
private val requested = MutableStateFlow(false)
private val busy = MutableStateFlow(false)
/**
* Represents the state of the runner:
* 0 - idle,
* 1 - a single task is either queued or running,
* 2 - has one running task and one queued task.
*/
private val requestCounter = MutableStateFlow(0)
private val runNow = Channel<Unit>(capacity = Channel.CONFLATED)
fun getIdleFlow(): Flow<Boolean> = requestCounter.map { it == 0 }
private val processorJob = cs.launch(Dispatchers.Default, CoroutineStart.LAZY) {
try {
while (true) {
checkCanceled()
requested.first { it }
if (delay.isPositive()) {
withTimeoutOrNull(delay) { runNow.receive() }
}
busy.value = true
requested.value = false
requestCounter.first { it > 0 }
checkCanceled()
runCatching {
task()
}.getOrHandleException { LOG.error("Task failed", it) }
busy.value = false
requestCounter.update { it.dec() }
}
}
finally {
requested.value = false
busy.value = false
requestCounter.value = 0
LOG.debug { "Task processing finished" }
}
}
fun request() {
if (processorJob.isCancelled) return
requested.value = true
}
fun requestNow() {
if (processorJob.isCancelled) return
runNow.trySend(Unit)
requested.value = true
requestCounter.update { it.inc().coerceAtMost(2) }
}
fun start() {
@@ -73,10 +62,15 @@ class SingleTaskRunner(
* Await the state where the task is not executed and there are no requests to do so.
*/
suspend fun awaitNotBusy() {
requested.combine(busy) { requested, busy -> !requested && !busy }.first { it }
getIdleFlow().first { it }
}
companion object {
private val LOG = logger<SingleTaskRunner>()
fun delayedTaskRunner(cs: CoroutineScope, delay: Duration, task: suspend () -> Unit): SingleTaskRunner = SingleTaskRunner(cs) {
delay(delay)
task()
}
}
}
@@ -22,7 +22,7 @@ class ChangeListsViewModel(
) {
val areChangeListsEnabled: StateFlow<Boolean> = changeListsApiFlow(checkRegistry = false) { api, projectId ->
emitAll(api.areChangeListsEnabled(projectId))
}.stateIn(cs, SharingStarted.Eagerly, false)
}.stateIn(cs, SharingStarted.Eagerly, true)
val changeListManagerState: StateFlow<ChangeListManagerState> = changeListsApiFlow(checkRegistry = false) { api, projectId ->
emitAll(api.getChangeListManagerState(projectId))
@@ -525,7 +525,6 @@ public final class ChangeListManagerImpl extends ChangeListManagerEx implements
isInitialUpdate = myInitialUpdate;
myInitialUpdate = false;
}
changesView.setBusy(true);
changesView.scheduleRefresh();
SensitiveProgressWrapper vcsIndicator = new SensitiveProgressWrapper(ProgressManager.getInstance().getProgressIndicator());
@@ -1,93 +0,0 @@
// 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.openapi.vcs.changes
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.application.ApplicationManager.getApplication
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.vcs.VcsConfiguration
import com.intellij.openapi.vcs.changes.ui.ChangesTree
import com.intellij.ui.ExpandableItemsHandler
import com.intellij.util.EditSourceOnDoubleClickHandler
import com.intellij.util.Processor
import com.intellij.util.ui.UIUtil
import java.awt.event.MouseEvent
import javax.swing.JComponent
abstract class ChangesTreeEditorDiffPreview(
protected val tree: ChangesTree,
targetComponent: JComponent = tree,
) : EditorTabDiffPreview(tree.project) {
init {
tree.doubleClickHandler = Processor { e -> handleDoubleClick(e) }
tree.enterKeyHandler = Processor { e -> handleEnterKey() }
tree.addSelectionListener { handleSingleClick() }
PreviewOnNextDiffAction().registerCustomShortcutSet(targetComponent, this)
UIUtil.putClientProperty(tree, ExpandableItemsHandler.IGNORE_ITEM_SELECTION, true)
}
protected open fun isPreviewOnDoubleClick(): Boolean = true
open fun handleDoubleClick(e: MouseEvent): Boolean {
if (!isPreviewOnDoubleClick()) return false
if (EditSourceOnDoubleClickHandler.isToggleEvent(tree, e)) return false
return performDiffAction()
}
protected open fun isPreviewOnEnter(): Boolean = true
open fun handleEnterKey(): Boolean {
if (!isPreviewOnEnter()) return false
return performDiffAction()
}
protected open fun isOpenPreviewWithSingleClickEnabled(): Boolean = false
protected open fun isOpenPreviewWithSingleClick(): Boolean {
if (!isOpenPreviewWithSingleClickEnabled()) return false
if (!VcsConfiguration.getInstance(project).LOCAL_CHANGES_DETAILS_PREVIEW_SHOWN) return false
return true
}
protected open fun handleSingleClick() {
if (!isOpenPreviewWithSingleClick()) return
val opened = openPreview(false)
if (!opened) {
closePreview() // auto-close editor tab if nothing to preview
}
}
protected open fun isOpenPreviewWithNextDiffShortcut(): Boolean = true
protected open fun handleNextDiffShortcut() {
if (!isOpenPreviewWithNextDiffShortcut()) return
openPreview(true)
}
override fun handleEscapeKey() {
closePreview()
// closing the diff preview editor schedules focus switch to "some" other editor in JBTabsImpl
// we can't disable that, so we have to manually override the focus
getApplication().invokeLater {
returnFocusToTree()
}
}
protected open fun returnFocusToTree() = Unit
private inner class PreviewOnNextDiffAction : DumbAwareAction() {
init {
copyShortcutFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_NEXT_DIFF))
}
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = isOpenPreviewWithNextDiffShortcut()
}
override fun actionPerformed(e: AnActionEvent) {
handleNextDiffShortcut()
}
}
}
@@ -27,7 +27,6 @@ public interface ChangesViewI {
void selectFile(VirtualFile vFile);
void selectChanges(@NotNull List<? extends Change> changes);
void updateProgressComponent(@NotNull List<Supplier<JComponent>> progress);
void setBusy(boolean b);
void setGrouping(@NotNull String groupingKey);
}
@@ -23,23 +23,23 @@ import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.registry.RegistryValue;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
import com.intellij.openapi.vcs.VcsApplicationSettings;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vcs.VcsConfiguration;
import com.intellij.openapi.vcs.changes.actions.diff.ShowDiffFromLocalChangesActionProvider;
import com.intellij.openapi.vcs.changes.shelf.ShelveChangesManager;
import com.intellij.openapi.vcs.changes.ui.*;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.platform.vcs.impl.shared.RdLocalChanges;
import com.intellij.platform.vcs.impl.shared.changes.ChangesViewDataKeys;
import com.intellij.platform.vcs.impl.shared.changes.ChangesViewSettings;
import com.intellij.platform.vcs.impl.shared.changes.PreviewDiffSplitterComponent;
import com.intellij.ui.ComponentUtil;
import com.intellij.ui.JBColor;
import com.intellij.ui.components.JBLabel;
import com.intellij.ui.components.panels.Wrapper;
import com.intellij.ui.content.Content;
import com.intellij.util.*;
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
import com.intellij.util.concurrency.annotations.RequiresEdt;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.messages.MessageBusConnection;
@@ -47,23 +47,18 @@ import com.intellij.util.ui.JBDimension;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.components.BorderLayoutPanel;
import com.intellij.util.ui.tree.TreeUtil;
import com.intellij.vcs.changes.BackendChangesView;
import com.intellij.vcs.commit.*;
import com.intellij.vcsUtil.VcsUtil;
import kotlinx.coroutines.CoroutineScope;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.function.Supplier;
@@ -71,26 +66,25 @@ import static com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager.*;
import static com.intellij.openapi.vcs.changes.ui.ChangesViewContentManagerKt.isCommitToolWindowShown;
import static com.intellij.openapi.vcs.changes.ui.ChangesViewContentManagerKt.subscribeOnVcsToolWindowLayoutChanges;
import static com.intellij.util.ui.JBUI.Panels.simplePanel;
import static java.util.Arrays.asList;
public class ChangesViewManager implements ChangesViewEx, Disposable {
private static final String CHANGES_VIEW_PREVIEW_SPLITTER_PROPORTION = "ChangesViewManager.DETAILS_SPLITTER_PROPORTION";
private final @NotNull CoroutineScope myScope;
private final @NotNull Project myProject;
private @Nullable CommitChangesViewWithToolbarPanel myChangesPanel;
private @Nullable BackendChangesView myChangesView;
private @Nullable ChangesViewToolWindowPanel myToolWindowPanel;
@NotNull
@RequiresEdt
CommitChangesViewWithToolbarPanel initChangesPanel() {
if (myChangesPanel == null) {
BackendChangesView initChangesView() {
if (myChangesView == null) {
Activity activity = StartUpMeasurer.startActivity("ChangesViewPanel initialization");
ChangesListView tree = new LocalChangesListView(myProject);
myChangesPanel = new CommitChangesViewWithToolbarPanel(tree, this);
myChangesView = BackendChangesView.create(myProject, myScope);
activity.end();
}
return myChangesPanel;
return myChangesView;
}
@RequiresEdt
@@ -98,9 +92,8 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
if (myToolWindowPanel == null) {
Activity activity = StartUpMeasurer.startActivity("ChangesViewToolWindowPanel initialization");
// ChangesViewPanel is used for a singular ChangesViewToolWindowPanel instance. Cleanup is not needed.
CommitChangesViewWithToolbarPanel changesViewPanel = initChangesPanel();
ChangesViewToolWindowPanel panel = new ChangesViewToolWindowPanel(myProject, changesViewPanel);
BackendChangesView changesView = initChangesView();
ChangesViewToolWindowPanel panel = new ChangesViewToolWindowPanel(myProject, changesView);
Disposer.register(this, panel);
panel.updateCommitWorkflow();
@@ -108,7 +101,7 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
myToolWindowPanel = panel;
Disposer.register(panel, () -> {
// Content is removed from TW
myChangesPanel = null;
myChangesView = null;
myToolWindowPanel = null;
});
@@ -117,7 +110,8 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
return myToolWindowPanel;
}
public ChangesViewManager(@NotNull Project project) {
public ChangesViewManager(@NotNull Project project, @NotNull CoroutineScope coroutineScope) {
myScope = coroutineScope;
myProject = project;
MessageBusConnection busConnection = project.getMessageBus().connect(this);
@@ -159,10 +153,10 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
public void dispose() {
}
public static class ContentPreloader implements ChangesViewContentProvider.Preloader {
static class ContentPreloader implements ChangesViewContentProvider.Preloader {
private final @NotNull Project myProject;
public ContentPreloader(@NotNull Project project) {
ContentPreloader(@NotNull Project project) {
myProject = project;
}
@@ -182,27 +176,26 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
@Override
public void scheduleRefresh(@NotNull Runnable callback) {
if (myToolWindowPanel != null) {
myToolWindowPanel.scheduleRefreshNow(callback);
}
if (myChangesView == null) return;
myChangesView.getViewModel().scheduleRefreshNow(callback);
}
@Override
public void scheduleRefresh() {
if (myToolWindowPanel == null) return;
myToolWindowPanel.scheduleRefresh();
if (myChangesView == null) return;
myChangesView.getViewModel().scheduleDelayedRefresh();
}
@Override
public void selectFile(VirtualFile vFile) {
if (myToolWindowPanel == null) return;
myToolWindowPanel.selectFile(vFile);
if (myChangesView == null) return;
myChangesView.getViewModel().selectFile(vFile);
}
@Override
public void selectChanges(@NotNull List<? extends Change> changes) {
if (myToolWindowPanel == null) return;
myToolWindowPanel.selectChanges(changes);
if (myChangesView == null) return;
myChangesView.getViewModel().selectChanges(new ArrayList<>(changes));
}
@Override
@@ -211,16 +204,10 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
myToolWindowPanel.updateProgressComponent(progress);
}
@Override
public void setBusy(boolean b) {
if (myToolWindowPanel == null) return;
myToolWindowPanel.myChangesPanel.setBusy(b);
}
@Override
public void setGrouping(@NotNull String groupingKey) {
if (myToolWindowPanel == null) return;
myToolWindowPanel.myChangesPanel.setGrouping(groupingKey);
if (myChangesView == null) return;
myChangesView.getViewModel().setGrouping(groupingKey);
}
private void updateCommitWorkflow() {
@@ -241,8 +228,8 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
@Override
public void resetViewImmediatelyAndRefreshLater() {
if (myToolWindowPanel != null) {
myToolWindowPanel.myChangesPanel.resetViewImmediatelyAndRefreshLater();
if (myChangesView != null) {
myChangesView.getViewModel().resetViewImmediatelyAndRefreshLater();
}
}
@@ -255,25 +242,12 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
@Override
public void initTabContent(@NotNull Content content) {
if (RdLocalChanges.isEnabled()) {
// Action isn't executed unless a context component is showing,
// which makes things complicated in RD scenario because:
// * For Commit TW we still use PROJECTOR_STEALING hacking content of the particular tab.
// * The current content component in a backend TW is selected as the context component.
// (see `ToolWindowComponentIdProvider` and `ComponentIdOwner` for more insights).
// * An empty panel is used for this tab content on the backend.
//
// As a workaround, it's explicitly marked as shown.
ComponentUtil.markAsShowing(content.getComponent(), true);
return;
}
ChangesViewManager viewManager = (ChangesViewManager)getInstance(myProject);
ChangesViewToolWindowPanel panel = viewManager.initToolWindowPanel();
content.setHelpId(ChangesListView.HELP_ID);
content.setComponent(panel);
content.setPreferredFocusableComponent(panel.myView);
content.setPreferredFocusableComponent(panel.myChangesView.getViewModel().getPreferredFocusableComponent());
}
}
@@ -318,7 +292,7 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
}
}
public static final class ChangesViewToolWindowPanel extends SimpleToolWindowPanel implements Disposable {
private static final class ChangesViewToolWindowPanel extends SimpleToolWindowPanel implements Disposable {
private static final @NotNull RegistryValue isOpenEditorDiffPreviewWithSingleClick =
Registry.get("show.diff.preview.as.editor.tab.with.single.click");
@@ -328,8 +302,8 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
private final @NotNull Wrapper myMainPanelContent;
private final @NotNull BorderLayoutPanel myContentPanel;
private final @NotNull CommitChangesViewWithToolbarPanel myChangesPanel;
private final @NotNull ChangesListView myView;
private final @NotNull BackendChangesView myChangesView;
private final @NotNull ChangesViewCommitPanelSplitter myCommitPanelSplitter;
private final @NotNull ChangesViewEditorDiffPreview myEditorDiffPreview;
@@ -345,18 +319,16 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
private boolean myDisposed = false;
private ChangesViewToolWindowPanel(@NotNull Project project,
@NotNull CommitChangesViewWithToolbarPanel changesViewPanel) {
@NotNull BackendChangesView backendChangesView) {
super(false, true);
myProject = project;
myChangesViewSettings = ChangesViewSettings.getInstance(project);
myChangesPanel = changesViewPanel;
changesViewPanel.initPanel(new ModelProvider(project, () -> myCommitWorkflowHandler));
myChangesView = backendChangesView;
myChangesView.getViewModel().initPanel();
MessageBusConnection busConnection = myProject.getMessageBus().connect(this);
myVcsConfiguration = VcsConfiguration.getInstance(myProject);
// ChangesViewPanel is used for a singular ChangesViewToolWindowPanel instance. Cleanup is not needed.
myView = myChangesPanel.getChangesView();
registerShortcuts(this);
ApplicationManager.getApplication().getMessageBus().connect(project)
@@ -372,16 +344,9 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
myCommitPanelSplitter = new ChangesViewCommitPanelSplitter(myProject);
Disposer.register(this, myCommitPanelSplitter);
myCommitPanelSplitter.setFirstComponent(myChangesPanel);
myCommitPanelSplitter.setFirstComponent(backendChangesView.getChangesPanel());
myContentPanel = new BorderLayoutPanel() {
@Override
public Dimension getMinimumSize() {
return isMinimumSizeSet() || myChangesPanel.isToolbarHorizontal()
? super.getMinimumSize()
: myChangesPanel.getToolbar().getComponent().getPreferredSize();
}
};
myContentPanel = new BorderLayoutPanel();
myContentPanel.addToCenter(myCommitPanelSplitter);
myMainPanelContent = new Wrapper(myContentPanel);
JPanel mainPanel = simplePanel(myMainPanelContent)
@@ -391,17 +356,17 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
Disposer.register(this, myEditorDiffPreview);
// Override the handlers registered by editorDiffPreview
myView.setDoubleClickHandler(e -> {
if (EditSourceOnDoubleClickHandler.isToggleEvent(myView, e)) return false;
myChangesView.getViewModel().getTree().setDoubleClickHandler(e -> {
if (EditSourceOnDoubleClickHandler.isToggleEvent(myChangesView.getViewModel().getTree(), e)) return false;
if (performHoverAction()) return true;
if (myEditorDiffPreview.handleDoubleClick(e)) return true;
OpenSourceUtil.openSourcesFrom(DataManager.getInstance().getDataContext(myView), true);
OpenSourceUtil.openSourcesFrom(DataManager.getInstance().getDataContext(myChangesView.getViewModel().getTree()), true);
return true;
});
myView.setEnterKeyHandler(e -> {
myChangesView.getViewModel().getTree().setEnterKeyHandler(e -> {
if (performHoverAction()) return true;
if (myEditorDiffPreview.handleEnterKey()) return true;
OpenSourceUtil.openSourcesFrom(DataManager.getInstance().getDataContext(myView), false);
OpenSourceUtil.openSourcesFrom(DataManager.getInstance().getDataContext(myChangesView.getViewModel().getTree()), false);
return true;
});
@@ -412,7 +377,7 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
}
private boolean performHoverAction() {
ChangesBrowserNode<?> selected = VcsTreeModelData.selected(myView).iterateNodes().single();
ChangesBrowserNode<?> selected = VcsTreeModelData.selected(myChangesView.getViewModel().getTree()).iterateNodes().single();
if (selected == null) return false;
for (ChangesViewNodeAction extension : ChangesViewNodeAction.EP_NAME.getExtensions(myProject)) {
@@ -453,7 +418,7 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
private class ChangesViewEditorDiffPreview extends TreeHandlerEditorDiffPreview {
private ChangesViewEditorDiffPreview() {
super(myView, myContentPanel, ChangesViewDiffPreviewHandler.INSTANCE);
super(myChangesView.getViewModel().getTree(), myContentPanel, ChangesViewDiffPreviewHandler.INSTANCE);
}
@Override
@@ -491,7 +456,7 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
@Override
protected boolean isOpenPreviewWithSingleClick() {
if (myChangesPanel.getChangesView().isModelUpdateInProgress()) return false;
if (myChangesView.getViewModel().isModelUpdateInProgress()) return false;
if (mySplitterDiffPreview != null && myVcsConfiguration.LOCAL_CHANGES_DETAILS_PREVIEW_SHOWN) return false;
return super.isOpenPreviewWithSingleClick();
}
@@ -544,8 +509,7 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
}
private ChangesViewDiffPreviewProcessor createDiffPreviewProcessor(boolean isInEditor) {
ChangesViewDiffPreviewProcessor processor = new ChangesViewDiffPreviewProcessor(myView, isInEditor);
ChangesViewDiffPreviewProcessor processor = new ChangesViewDiffPreviewProcessor(myChangesView.getViewModel().getTree(), isInEditor);
this.addListener(new Listener() {
@Override
public void allowExcludeFromCommitChanged() {
@@ -583,6 +547,7 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
myCommitWorkflowHandler = null;
myCommitPanel = null;
}
myChangesView.getViewModel().setCommitWorkflowHandler(myCommitWorkflowHandler);
myDispatcher.getMulticaster().allowExcludeFromCommitChanged();
configureToolbars();
@@ -598,12 +563,12 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
private void configureToolbars() {
boolean isToolbarHorizontal = CommitModeManager.getInstance(myProject).getCurrentCommitMode().useCommitToolWindow();
myChangesPanel.setToolbarHorizontal(isToolbarHorizontal);
myChangesView.getViewModel().setToolbarHorizontal(isToolbarHorizontal);
}
@Override
public @NotNull List<AnAction> getActions(boolean originalProvider) {
return asList(myChangesPanel.getToolbarActionGroup().getChildren(ActionManager.getInstance()));
return myChangesView.getViewModel().getActions();
}
@Override
@@ -611,7 +576,7 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
super.uiDataSnapshot(sink);
sink.set(DiffDataKeys.EDITOR_TAB_DIFF_PREVIEW, myEditorDiffPreview);
sink.set(ChangesViewDataKeys.SETTINGS, myChangesViewSettings);
sink.set(ChangesViewDataKeys.REFRESHER, () -> scheduleRefreshNow(null));
sink.set(ChangesViewDataKeys.REFRESHER, () -> myChangesView.getViewModel().scheduleRefreshNow(null));
// This makes COMMIT_WORKFLOW_HANDLER available anywhere in "Local Changes" - so commit executor actions are enabled.
DataSink.uiDataSnapshot(sink, myCommitPanel);
@@ -638,42 +603,6 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
});
}
public void scheduleRefresh() {
myChangesPanel.scheduleRefresh();
}
private void scheduleRefreshNow(@Nullable @RequiresBackgroundThread Runnable callback) {
myChangesPanel.scheduleRefreshNow(callback);
}
public void selectFile(@Nullable VirtualFile vFile) {
if (vFile == null) return;
ChangesBrowserNode<?> node = findNodeForFile(vFile);
if (node == null) return;
TreeUtil.selectNode(myView, node);
}
public void selectChanges(@NotNull List<? extends Change> changes) {
List<TreePath> paths = new ArrayList<>();
for (Change change : changes) {
ContainerUtil.addIfNotNull(paths, myView.findNodePathInTree(change));
}
TreeUtil.selectPaths(myView, paths);
}
private @Nullable ChangesBrowserNode<?> findNodeForFile(@NotNull VirtualFile file) {
FilePath filePath = VcsUtil.getFilePath(file);
DefaultMutableTreeNode root = (DefaultMutableTreeNode)myView.getModel().getRoot();
return (ChangesBrowserNode<?>)TreeUtil.findNode(root, node -> {
FilePath nodeFilePath = VcsTreeModelData.mapUserObjectToFilePath(node.getUserObject());
return Objects.equals(filePath, nodeFilePath);
});
}
private void invokeLaterIfNeeded(Runnable runnable) {
ModalityUiUtil.invokeLaterIfNeeded(ModalityState.nonModal(), myProject.getDisposed(), runnable);
}
@@ -693,43 +622,6 @@ public class ChangesViewManager implements ChangesViewEx, Disposable {
return ChangesViewWorkflowManager.getInstance(myProject).getCommitWorkflowHandler();
}
private static class ModelProvider implements CommitChangesViewWithToolbarPanel.ModelProvider {
private final @NotNull Project project;
private final @NotNull Supplier<@Nullable ChangesViewCommitWorkflowHandler> commitWorkflowHandlerSupplier;
private ModelProvider(@NotNull Project project,
@NotNull Supplier<@Nullable ChangesViewCommitWorkflowHandler> commitWorkflowHandlerSupplier) {
this.project = project;
this.commitWorkflowHandlerSupplier = commitWorkflowHandlerSupplier;
}
@Override
public @NotNull ExtendedTreeModel getModel(@NotNull ChangesGroupingPolicyFactory grouping) {
ChangeListManagerImpl changeListManager = ChangeListManagerImpl.getInstanceImpl(project);
List<LocalChangeList> changeLists = changeListManager.getChangeLists();
List<FilePath> unversionedFiles = changeListManager.getUnversionedFilesPaths();
DefaultTreeModel treeModel = ChangesViewUtil.INSTANCE.createTreeModel(
project,
grouping,
changeLists,
unversionedFiles,
ChangesViewSettings.getInstance(project).getShowIgnored(),
() -> isAllowExcludeFromCommit(commitWorkflowHandlerSupplier.get())
);
return new ExtendedTreeModel(changeLists, unversionedFiles, treeModel);
}
@Override
public void synchronizeInclusion(@NotNull List<? extends @NotNull LocalChangeList> changeLists,
@NotNull List<? extends @NotNull FilePath> unversionedFiles) {
ChangesViewCommitWorkflowHandler commitWorkflowHandler = commitWorkflowHandlerSupplier.get();
if (commitWorkflowHandler != null) {
commitWorkflowHandler.synchronizeInclusion(changeLists, unversionedFiles);
}
}
}
private static boolean isAllowExcludeFromCommit(@Nullable ChangesViewCommitWorkflowHandler handler) {
return handler != null && handler.isActive();
}
@@ -40,9 +40,9 @@ internal class ChangesViewWorkflowManagerImpl(
val activity = StartUpMeasurer.startActivity("ChangesViewWorkflowManager initialization")
// ChangesViewPanel can be reused between workflow instances -> should clean up after ourselves
val changesPanel = (ChangesViewManager.getInstance(project) as ChangesViewManager).initChangesPanel()
val changesView = (ChangesViewManager.getInstance(project) as ChangesViewManager).initChangesView()
val workflow = ChangesViewCommitWorkflow(project)
val commitPanel = ChangesViewCommitPanel(project, changesPanel.changesView)
val commitPanel = ChangesViewCommitPanel(project, changesView.viewModel)
_commitWorkflowHandler = ChangesViewCommitWorkflowHandler(workflow, commitPanel)
project.messageBus.syncPublisher(TOPIC).commitWorkflowChanged()
@@ -48,10 +48,6 @@ class DummyChangesView implements ChangesViewEx {
public void updateProgressComponent(@NotNull List<Supplier<JComponent>> progress) {
}
@Override
public void setBusy(boolean b) {
}
@Override
public void setGrouping(@NotNull String groupingKey) {
}
@@ -1,6 +1,7 @@
// 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.openapi.vcs.changes.ui
import com.intellij.openapi.project.InitialVfsRefreshService
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FileStatus
import com.intellij.openapi.vcs.changes.*
@@ -24,6 +25,12 @@ internal class BackendChangesViewModelBuilderService(private val project: Projec
override fun modifyTreeModelBuilder(modelBuilder: TreeModelBuilder) {
val changeListManager = ChangeListManagerImpl.getInstanceImpl(project)
val shouldShowUntrackedLoading = changeListManager.unversionedFilesPaths.isEmpty() &&
!project.getService(InitialVfsRefreshService::class.java).isInitialVfsRefreshFinished() &&
changeListManager.isUnversionedInUpdateMode
if (shouldShowUntrackedLoading) {
modelBuilder.insertSubtreeRoot(ChangesBrowserUnversionedLoadingPendingNode())
}
modelBuilder.setLocallyDeletedPaths(changeListManager.deletedFiles)
.setModifiedWithoutEditing(changeListManager.modifiedWithoutEditing)
@@ -38,7 +38,6 @@ internal class BackendCommitChangesViewWithToolbarInitializer : CommitChangesVie
}
override fun changedFileStatusChanged() {
panel.setBusy(false)
panel.scheduleRefresh()
val changeListManager = ChangeListManagerImpl.getInstanceImpl(panel.project)
ChangesViewManager.getInstance(panel.project).updateProgressComponent(changeListManager.getAdditionalUpdateInfo())
@@ -7,13 +7,23 @@ import com.intellij.diff.impl.DiffEditorViewer
import com.intellij.diff.tools.combined.*
import com.intellij.diff.util.DiffUtil
import com.intellij.openapi.ListSelection
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.application.ApplicationManager.getApplication
import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vcs.VcsConfiguration
import com.intellij.openapi.vcs.changes.Change
import com.intellij.openapi.vcs.changes.ChangeViewDiffRequestProcessor
import com.intellij.openapi.vcs.changes.ChangeViewDiffRequestProcessor.Wrapper
import com.intellij.openapi.vcs.changes.ChangesTreeEditorDiffPreview
import com.intellij.openapi.vcs.changes.EditorTabDiffPreview
import com.intellij.openapi.vcs.changes.actions.diff.WrapperCombinedBlockProducer
import com.intellij.openapi.vcs.changes.actions.diff.prepareCombinedBlocksFromWrappers
import com.intellij.ui.ExpandableItemsHandler
import com.intellij.util.EditSourceOnDoubleClickHandler
import com.intellij.util.Processor
import com.intellij.util.containers.JBIterable
import com.intellij.util.ui.UIUtil
import com.intellij.util.ui.tree.TreeUtil
@@ -24,6 +34,7 @@ import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.CalledInAny
import java.awt.event.FocusAdapter
import java.awt.event.FocusEvent
import java.awt.event.MouseEvent
import java.beans.PropertyChangeListener
import javax.swing.JComponent
import javax.swing.JTree
@@ -191,12 +202,64 @@ open class TreeHandlerChangesTreeTracker(
abstract class TreeHandlerEditorDiffPreview(
tree: ChangesTree,
protected val tree: ChangesTree,
targetComponent: JComponent = tree,
protected val handler: ChangesTreeDiffPreviewHandler
) : ChangesTreeEditorDiffPreview(tree, targetComponent) {
) : EditorTabDiffPreview(tree.project) {
constructor(tree: ChangesTree, handler: ChangesTreeDiffPreviewHandler) : this(tree, tree, handler)
init {
tree.doubleClickHandler = Processor { e -> handleDoubleClick(e) }
tree.enterKeyHandler = Processor { _ -> handleEnterKey() }
tree.addSelectionListener { handleSingleClick() }
PreviewOnNextDiffAction().registerCustomShortcutSet(targetComponent, this)
UIUtil.putClientProperty(tree, ExpandableItemsHandler.IGNORE_ITEM_SELECTION, true)
}
protected open fun isPreviewOnDoubleClick(): Boolean = true
open fun handleDoubleClick(e: MouseEvent): Boolean {
if (!isPreviewOnDoubleClick()) return false
if (EditSourceOnDoubleClickHandler.isToggleEvent(tree, e)) return false
return performDiffAction()
}
protected open fun isPreviewOnEnter(): Boolean = true
open fun handleEnterKey(): Boolean {
if (!isPreviewOnEnter()) return false
return performDiffAction()
}
protected open fun isOpenPreviewWithSingleClickEnabled(): Boolean = false
protected open fun isOpenPreviewWithSingleClick(): Boolean {
if (!isOpenPreviewWithSingleClickEnabled()) return false
if (!VcsConfiguration.getInstance(project).LOCAL_CHANGES_DETAILS_PREVIEW_SHOWN) return false
return true
}
protected open fun handleSingleClick() {
if (!isOpenPreviewWithSingleClick()) return
val opened = openPreview(false)
if (!opened) {
closePreview() // auto-close editor tab if nothing to preview
}
}
protected open fun isOpenPreviewWithNextDiffShortcut(): Boolean = true
protected open fun handleNextDiffShortcut() {
if (!isOpenPreviewWithNextDiffShortcut()) return
openPreview(true)
}
override fun handleEscapeKey() {
closePreview()
getApplication().invokeLater {
returnFocusToTree()
}
}
protected open fun returnFocusToTree() = Unit
final override fun hasContent(): Boolean {
return handler.hasContent(tree)
}
@@ -223,6 +286,22 @@ abstract class TreeHandlerEditorDiffPreview(
@CalledInAny
abstract fun getEditorTabName(wrapper: Wrapper?): String?
private inner class PreviewOnNextDiffAction : DumbAwareAction() {
init {
copyShortcutFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_NEXT_DIFF))
}
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
override fun update(e: AnActionEvent) {
e.presentation.isEnabledAndVisible = isOpenPreviewWithNextDiffShortcut()
}
override fun actionPerformed(e: AnActionEvent) {
handleNextDiffShortcut()
}
}
companion object {
fun createDefaultViewer(changesTree: ChangesTree, previewHandler: ChangesTreeDiffPreviewHandler, place: String): DiffEditorViewer {
val processor = if (CombinedDiffRegistry.isEnabled()) {
@@ -0,0 +1,45 @@
// 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.vcs.changes
import com.intellij.openapi.components.Service
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.changes.ChangesViewId
import com.intellij.openapi.vcs.changes.ChangesViewSplitComponentBinding
import com.intellij.openapi.vcs.changes.CommitChangesViewWithToolbarPanel
import com.intellij.openapi.vcs.changes.LocalChangesListView
import com.intellij.platform.kernel.ids.storeValueGlobally
import com.intellij.platform.vcs.impl.shared.RdLocalChanges
import com.intellij.ui.split.createComponent
import com.intellij.vcs.changes.viewModel.BackendCommitChangesViewModel
import com.intellij.vcs.changes.viewModel.BackendLocalCommitChangesViewModel
import com.intellij.vcs.changes.viewModel.BackendRemoteCommitChangesViewModel
import kotlinx.coroutines.CoroutineScope
import javax.swing.JComponent
internal class BackendChangesView private constructor(
val changesPanel: JComponent,
val viewModel: BackendCommitChangesViewModel,
) {
companion object {
@JvmStatic
fun create(project: Project, scope: CoroutineScope): BackendChangesView {
if (RdLocalChanges.isEnabled()) {
val viewModel = BackendRemoteCommitChangesViewModel(project)
val id = storeValueGlobally(scope, viewModel, BackendChangesViewValueIdType)
val panel = ChangesViewSplitComponentBinding.createComponent(project, scope, id)
return BackendChangesView(panel, viewModel)
}
else {
val panel = CommitChangesViewWithToolbarPanel(LocalChangesListView(project), scope)
val viewModel = BackendLocalCommitChangesViewModel(panel)
val backendChangesView = BackendChangesView(panel, viewModel)
val id = storeValueGlobally(scope, viewModel, BackendChangesViewValueIdType)
panel.id = id
return backendChangesView
}
}
}
@Service(Service.Level.PROJECT)
internal class ScopeProvider(val cs: CoroutineScope)
}

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