diff --git a/.idea/libraries/kotlinx_serialization_cbor.xml b/.idea/libraries/kotlinx_serialization_cbor.xml index ad2f6d9edd8e..ea92d0da61cb 100644 --- a/.idea/libraries/kotlinx_serialization_cbor.xml +++ b/.idea/libraries/kotlinx_serialization_cbor.xml @@ -1,18 +1,18 @@ - + - - 604c4130ca7a0e979a449cf550087a5c2c586485e8af01dcf24b03b7b287306d + + e45f32f0d80e1164593cfcf9f1907ae39871084c6aa08e49acc2e8e22e1d93ce - + - + \ No newline at end of file diff --git a/.idea/libraries/kotlinx_serialization_core.xml b/.idea/libraries/kotlinx_serialization_core.xml index 2ef10996c861..4691d70eaafe 100644 --- a/.idea/libraries/kotlinx_serialization_core.xml +++ b/.idea/libraries/kotlinx_serialization_core.xml @@ -1,18 +1,18 @@ - + - - 3565b6d4d789bf70683c45566944287fc1d8dc75c23d98bd87d01059cc76f2b3 + + 1f0afa172110e45a7231ef1b44ae8fd84c1ebaff96f3fc3ad68ef8c48120b59c - + - + \ No newline at end of file diff --git a/.idea/libraries/kotlinx_serialization_json.xml b/.idea/libraries/kotlinx_serialization_json.xml index eb577b4dca44..c1645e98c530 100644 --- a/.idea/libraries/kotlinx_serialization_json.xml +++ b/.idea/libraries/kotlinx_serialization_json.xml @@ -1,18 +1,18 @@ - + - - 8769e5647557e3700919c32d508f5c5dad53c5d8234cd10846354fbcff14aa24 + + d94cc34cae39246a1af74fda63f9c4812ce12216ef641d5fa3bbbb539a6922d8 - + - + \ No newline at end of file diff --git a/.idea/libraries/kotlinx_serialization_json_io.xml b/.idea/libraries/kotlinx_serialization_json_io.xml index cf3eaa57a974..4ea1fe885b08 100644 --- a/.idea/libraries/kotlinx_serialization_json_io.xml +++ b/.idea/libraries/kotlinx_serialization_json_io.xml @@ -1,18 +1,18 @@ - + - - ab5ec8ea48815197c46a7df9f418a62791fe91cda5023b941ce4de872d810cc1 + + 98abb132b03e11b80318d45350ec9ebbb5453d26505e81331bf2eea7143b6fbe - + - + \ No newline at end of file diff --git a/.idea/libraries/kotlinx_serialization_protobuf.xml b/.idea/libraries/kotlinx_serialization_protobuf.xml index 609d78da90eb..57063bccd172 100644 --- a/.idea/libraries/kotlinx_serialization_protobuf.xml +++ b/.idea/libraries/kotlinx_serialization_protobuf.xml @@ -1,18 +1,18 @@ - + - - fbd174f72ea82364baa112069f2d7b361758f6b68bd81d2ac51b02c66a48e924 + + 555035205ad1a21254b49fd6b5ddbe6ad1abb5c7baba244258bd758f80687bca - + - + \ No newline at end of file diff --git a/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/impl/DeltaImpl.java b/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/impl/DeltaImpl.java index a9abb217c465..a7cddeae8499 100644 --- a/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/impl/DeltaImpl.java +++ b/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/impl/DeltaImpl.java @@ -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 myBaseSources; private final Set myDeletedSources; - public DeltaImpl(Iterable baseSources, Iterable 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 baseSources, Iterable deletedSources, IndexFactory indexFactory) { + super(Containers.MEMORY_CONTAINER_FACTORY, indexFactory); myBaseSources = Collections.unmodifiableSet(baseSources instanceof Set? (Set)baseSources : Iterators.collect(baseSources, new HashSet<>())); myDeletedSources = Collections.unmodifiableSet(deletedSources instanceof Set? (Set)deletedSources : Iterators.collect(deletedSources, new HashSet<>())); } diff --git a/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/impl/DependencyGraphImpl.java b/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/impl/DependencyGraphImpl.java index e53b07563a5c..cb8554d60d0a 100644 --- a/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/impl/DependencyGraphImpl.java +++ b/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/impl/DependencyGraphImpl.java @@ -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 ourDifferentiateStrategies = List.of(new KotlinSourceOnlyDifferentiateStrategy(), new GeneralJvmDifferentiateStrategy()); - private final Set 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 compiledSources, Iterable deletedSources, boolean isSourceOnly) { - Delta delta = isSourceOnly? new SourceOnlyDelta(myRegisteredIndices, compiledSources, deletedSources) : new DeltaImpl(compiledSources, deletedSources); - - Set 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 diff --git a/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/impl/GraphImpl.java b/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/impl/GraphImpl.java index 57c9e628cedc..21c78f7c51de 100644 --- a/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/impl/GraphImpl.java +++ b/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/impl/GraphImpl.java @@ -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 myNodeToSourcesMap; protected final MultiMaplet> mySourceToNodesMap; private final MapletFactory myContainerFactory; + private final IndexFactory myIndexFactory; + private static final Set ourMandatoryIndexNames = Set.of( + NodeDependenciesIndex.NAME, SubclassesIndex.NAME, TypealiasesIndex.NAME + ); - protected GraphImpl(@NotNull MapletFactory cFactory) { + public interface IndexFactory { + Iterable 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... 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 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 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 diff --git a/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/kotlin/LookupsIndex.java b/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/kotlin/LookupsIndex.java new file mode 100644 index 000000000000..ef14f2096dff --- /dev/null +++ b/build/jvm-rules/dependency-graph/src/org/jetbrains/jps/dependency/kotlin/LookupsIndex.java @@ -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 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); + }); + } +} \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/BuildContext.java b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/BuildContext.java index cd5fc2177379..6c33a82a96e9 100644 --- a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/BuildContext.java +++ b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/BuildContext.java @@ -36,6 +36,9 @@ public interface BuildContext extends DiagnosticSink { @Nullable Path getAbiOutputZip(); + @Nullable + Path getKotlinCriStoragePath(); + NodeSourceSnapshot getSources(); NodeSourceSnapshot getBinaryDependencies(); diff --git a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/DataPaths.java b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/DataPaths.java index 8e14fce15df7..aa70a4f6ec2c 100644 --- a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/DataPaths.java +++ b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/DataPaths.java @@ -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"; diff --git a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/StorageManager.java b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/StorageManager.java index 930c3ab9e5ae..0e8a79dfdc10 100644 --- a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/StorageManager.java +++ b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/StorageManager.java @@ -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) { diff --git a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/BuildContextImpl.java b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/BuildContextImpl.java index fcdeb67c9bd2..c4c6ad73610d 100644 --- a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/BuildContextImpl.java +++ b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/BuildContextImpl.java @@ -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; diff --git a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/KotlinCriUtil.kt b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/KotlinCriUtil.kt new file mode 100644 index 000000000000..fa159929826a --- /dev/null +++ b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/KotlinCriUtil.kt @@ -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 { + val serializedDataResult = mutableMapOf() + + // 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() + 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): Int { + return fileIdToPathEntryAccumulator.computeIfAbsent(this) { fileIdToPathEntryAccumulator.size + 1 } +} + +private fun DataOutputStream.writeVersion() = writeInt(VERSION) \ No newline at end of file diff --git a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/graph/LibraryGraphLoader.java b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/graph/LibraryGraphLoader.java index 4c82ef0676b7..ad271c4a2b10 100644 --- a/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/graph/LibraryGraphLoader.java +++ b/build/jvm-rules/jvm-inc-builder/src/com/intellij/tools/build/bazel/jvmIncBuilder/impl/graph/LibraryGraphLoader.java @@ -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 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)); } } diff --git a/fleet/compiler-plugins/expects/.gitignore b/fleet/compiler-plugins/expects/.gitignore new file mode 100644 index 000000000000..01d8fb504537 --- /dev/null +++ b/fleet/compiler-plugins/expects/.gitignore @@ -0,0 +1,4 @@ +out/ +build/ +.idea/ +.gradle/ \ No newline at end of file diff --git a/java/debugger/impl/resources/META-INF/java-debugger.xml b/java/debugger/impl/resources/META-INF/java-debugger.xml index ccde48254eef..2aac46de309b 100644 --- a/java/debugger/impl/resources/META-INF/java-debugger.xml +++ b/java/debugger/impl/resources/META-INF/java-debugger.xml @@ -108,6 +108,7 @@ + @@ -219,6 +220,8 @@ description="The timeout between the hit run-to-cursor breakpoint in another thread/coroutine and pause the debugger"/> "/> + diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsEx.java b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsEx.java index 43bb22015600..e5791749f403 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsEx.java +++ b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsEx.java @@ -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; } diff --git a/java/java-analysis-impl/resources/META-INF/JavaAnalysisPlugin.xml b/java/java-analysis-impl/resources/META-INF/JavaAnalysisPlugin.xml index 44c2aee521fa..9ec4a1693a12 100644 --- a/java/java-analysis-impl/resources/META-INF/JavaAnalysisPlugin.xml +++ b/java/java-analysis-impl/resources/META-INF/JavaAnalysisPlugin.xml @@ -365,5 +365,8 @@ + + diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CommonDataflow.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CommonDataflow.java index 1f06016f5873..a2e47c769708 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CommonDataflow.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CommonDataflow.java @@ -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 fileMap = + + ConcurrentMap 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); } /** diff --git a/java/java-impl/resources/META-INF/JavaPlugin.xml b/java/java-impl/resources/META-INF/JavaPlugin.xml index 7c865a63cd7e..4a6fe53c3863 100644 --- a/java/java-impl/resources/META-INF/JavaPlugin.xml +++ b/java/java-impl/resources/META-INF/JavaPlugin.xml @@ -678,10 +678,6 @@ implementationClass="com.intellij.codeInsight.hint.NextParameterAfterCompletionHandler"/> - - - - @@ -811,6 +807,10 @@ + + + @@ -2785,12 +2785,19 @@ + + + + diff --git a/java/java-tests/testSrc/com/intellij/java/index/ConcurrentIndexTest.java b/java/java-tests/testSrc/com/intellij/java/index/ConcurrentIndexTest.java index 99ee53127a82..ee6baef97746 100644 --- a/java/java-tests/testSrc/com/intellij/java/index/ConcurrentIndexTest.java +++ b/java/java-tests/testSrc/com/intellij/java/index/ConcurrentIndexTest.java @@ -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); } } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/cache/loader/JpsCompilationOutputLoader.java b/jps/jps-builders/src/org/jetbrains/jps/cache/loader/JpsCompilationOutputLoader.java index 0b1da07254ca..4f2b6eb07d2b 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/cache/loader/JpsCompilationOutputLoader.java +++ b/jps/jps-builders/src/org/jetbrains/jps/cache/loader/JpsCompilationOutputLoader.java @@ -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> 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; diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/dependencies/DependencyResolvingBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/dependencies/DependencyResolvingBuilder.java index c8ff5cf8869c..17a5773131e9 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/dependencies/DependencyResolvingBuilder.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/dependencies/DependencyResolvingBuilder.java @@ -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> futures = ContainerUtil.map(libs, lib -> executorService.submit(() -> resolveAction.accept(lib))); - for (Future future : futures) future.get(); + ConcurrencyUtil.getAll(futures); } finally { executorService.shutdown(); diff --git a/lib/BUILD.bazel b/lib/BUILD.bazel index 5b0c420f3a35..e859bbcc4766 100644 --- a/lib/BUILD.bazel +++ b/lib/BUILD.bazel @@ -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"] ) diff --git a/lib/MODULE.bazel b/lib/MODULE.bazel index b4bd25a7549a..768e01e778db 100644 --- a/lib/MODULE.bazel +++ b/lib/MODULE.bazel @@ -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", diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/PluginVerifier.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/PluginVerifier.kt index 9d5de8eff767..7b3c664cda3f 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/PluginVerifier.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/PluginVerifier.kt @@ -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, diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/BuiltInHelpPlugin.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/BuiltInHelpPlugin.kt index 72f77cda6f3c..fa81b76b53b1 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/BuiltInHelpPlugin.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/BuiltInHelpPlugin.kt @@ -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 = 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, 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.") + } } } } + + diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PlatformModules.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PlatformModules.kt index 58d998924f76..57f50d3a70ce 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PlatformModules.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/impl/PlatformModules.kt @@ -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()) diff --git a/platform/buildView/frontend/src/BuildTreeView.kt b/platform/buildView/frontend/src/BuildTreeView.kt index 35bcea25a629..e8858cac167c 100644 --- a/platform/buildView/frontend/src/BuildTreeView.kt +++ b/platform/buildView/frontend/src/BuildTreeView.kt @@ -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? = 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 { - 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 { + cachedFilteringState = filteringState val result = mutableListOf() - 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 { diff --git a/platform/core-api/src/com/intellij/openapi/application/ThreadingRuntimeFlags.kt b/platform/core-api/src/com/intellij/openapi/application/ThreadingRuntimeFlags.kt index acd46b4d9e59..f68e4b4aaa31 100644 --- a/platform/core-api/src/com/intellij/openapi/application/ThreadingRuntimeFlags.kt +++ b/platform/core-api/src/com/intellij/openapi/application/ThreadingRuntimeFlags.kt @@ -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 diff --git a/platform/core-api/src/com/intellij/openapi/vfs/AsyncFileListener.java b/platform/core-api/src/com/intellij/openapi/vfs/AsyncFileListener.java index 9fb216127607..b96d223a17b1 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/AsyncFileListener.java +++ b/platform/core-api/src/com/intellij/openapi/vfs/AsyncFileListener.java @@ -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 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 diff --git a/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleDiffViewer.java b/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleDiffViewer.java index 5c355d7deaba..1c97b138b75c 100644 --- a/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleDiffViewer.java +++ b/platform/diff-impl/src/com/intellij/diff/tools/simple/SimpleDiffViewer.java @@ -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 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) { diff --git a/platform/eel-impl/src/com/intellij/platform/eel/impl/local/EelLocalExecApi.kt b/platform/eel-impl/src/com/intellij/platform/eel/impl/local/EelLocalExecApi.kt index 286897f93dd9..30af75a17168 100644 --- a/platform/eel-impl/src/com/intellij/platform/eel/impl/local/EelLocalExecApi.kt +++ b/platform/eel-impl/src/com/intellij/platform/eel/impl/local/EelLocalExecApi.kt @@ -64,9 +64,7 @@ class EelLocalExecPosixApi( val (cache, interactive) = when (opts.mode) { EelExecPosixApi.PosixEnvironmentVariablesOptions.Mode.MINIMAL -> { - return service().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> = - service().coroutineScope.async { - EnvironmentUtil.getEnvironmentMap() - } + CompletableDeferred(EnvironmentUtil.getEnvironmentMap()) override suspend fun findExeFilesInPath(binaryName: String): List = findExeFilesInPath(binaryName, LOG) diff --git a/platform/execution.dashboard/src/BackendRunDashboardManagerState.kt b/platform/execution.dashboard/src/BackendRunDashboardManagerState.kt index 0e1f78b1dfd6..79390481c197 100644 --- a/platform/execution.dashboard/src/BackendRunDashboardManagerState.kt +++ b/platform/execution.dashboard/src/BackendRunDashboardManagerState.kt @@ -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 { diff --git a/platform/execution.dashboard/src/RunDashboardManagerImpl.java b/platform/execution.dashboard/src/RunDashboardManagerImpl.java index 4709a3075035..48b13ca46bc2 100644 --- a/platform/execution.dashboard/src/RunDashboardManagerImpl.java +++ b/platform/execution.dashboard/src/RunDashboardManagerImpl.java @@ -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; diff --git a/platform/lang-api/resources/messages/LangBundle.properties b/platform/lang-api/resources/messages/LangBundle.properties index b4216b9e92a1..c8b8ed174df4 100644 --- a/platform/lang-api/resources/messages/LangBundle.properties +++ b/platform/lang-api/resources/messages/LangBundle.properties @@ -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 diff --git a/platform/lang-impl/api-dump-unreviewed.txt b/platform/lang-impl/api-dump-unreviewed.txt index 8102b1d5025a..7a29d041fb45 100644 --- a/platform/lang-impl/api-dump-unreviewed.txt +++ b/platform/lang-impl/api-dump-unreviewed.txt @@ -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 -- (java.lang.String,java.lang.String,java.lang.String,javax.swing.Icon,java.lang.Integer,java.lang.String):V -- b:(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 diff --git a/platform/lang-impl/api-dump.txt b/platform/lang-impl/api-dump.txt index cb04f24e4cc3..7767ce391d00 100644 --- a/platform/lang-impl/api-dump.txt +++ b/platform/lang-impl/api-dump.txt @@ -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 +- (java.lang.String,java.lang.String,java.lang.String,javax.swing.Icon,java.lang.Integer,java.lang.String,Z):V +- b:(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 diff --git a/platform/lang-impl/src/com/intellij/application/options/editor/EditorAppearanceConfigurable.kt b/platform/lang-impl/src/com/intellij/application/options/editor/EditorAppearanceConfigurable.kt index 95f6b59f174e..8369c04f9bb6 100644 --- a/platform/lang-impl/src/com/intellij/application/options/editor/EditorAppearanceConfigurable.kt +++ b/platform/lang-impl/src/com/intellij/application/options/editor/EditorAppearanceConfigurable.kt @@ -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( +@ApiStatus.Internal +class EditorAppearanceConfigurable : BoundCompositeSearchableConfigurable( ApplicationBundle.message("tab.editor.settings.appearance"), "reference.settingsdialog.IDE.editor.appearance", "editor.preferences.appearance" diff --git a/platform/lang-impl/src/com/intellij/application/options/editor/EditorAppearanceConfigurableEP.java b/platform/lang-impl/src/com/intellij/application/options/editor/EditorAppearanceConfigurableEP.java index d21b4db77425..0a9a5c9f17ec 100644 --- a/platform/lang-impl/src/com/intellij/application/options/editor/EditorAppearanceConfigurableEP.java +++ b/platform/lang-impl/src/com/intellij/application/options/editor/EditorAppearanceConfigurableEP.java @@ -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; *

* A new instance of the specified class will be created each time then the Settings dialog is opened */ -final class EditorAppearanceConfigurableEP extends ConfigurableEP { +@ApiStatus.Internal +public final class EditorAppearanceConfigurableEP extends ConfigurableEP { } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/model/ZeroWidthCodeVisionEntry.kt b/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/model/ZeroWidthCodeVisionEntry.kt new file mode 100644 index 000000000000..4b02c0eb7c39 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/model/ZeroWidthCodeVisionEntry.kt @@ -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 + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/model/ZombieCodeVisionEntry.kt b/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/model/ZombieCodeVisionEntry.kt index 5f1889b2b847..594d97ab26b1 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/model/ZombieCodeVisionEntry.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/model/ZombieCodeVisionEntry.kt @@ -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')" diff --git a/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/renderers/painters/CodeVisionListPainter.kt b/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/renderers/painters/CodeVisionListPainter.kt index df138acc4710..3303ee5bd050 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/renderers/painters/CodeVisionListPainter.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/renderers/painters/CodeVisionListPainter.kt @@ -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 } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/renderers/painters/ICodeVisionEntryBasePainter.kt b/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/renderers/painters/ICodeVisionEntryBasePainter.kt index c807156c4d8f..4953d516a1f4 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/renderers/painters/ICodeVisionEntryBasePainter.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/renderers/painters/ICodeVisionEntryBasePainter.kt @@ -26,5 +26,5 @@ interface ICodeVisionEntryBasePainter : ICodeVisionPainter { ): Dimension @ApiStatus.Internal - fun shouldBeDelimited(): Boolean = true + fun shouldBeDelimited(entry: T): Boolean = true } \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/renderers/providers/CodeVisionPainterProviders.kt b/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/renderers/providers/CodeVisionPainterProviders.kt index 7789785a58c6..09dd299d048a 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/renderers/providers/CodeVisionPainterProviders.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/codeVision/ui/renderers/providers/CodeVisionPainterProviders.kt @@ -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 { + + 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 { return INSTANCE.getPainter(this@painter) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/TrafficLightRenderer.kt b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/TrafficLightRenderer.kt index 0be7b85c4051..a9317132af4d 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/TrafficLightRenderer.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/TrafficLightRenderer.kt @@ -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(languages.size) + val settingMap = LinkedHashMap(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 { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/grave/CodeVisionZombie.kt b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/grave/CodeVisionZombie.kt index dc6267b33717..cf4daa1f8a56 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/grave/CodeVisionZombie.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/grave/CodeVisionZombie.kt @@ -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 { @@ -74,6 +77,7 @@ internal data class CodeVisionLimb( val tooltip: String, val icon: Icon?, val count: Int?, + val shouldBeDelimited: Boolean ) { constructor(pair: Pair) : 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) } } diff --git a/platform/lang-impl/src/com/intellij/execution/filters/TextConsoleBuilderImpl.java b/platform/lang-impl/src/com/intellij/execution/filters/TextConsoleBuilderImpl.java index ffb080dd1eb5..bffdd6799e80 100644 --- a/platform/lang-impl/src/com/intellij/execution/filters/TextConsoleBuilderImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/filters/TextConsoleBuilderImpl.java @@ -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; } diff --git a/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.kt b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.kt index d7f22cddbe77..6810ece05c8a 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.kt +++ b/platform/lang-impl/src/com/intellij/execution/impl/ConsoleViewImpl.kt @@ -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 diff --git a/platform/lang-impl/src/com/intellij/ui/StringComboboxEditor.java b/platform/lang-impl/src/com/intellij/ui/StringComboboxEditor.java index 0309131bb444..b33f15baf7f5 100644 --- a/platform/lang-impl/src/com/intellij/ui/StringComboboxEditor.java +++ b/platform/lang-impl/src/com/intellij/ui/StringComboboxEditor.java @@ -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() { diff --git a/platform/platform-impl/src/com/intellij/accessibility/AccessibilityToolsStateCollector.kt b/platform/platform-impl/src/com/intellij/accessibility/AccessibilityToolsStateCollector.kt new file mode 100644 index 000000000000..b42e40af367f --- /dev/null +++ b/platform/platform-impl/src/com/intellij/accessibility/AccessibilityToolsStateCollector.kt @@ -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("name", "Name of the screen reader tool"), + "Screen reader is running in the system") + private val SCREEN_MAGNIFIER = GROUP.registerEvent( + "screen.magnifier", + EventFields.Enum("name", "Name of the screen magnifier tool"), + "Screen magnifier is running in the system") + private val VOICE_CONTROL = GROUP.registerEvent( + "voice.control", + EventFields.Enum("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 { + val set = mutableSetOf() + + 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 + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java index 9d524a82c3b6..945ceeb1d4e0 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurable.java @@ -259,6 +259,12 @@ public final class PluginManagerConfigurable } } + public void scheduleApply() { + if (myPanel != null) { + myPanel.scheduleApply(); + } + } + @Override public void reset() { if (myPanel != null) { diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurablePanel.java b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurablePanel.java index 89572a21d2cc..db268e82c384 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurablePanel.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerConfigurablePanel.java @@ -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(); diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/marketplace/MarketplaceRequests.kt b/platform/platform-impl/src/com/intellij/ide/plugins/marketplace/MarketplaceRequests.kt index 73dc79bdc6aa..4de0b5d14cb0 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/marketplace/MarketplaceRequests.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/marketplace/MarketplaceRequests.kt @@ -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>() {}) }.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, diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/marketplace/utils/MarketplaceUrls.kt b/platform/platform-impl/src/com/intellij/ide/plugins/marketplace/utils/MarketplaceUrls.kt index b571b21fb17e..98c1e0925b68 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/marketplace/utils/MarketplaceUrls.kt +++ b/platform/platform-impl/src/com/intellij/ide/plugins/marketplace/utils/MarketplaceUrls.kt @@ -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() diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/ThemeListProviderImpl.kt b/platform/platform-impl/src/com/intellij/ide/ui/laf/ThemeListProviderImpl.kt index 5d76bad5ac3f..37f6d08ba3c7 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/ThemeListProviderImpl.kt +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/ThemeListProviderImpl.kt @@ -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) diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionMenu.kt b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionMenu.kt index 715986d965f2..34033b1b40df 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionMenu.kt +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionMenu.kt @@ -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()) { diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/islands/IslandsFeedback.kt b/platform/platform-impl/src/com/intellij/openapi/application/impl/islands/IslandsFeedback.kt index 7a333de2d6f7..08ae96bd65df 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/islands/IslandsFeedback.kt +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/islands/IslandsFeedback.kt @@ -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) } diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/islands/IslandsUICustomization.kt b/platform/platform-impl/src/com/intellij/openapi/application/impl/islands/IslandsUICustomization.kt index d54ad43bdc0f..381d6ab4d731 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/islands/IslandsUICustomization.kt +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/islands/IslandsUICustomization.kt @@ -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 diff --git a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.kt b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.kt index efd3e7ad4dd8..dc80291e4e09 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.kt +++ b/platform/platform-impl/src/com/intellij/openapi/project/impl/ProjectManagerImpl.kt @@ -1136,7 +1136,7 @@ fun CoroutineScope.runInitProjectActivities(project: Project) { (project.serviceAsync() 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) diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InfoAndProgressPanel.kt b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InfoAndProgressPanel.kt index 8a7732e700ee..4fa870660ccd 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InfoAndProgressPanel.kt +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/status/InfoAndProgressPanel.kt @@ -414,9 +414,14 @@ class InfoAndProgressPanel internal constructor( ): BalloonHandler { @Suppress("HardCodedStringLiteral") val htmlContent = htmlBody.replace("\n", "
") + 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) } diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/PluginsTabFactory.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/PluginsTabFactory.java index 0db6298854aa..c0b5756e4eff 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/PluginsTabFactory.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/welcomeScreen/PluginsTabFactory.java @@ -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(); } }); diff --git a/platform/platform-impl/src/com/intellij/ui/EditorComboBoxEditor.java b/platform/platform-impl/src/com/intellij/ui/EditorComboBoxEditor.java index 558aeea4c264..7a1afd887854 100644 --- a/platform/platform-impl/src/com/intellij/ui/EditorComboBoxEditor.java +++ b/platform/platform-impl/src/com/intellij/ui/EditorComboBoxEditor.java @@ -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() { diff --git a/platform/platform-impl/src/com/intellij/ui/WindowMouseListener.java b/platform/platform-impl/src/com/intellij/ui/WindowMouseListener.java index 0307d009aa0c..a1bf14a7c673 100644 --- a/platform/platform-impl/src/com/intellij/ui/WindowMouseListener.java +++ b/platform/platform-impl/src/com/intellij/ui/WindowMouseListener.java @@ -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() { } diff --git a/platform/platform-impl/src/com/intellij/ui/WindowMouseListenerSupport.kt b/platform/platform-impl/src/com/intellij/ui/WindowMouseListenerSupport.kt new file mode 100644 index 000000000000..56a24866b1f5 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/WindowMouseListenerSupport.kt @@ -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 + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/WindowResizeListener.java b/platform/platform-impl/src/com/intellij/ui/WindowResizeListener.java index ae4c6fd96bb4..21edf1d407ae 100644 --- a/platform/platform-impl/src/com/intellij/ui/WindowResizeListener.java +++ b/platform/platform-impl/src/com/intellij/ui/WindowResizeListener.java @@ -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); } diff --git a/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java b/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java index a6b2b9cff182..f9273dff3834 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/AbstractPopup.java @@ -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); + } } diff --git a/platform/platform-impl/src/com/intellij/ui/popup/PopupFactoryImpl.java b/platform/platform-impl/src/com/intellij/ui/popup/PopupFactoryImpl.java index 8787adcb8b5e..3a9fa95d823f 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/PopupFactoryImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/PopupFactoryImpl.java @@ -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); diff --git a/platform/platform-impl/src/com/intellij/ui/popup/list/ListPopupImpl.java b/platform/platform-impl/src/com/intellij/ui/popup/list/ListPopupImpl.java index 5604d7fc941e..7f3d9319bc6d 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/list/ListPopupImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/list/ListPopupImpl.java @@ -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(); diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index 924ee96ee32a..7b00bfa3788e 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -793,6 +793,8 @@ implementationClass="com.intellij.codeInsight.codeVision.ui.renderers.providers.RichTextCodeVisionEntryPainter"/> + diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index 1a389f96a3aa..ed7e402d8699 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -1027,6 +1027,7 @@ + + diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/project/impl/ProjectOpeningTest.kt b/platform/platform-tests/testSrc/com/intellij/openapi/project/impl/ProjectOpeningTest.kt index 67913f05312f..c3bbd00d66fb 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/project/impl/ProjectOpeningTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/openapi/project/impl/ProjectOpeningTest.kt @@ -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() + } } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/impl/PersistentFS_FindFilesTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/impl/PersistentFS_FindFilesTest.java index 25a3b16fe22f..f8512d44c42e 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/impl/PersistentFS_FindFilesTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/impl/PersistentFS_FindFilesTest.java @@ -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> futures = ContainerUtil.map(tasks, task -> pool.submit(task)); - for (Future future : futures) { - future.get(); - } + ConcurrencyUtil.getAll(futures); } finally { pool.shutdown(); diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/persistent/namecache/FileNameCacheMicroBenchmark.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/persistent/namecache/FileNameCacheMicroBenchmark.java index 4b6f6e853875..e272080bc994 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/persistent/namecache/FileNameCacheMicroBenchmark.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/newvfs/persistent/namecache/FileNameCacheMicroBenchmark.java @@ -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; } diff --git a/platform/platform-tests/testSrc/com/intellij/util/concurrency/ThreadContextPropagationTest.kt b/platform/platform-tests/testSrc/com/intellij/util/concurrency/ThreadContextPropagationTest.kt index 2034239610d0..473f8cecd6ef 100644 --- a/platform/platform-tests/testSrc/com/intellij/util/concurrency/ThreadContextPropagationTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/util/concurrency/ThreadContextPropagationTest.kt @@ -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) } } diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 246deafc97e6..0ff96cb3a120 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -35,12 +35,19 @@ actionSystem.playback.typecommand.delay=200 actionSystem.playback.typecommand.delay.description=Delay after typing and shortcut related commands\ \ being executed by the playback runner. actionSystem.playback.useDirectActionCall=true +actionSystem.playback.useDirectActionCall.description=Invoke actions directly instead of dispatching input events during test playback actionSystem.playback.useTypingTargets=true +actionSystem.playback.useTypingTargets.description=Send typing events to typing targets during playback actionSystem.keyGestures.enabled=false +actionSystem.keyGestures.enabled.description=Enable key gesture shortcuts (multi-stroke) handling actionSystem.suspendFocusTransferIfApplicationInactive=true +actionSystem.suspendFocusTransferIfApplicationInactive.description=Suspend focus transfers when the application is inactive actionSystem.getContextByRecentMouseEvent=false +actionSystem.getContextByRecentMouseEvent.description=Get DataContext based on the most recent mouse event actionSystem.secondKeystrokeTimeout=2000 +actionSystem.secondKeystrokeTimeout.description=Timeout in milliseconds to wait for the second keystroke of a multi-stroke shortcut actionSystem.win.suppressAlt=true +actionSystem.win.suppressAlt.description=Suppress native Alt key menu activation on Windows actionSystem.update.actions.async.max.nested.loops=20 actionSystem.update.actions.async.max.nested.loops.description=A maximum number of nested EDT loops @@ -65,7 +72,9 @@ action.toolbar.position.bottom.on.mac=false action.toolbar.position.bottom.on.mac.description=Moves the action toolbar to the bottom of the corresponding component. Mac only! actionSystem.mouseGesturesEnabled=true +actionSystem.mouseGesturesEnabled.description=Enable mouse gesture support for invoking actions actionSystem.assertFocusAccessFromEdt=true +actionSystem.assertFocusAccessFromEdt.description=Assert that focus-related APIs are accessed on the EDT ide.highlighting.mode.essential=false ide.highlighting.mode.essential.description=Enable essential highlighting mode @@ -124,9 +133,9 @@ ide.recent.files.tool.window.list=true ide.recent.files.tool.window.list.description=Shows a list of tool windows in the Recent Files popup ide.new.tool.window.resize.area.height=13 -ide.new.tool.window.resize.area.height.description=Size of tool window header top part [1..26] reserved for resize (while the bottom part is for drag-n-drop) +ide.new.tool.window.resize.area.height.description=Size of the tool window header top part [1..26] reserved for resize (while the bottom part is for drag-n-drop) ide.new.tool.window.start.drag.deadzone=100 -ide.new.tool.window.start.drag.deadzone.description=Minimal distance from pressed point to start drag (and thus to avoid mistaken one) +ide.new.tool.window.start.drag.deadzone.description=Minimal distance from the pressed point to start drag (and thus to avoid mistaken one) ide.switcher.tool.window.list=true ide.switcher.tool.window.list.description=Shows a list of tool windows in the Switcher popup ide.allow.tool.window.tabs.reorder=true @@ -160,9 +169,9 @@ ide.navigate.to.recently.focused.editor.description=Use the last focused editor By default, navigation (e.g., from tool windows) goes to the main IDE window even if you work with editors in a separate window. ide.editor.max.pinned.tab.width=2000 -ide.editor.max.pinned.tab.width.description=Max width for pinned editor tab +ide.editor.max.pinned.tab.width.description=Max width for a pinned editor tab ide.editor.tabs.interactive.pin.button=true -ide.editor.tabs.interactive.pin.button.description=Unpin pinned editor tab when 'pin' button is clicked +ide.editor.tabs.interactive.pin.button.description=Unpin the pinned editor tab when the 'pin' button is clicked ide.editor.tabs.show.fadeout=true ide.editor.tabs.show.fadeout.description=Show fadeout overlay to decorate partially hidden (scrolled aside) tabs ide.editor.tabs.fadeout.width=10 @@ -172,15 +181,15 @@ ide.editor.tabs.visible.in.presentation.mode.description=Keep editor tabs visibl ide.hide.excluded.files=false ide.hide.excluded.files.restartRequired=true -ide.hide.excluded.files.description=Do not show excluded files in Project View and exclude them from VCS +ide.hide.excluded.files.description=Do not show excluded files in the Project View and exclude them from VCS use.tty.for.external.tools=false -use.tty.for.external.tools.description=Use command line with TTY support to run 'External Tools' +use.tty.for.external.tools.description=Use the command line with TTY support to run 'External Tools' ide.appIcon.badge=true ide.appIcon.requestAttention=true ide.appIcon.requestAttention.after.indexing=false -ide.appIcon.requestAttention.after.indexing.description=Whether IDE should request user's attention (e.g., by flashing application icon on taskbar) after the end of project analysis +ide.appIcon.requestAttention.after.indexing.description=Whether IDE should request user's attention (e.g., by flashing the application icon on the taskbar) after the end of project analysis win.request.focus.delay.ms=20 win.request.focus.delay.ms.description=Time to wait (blocking EDT) before trying to activate the IDE window on Windows. The value around \ @@ -191,7 +200,7 @@ ide.index.image.max.size=10 ide.index.image.max.size.description=Max size of an image to index, in megabytes no.window.actions.in.editor=false -no.window.actions.in.editor.description=Disables window actions "Stretch to\u2026" within focused editor as before. +no.window.actions.in.editor.description=Disables window actions "Stretch to\u2026" within the focused editor as before. ide.windowSystem.hScrollChars=5 ide.windowSystem.vScrollChars=5 @@ -199,20 +208,30 @@ ide.windowSystem.autoShowProcessPopup=false ide.windowSystem.showListItemsPopup=true ide.tree.yieldingUiUpdate=true +ide.tree.yieldingUiUpdate.description=Perform heavy tree UI updates in yielding mode to keep the UI responsive at the cost of longer updates ide.tree.showBusyIndicator=true +ide.tree.showBusyIndicator.description=Show a busy/loading indicator in tree controls while data is being loaded or updated ide.tree.waitForReadyTimeout=250 +ide.tree.waitForReadyTimeout.description=Maximum time in milliseconds to wait for a tree to become ready before proceeding ide.tree.clearOnHideTime.description=Time in milliseconds when the hidden project view tree will be deactivated. ide.tree.autoscrollToVCenter=false +ide.tree.autoscrollToVCenter.description=Automatically scroll the selected tree node to the vertical center of the visible area ide.tree.horizontal.default.autoscrolling=true ide.tree.horizontal.default.autoscrolling.description=Allow automatic horizontal scroll of selected tree nodes to visible area in tree views ide.tree.ensureSelectionOnFocusGained=true +ide.tree.ensureSelectionOnFocusGained.description=Ensure that a tree has a valid selection when it gains focus ide.tree.autoExpandMaxDepth=5 +ide.tree.autoExpandMaxDepth.description=Maximum depth of nodes to auto-expand in trees ide.tree.expandRecursionDepth=50 +ide.tree.expandRecursionDepth.description=Maximum recursion depth when expanding tree nodes programmatically ide.tree.checkStructure=false +ide.tree.checkStructure.description=Enable additional consistency checks for tree structure updates (may impact performance) ide.tree.uiLockAttempt=250 +ide.tree.uiLockAttempt.description=Maximum time in milliseconds to attempt to acquire the UI lock for tree updates before yielding ide.tree.deferred.icon.invalidates.cache=true +ide.tree.deferred.icon.invalidates.cache.description=Whether a change in a deferred icon should invalidate the renderer cache for the corresponding tree node ide.tree.expand.on.double.click=[DEFAULT*|ALWAYS|NEVER|NAVIGATABLE] ide.tree.expand.on.double.click.description=Specify the default behavior of expanding/collapsing tree nodes by double-clicking (see ExpandOnDoubleClick enum) ide.tree.additional.rows.count=1 @@ -224,7 +243,7 @@ ide.rich.cancellation.traces.description=Enables detailed stacktrace collection edit.source.on.enter.key.disabled=false edit.source.on.enter.key.disabled.description=Allows preventing navigation on the Enter key pressed edit.source.on.enter.key.non.source.navigation.enabled=false -edit.source.on.enter.key.non.source.navigation.enabled.description=Allows navigation even if navigatable object cannot navigate to the editor +edit.source.on.enter.key.non.source.navigation.enabled.description=Allows navigation even if a navigatable object cannot navigate to the editor ide.bookmark.show.all.breakpoints=false ide.bookmark.show.all.breakpoints.description=Shows every breakpoint in the Bookmarks tool window @@ -236,12 +255,12 @@ ide.bookmark.mnemonic.chooser.cyclic.scrolling.allowed=true ide.bookmark.mnemonic.chooser.cyclic.scrolling.allowed.description=Allows cyclic scrolling in the Mnemonic Chooser ide.bookmark.mnemonic.chooser.always.above.gutter=false -ide.bookmark.mnemonic.chooser.always.above.gutter.description=Always shows above editor's gutter if possible +ide.bookmark.mnemonic.chooser.always.above.gutter.description=Always shows the above editor's gutter if possible ide.tree.ui.cyclic.scrolling.allowed=false ide.tree.ui.cyclic.scrolling.allowed.description=Enables cyclic scrolling in trees if the UISettings.getCycleScrolling is enabled ide.tree.painter.classic.compact=false -ide.tree.painter.classic.compact.description=Enables the compact version of classic painter for tree control +ide.tree.painter.classic.compact.description=Enables the compact version of the classic painter for tree control ide.tree.painter.compact.default=false ide.tree.painter.compact.default.description=Enables the default version of compact painter for tree control @@ -253,7 +272,7 @@ ide.tree.ui.async.batch.size.description=Number of virtual files to process in o ide.tree.experimental.layout.cache=true ide.tree.experimental.layout.cache.restartRequired=true -ide.tree.experimental.layout.cache.description=Use experimental tree layout cache for all trees +ide.tree.experimental.layout.cache.description=Use the experimental tree layout cache for all trees ide.tree.experimental.layout.cache.debug=false ide.tree.experimental.layout.cache.debug.description=Enable debugging (slow!) for DefaultTreeLayoutCache @@ -272,18 +291,26 @@ ide.main.menu.expand.horizontal=true ide.main.menu.expand.horizontal.description=Expand the hamburger menu horizontally (Windows and Linux only) ide.macos.main.menu.alignment.options=[Native*|Aligned|Aligned in group|No icons] -ide.macos.main.menu.alignment.options.description="Native" - align menu items as macOS does, \ - "Aligned" - align menu items vertically inside a group, \ - "No icons" - hide icons +ide.macos.main.menu.alignment.options.description="Native" \u2013 align menu items as macOS does, \ + "Aligned" \u2013 align menu items vertically inside a group, \ + "No icons" \u2013 hide icons ide.splitter.mouseZone=6 +ide.splitter.mouseZone.description=The size in pixels of the mouse resize zone around splitters ide.splitter.chooser.info.panel.height=150 +ide.splitter.chooser.info.panel.height.description=Specifies the height of the Splitter chooser info panel in pixels ide.splitter.chooser.info.panel.width=250 +ide.splitter.chooser.info.panel.width.description=Specifies the width of the Splitter chooser info panel in pixels ide.splitter.chooser.info.panel.arc=10 +ide.splitter.chooser.info.panel.arc.description=Specifies the corner arc radius (in pixels) for the info panel\u2019s rounded corners ide.splitter.chooser.info.panel.arrows.shift.center=-20 +ide.splitter.chooser.info.panel.arrows.shift.center.description=Specifies the offset of the arrows from the panel center in pixels ide.splitter.chooser.info.panel.arrows.shift.vertical=20 +ide.splitter.chooser.info.panel.arrows.shift.vertical.description=Specifies the additional vertical offset for arrows inside the info panel in pixels ide.splitter.chooser.info.panel.arrows.shift.horizontal=30 +ide.splitter.chooser.info.panel.arrows.shift.horizontal.description=Specifies the additional horizontal offset for arrows inside the info panel in pixels ide.splitter.chooser.info.panel.text.shift=50 +ide.splitter.chooser.info.panel.text.shift.description=Specifies the offset of the text inside the info panel in pixels ide.autoscroll.to.source.delay=100 ide.autoscroll.to.source.delay.description=Delay before performing the AutoscrollToSource action @@ -307,13 +334,13 @@ ide.scroll.thumb.small.if.opaque=false ide.scroll.thumb.small.if.opaque.description=Allows tuning an opaque thumb thickness. Affects Windows and Linux only. ide.onboarding.background.enabled=true -ide.onboarding.background.enabled.description=Enable background images for License, Import Settings and Update IDE screens dialogs. +ide.onboarding.background.enabled.description=Enable background images for License, Import Settings, and Update IDE screens dialogs. ide.update.dialog.new.ui.enabled=false ide.update.dialog.new.ui.enabled.description=Enable the new UI for IDE Update Dialog ide.onboarding.background.use.long.timeout=true -ide.onboarding.background.use.long.timeout.description=If true, long timeout (30 sec) for background images for License and Import Settings screens dialogs is used +ide.onboarding.background.use.long.timeout.description=If true, the long timeout (30 sec) for background images for License and Import Settings screens dialogs is used mac.scroll.horizontal.gap=true mac.scroll.horizontal.gap.description=Shows additional space after the last row of a list or a tree. Affects macOS only. @@ -328,25 +355,33 @@ ide.preferred.scrollable.viewport.fixed.height=0 ide.preferred.scrollable.viewport.fixed.height.description=Specifies default row height for an empty list ide.preferred.scrollable.viewport.visible.rows=10 -ide.preferred.scrollable.viewport.visible.rows.description=Specifies default number of visible rows in a list +ide.preferred.scrollable.viewport.visible.rows.description=Specifies the default number of visible rows in a list ide.preferred.scrollable.viewport.extra.space=true ide.preferred.scrollable.viewport.extra.space.description=Adds an extra space to show that a list can be scrolled ide.suvorov.progress.kind=[None|Bar|Spinning|Overlay|NiceOverlay*] -ide.suvorov.progress.kind.description=Kind of progress that is used to make an impression of live IDE when it is freezed on acquisition of locks +ide.suvorov.progress.kind.description=Kind of progress that is used to make an impression of a live IDE when it is freezed on acquisition of locks ide.suvorov.progress.showing.delay.ms=1000 -ide.suvorov.progress.showing.delay.ms.description=Defines delay until the UI component of progress indicator during a UI freeze starts being visible +ide.suvorov.progress.showing.delay.ms.description=Defines delay until the UI component of the progress indicator during a UI freeze starts being visible ide.tooltip.animationCycle=150 +ide.tooltip.animationCycle.description=Duration in milliseconds for one animation cycle of tooltip animations. ide.tooltip.initialDelay=1200 +ide.tooltip.initialDelay.description=Delay in milliseconds before showing a tooltip after the pointer rests on a component. ide.tooltip.initialDelay.highlighter=150 +ide.tooltip.initialDelay.highlighter.description=Delay in milliseconds before showing editor highlighter tooltips. ide.tooltip.reshowDelay=500 +ide.tooltip.reshowDelay.description=Minimum time in milliseconds before a tooltip can be shown again after it was hidden. ide.tooltip.dismissDelay=4000 +ide.tooltip.dismissDelay.description=Time in milliseconds after which a visible tooltip is automatically hidden if the mouse stays idle. ide.tooltip.initialReshowDelay=500 +ide.tooltip.initialReshowDelay.description=Delay in milliseconds before showing a tooltip when moving within the same component. ide.tooltip.autoDismissDeadZone=300 +ide.tooltip.autoDismissDeadZone.description=Grace-period in milliseconds around auto\u2011dismiss to reduce flicker when the pointer is near the tooltip. ide.tooltip.showAllSeverities=false +ide.tooltip.showAllSeverities.description=Show all inspection severities in editor tooltips instead of only the most relevant. ide.disable.editor.tooltips=false ide.disable.editor.tooltips.description=Prevents tooltips displayed in the editor (provided by inspections etc.) ide.balloon.shadowEnabled=true @@ -369,10 +404,10 @@ gutter.tooltip.single.link.description=Shows a single link for all classes in a ide.helptooltip.regular.dismissDelay=10000 ide.helptooltip.full.dismissDelay=30000 ide.hide.expandable.tooltip.owner.mouse.exit=true -ide.hide.expandable.tooltip.owner.mouse.exit.description=Provides autohiding for expandable tooltip when mouse leaves visible area of 'owner' component +ide.hide.expandable.tooltip.owner.mouse.exit.description=Provides autohiding for expandable tooltip when the mouse leaves the visible area of the 'owner' component ide.expansion.hints.enabled=true -ide.expansion.hints.enabled.description=Enables expansion hints for lists, trees, and tables to show invisible part of a cell renderer. +ide.expansion.hints.enabled.description=Enables expansion hints for lists, trees, and tables to show the invisible part of a cell renderer. ide.browser.jcef.enabled=true ide.browser.jcef.enabled.restartRequired=true @@ -391,14 +426,14 @@ ide.browser.jcef.markdownView.osr.enabled.description=Whether the Markdown view ide.browser.jcef.codeWithMe.osr.enabled=false ide.browser.jcef.codeWithMe.osr.enabled.description=Whether Code With Me should be displayed in the off-screen mode. ide.browser.jcef.codeWithMe.inBrowserScreenSharing.enabled=true -ide.browser.jcef.codeWithMe.inBrowserScreenSharing.enabled.description=Start screen sharing in external browser. +ide.browser.jcef.codeWithMe.inBrowserScreenSharing.enabled.description=Start screen sharing in an external browser. ide.browser.jcef.headless.enabled=false ide.browser.jcef.headless.enabled.description=Enable JCEF browser in the IDE headless mode ide.browser.jcef.testMode.enabled=false ide.browser.jcef.testMode.enabled.description=Allows for JBCefApp.getInstance() to re-initialize on a subsequent call. ide.browser.jcef.debug.port=-1 ide.browser.jcef.debug.port.restartRequired=true -ide.browser.jcef.debug.port.description=The port number at which remote DevTools will be available(0 - to select a random) +ide.browser.jcef.debug.port.description=The port number at which remote DevTools will be available (0 \u2013 to select a random) ide.browser.jcef.preinit=false ide.browser.jcef.preinit.restartRequired=true ide.browser.jcef.preinit.description=Force JCEF init at IDE start-up to support co-existence with JavaFX (affects macOS only) @@ -406,7 +441,7 @@ ide.browser.jcef.jsQueryPoolSize=0 ide.browser.jcef.jsQueryPoolSize.description=Define JBCefJSQuery pool size used after browser creation ide.browser.jcef.gpu.disable=false ide.browser.jcef.gpu.disable.restartRequired=true -ide.browser.jcef.gpu.disable.description=Disables GPU hardware acceleration in JCEF. If software renderer is not in place, then the GPU process won't launch. +ide.browser.jcef.gpu.disable.description=Disables GPU hardware acceleration in JCEF. If a software renderer is not in place, then the GPU process won't launch. ide.browser.jcef.gpu.infinitecrash=true ide.browser.jcef.gpu.infinitecrash.restartRequired=true ide.browser.jcef.gpu.infinitecrash.description=Disables crash limit for a GPU process. If it crashes too often, then there will be shown a notification (which recommends disabling GPU). @@ -450,14 +485,14 @@ editor.focus.mode.color.dark=404040 editor.focus.mode.color.dark.description=Dark color for focus mode editor.position.mouse.cursor.on.doubleclicked.tab=false -editor.position.mouse.cursor.on.doubleclicked.tab.description=Position mouse cursor on editor tab when the tab was double-clicked \ +editor.position.mouse.cursor.on.doubleclicked.tab.description=Position the mouse cursor on the editor tab when the tab was double-clicked \ to perform 'Maximize Editor' or 'Hide All Tool Windows' actions editor.max.caret.count=1000 editor.max.caret.count.description=Maximum number of carets that can be created in the editor (performance-related limit) editor.block.caret.selection.vim-like=false -editor.block.caret.selection.vim-like.description=On mouse-drag selection in editor with block caret, always include character under caret \ +editor.block.caret.selection.vim-like.description=On mouse-drag selection in the editor with a block caret, always include character under caret \ in selection editor.caret.update.primary.selection=true @@ -467,16 +502,16 @@ editor.navigation.history.stack.size=150 editor.navigation.history.stack.size.description=Stack size limit for back/forward and last/next edit location navigation editor.reformat.on.typing=false -editor.reformat.on.typing.description=Enable the auto formatting typed handler +editor.reformat.on.typing.description=Enable the auto-formatting typed handler editor.move.semicolon.after.paren=true -editor.move.semicolon.after.paren.description=Moves semicolon typed before closing paren to the end of statement +editor.move.semicolon.after.paren.description=Moves semicolon typed before closing paren to the end of the statement editor.virtual.lines=5 editor.virtual.lines.description=Number of virtual empty lines displayed at the editor bottom editor.scrolling.animation.interval.ms=100 -editor.scrolling.animation.interval.ms.description=Animation interval (in milliseconds) for scrolling in the editor, when mouse is dragged \ - beyond visible area. The smaller the value, the faster is scrolling. +editor.scrolling.animation.interval.ms.description=Animation interval (in milliseconds) for scrolling in the editor, when the mouse is dragged \ + beyond the visible area. The smaller the value, the faster is scrolling. editor.highlight.selected.text.max.occurrences.threshold=50 editor.highlight.selected.text.max.occurrences.threshold.description=Maximum number of highlights in the editor when selecting text. \ @@ -489,13 +524,13 @@ editor.rd.reopen.editors.on.frontend.description=Enables reopening the editors o consequently, restoring the editor splitters highlight.caret.line.at.custom.fold=false -highlight.caret.line.at.custom.fold.description=Whether caret line highlighting is applied when caret is located at a custom fold region +highlight.caret.line.at.custom.fold.description=Whether caret line highlighting is applied when the caret is located at a custom fold region doc.render.cache.size=0.02 -doc.render.cache.size.description=Maximum fraction of available heap that will be used to store rendered comments (text content) +doc.render.cache.size.description=Maximum fraction of the available heap that will be used to store rendered comments (text content) doc.render.cache.size.restartRequired=true doc.render.image.cache.size=0.04 -doc.render.image.cache.size.description=Maximum fraction of available heap that will be used to store (unpacked) images in rendered comments +doc.render.image.cache.size.description=Maximum fraction of the available heap that will be used to store (unpacked) images in rendered comments doc.render.image.cache.size.restartRequired=true doc.render.adaptive.image=false doc.render.adaptive.image.description=Use the new experimental adaptive image loading @@ -524,9 +559,9 @@ ide.run.blocking.cancellable.assert.in.tests=false ide.run.blocking.cancellable.assert.in.tests.description=Implement assertions within 'runBlockingCancellable' in tests ide.unused.symbol.calculation.maxFilesToSearchUsagesIn=10 -ide.unused.symbol.calculation.maxFilesToSearchUsagesIn.description=Max number of scanned files to find usages of symbol +ide.unused.symbol.calculation.maxFilesToSearchUsagesIn.description=Max number of scanned files to find usages of a symbol ide.unused.symbol.calculation.maxFilesSizeToSearchUsagesIn=524288 -ide.unused.symbol.calculation.maxFilesSizeToSearchUsagesIn.description=Max files size in bytes to find usages of symbol in case there are a few files +ide.unused.symbol.calculation.maxFilesSizeToSearchUsagesIn.description=Max files size in bytes to find usages of a symbol in case there are a few files ide.tabbedPane.dragOutMultiplier=0.1 ide.tabbedPane.dragOutMultiplier.description=A threshold to define if editor tab drag&drop is intentional (ratio of tab size) @@ -534,7 +569,7 @@ ide.tabbedPane.dragToSplitRatio=0.2 ide.tabbedPane.dragToSplitRatio.description=Ratio of editor tabs area to process drag-n-drop as vertical or horizontal split ide.warmup.use.predicates=true -ide.warmup.use.predicates.description=Use predicate-based warmup instead of custom configuration-based one +ide.warmup.use.predicates.description=Use predicate-based warmup instead of a custom configuration-based one file.chooser.async.tree.model=true file.chooser.async.tree.model.description=Use the new implementation based on AsyncTreeModel + FileTreeModel @@ -575,31 +610,39 @@ ide.mac.pressAndHold.workaround=true ide.mac.pressAndHold.workaround.description=Enable workaround for press-and-hold input method for accented characters on macOS ide.mac.pressAndHold.brute.workaround=false ide.mac.pressAndHold.brute.workaround.description=Enable alternative workaround for press-and-hold input method for accented characters on macOS. \ - It might work in more cases, but known to cause issues with Japanese text input. + It might work in more cases, but is known to cause issues with Japanese text input. debugger.valueTooltipAutoShow=true debugger.valueTooltipAutoShow.description=Auto show tooltip on mouse over. debugger.valueTooltipAutoShowOnSelection=false debugger.valueTooltipAutoShowOnSelection.description=Auto show tooltip when editor selection changes debugger.mayBringFrameToFrontOnBreakpoint=true +debugger.mayBringFrameToFrontOnBreakpoint.description=Bring the IDE window to front when a breakpoint is hit debugger.mayBringDebuggeeWindowToFrontAfterResume=true +debugger.mayBringDebuggeeWindowToFrontAfterResume.description=Bring the debuggee window to front after resume # currently enabled only in Rider debugger.mayBringDebuggeeWindowToFrontAfterResume.supported=false -debugger.mayBringDebuggeeWindowToFrontAfterResume.supported.description=State of "Bring debuggee to foreground" feature +debugger.mayBringDebuggeeWindowToFrontAfterResume.supported.description=State of the "Bring debuggee to foreground" feature debugger.batch.evaluation=true +debugger.batch.evaluation.description=Evaluate multiple values in a single batch request to the debuggee debugger.batch.evaluation.force=false +debugger.batch.evaluation.force.description=Force batch evaluation even when individual evaluation is possible debugger.compiling.evaluator.magic.accessor=false +debugger.compiling.evaluator.magic.accessor.description=Allow access to private members via generated accessor methods in the compiling evaluator debugger.compiling.evaluator.reflection.access.with.java8=true +debugger.compiling.evaluator.reflection.access.with.java8.description=Allow reflective access in the compiling evaluator compatible with Java 8 rules debugger.compiling.evaluator.extract.generated.class=true -debugger.compiling.evaluator.extract.generated.class.description=Extract generated class to a separate file \ +debugger.compiling.evaluator.extract.generated.class.description=Extract the generated class to a separate file \ (only for java versions lower than 11) debugger.compiling.evaluator.method.refs=false debugger.compiling.evaluator.method.refs.description=Use the compiling evaluator for the code with method references debugger.watches.in.variables=true +debugger.watches.in.variables.description=Show the Watches node inside the Variables view debugger.auto.fetch.icons=true +debugger.auto.fetch.icons.description=Automatically request icons for objects shown in debugger views when available debugger.close.dialog.on.navigate=true debugger.close.dialog.on.navigate.description=Close the Evaluate dialog on Jump to (type) source action debugger.async.smart.step.into=true @@ -613,13 +656,13 @@ debugger.click.disable.breakpoints.description=Use a single click to disable a b debugger.renderers.file=true debugger.renderers.file.description=Enable file object renderer debugger.renderers.arrays=true -debugger.renderers.arrays.description=Show first elements for primitive and string arrays +debugger.renderers.arrays.description=Show the first elements for primitive and string arrays debugger.renderers.arrays.max.primitives=100 debugger.renderers.arrays.max.primitives.description=Number of primitive values to show debugger.renderers.arrays.max.strings=5 debugger.renderers.arrays.max.strings.description=Number of string values to show debugger.single.smart.step.force=true -debugger.single.smart.step.force.description=Do force step-into on single variant smart step-into +debugger.single.smart.step.force.description=Do force step-into on a single variant smart step-into debugger.method.breakpoints.entry.default=true debugger.method.breakpoints.entry.default.description=Stop only on method entry by default debugger.emulate.method.breakpoints=true @@ -627,9 +670,13 @@ debugger.emulate.method.breakpoints.description=Emulate method breakpoints with debugger.intern.string.literals=false debugger.intern.string.literals.description=Make string literal refer to the same instance of class String debugger.capture.points.agent.debug=false +debugger.capture.points.agent.debug.description=Enable debug logging for capture points agent debugger.collection.breakpoint.agent.debug=false +debugger.collection.breakpoint.agent.debug.description=Enable debug logging for collection breakpoints agent debugger.capture.points.agent.annotations=true +debugger.capture.points.agent.annotations.description=Enable reading capture points from annotations debugger.collection.watchpoints.enabled=false +debugger.collection.watchpoints.enabled.description=Enable collection watchpoints support debugger.async.stacks.coroutines=true debugger.async.stacks.coroutines.description=Enable async stack traces for coroutines debugger.async.stacks.flows=true @@ -643,56 +690,49 @@ debugger.log.async.stacks.description=Log async stack trace when log stack trace debugger.async.stack.trace.for.exceptions.printing=true debugger.async.stack.trace.for.exceptions.printing.description=Include async stack trace in exceptions' printed stack trace debugger.resume.yourkit.threads=true +debugger.resume.yourkit.threads.description=Automatically resume YourKit profiler helper threads when debugging debugger.keep.step.requests=false +debugger.keep.step.requests.description=Keep step requests active after a step completes (for advanced troubleshooting) debugger.enable.memory.view=true +debugger.enable.memory.view.description=Enable the Memory view/tool window in the debugger debugger.enable.overhead.monitor=true +debugger.enable.overhead.monitor.description=Enable the debugger overhead monitor to track evaluation cost debugger.memory.agent.debug=false -debugger.memory.agent.debug.description=Enable debug logging to stdout from memory agent +debugger.memory.agent.debug.description=Enable debug logging to stdout from a memory agent debugger.memory.agent.debug.path= -debugger.memory.agent.debug.path.description=Force the debugger to use the specified memory agent library instead of bundled one (only for debugging purpose). Will be used only if debugger.memory.agent.debug checked +debugger.memory.agent.debug.path.description=Force the debugger to use the specified memory agent library instead of the bundled one (only for debugging purposes). Will be used only if debugger.memory.agent.debug checked debugger.memory.agent.action.timeout=-1 -debugger.kotlin.analysis.api.editor.text.provider=true -debugger.kotlin.analysis.api.editor.text.provider.description=Use Analysis API-based implementation of 'EditorTextProvider' -debugger.kotlin.inline.stack.trace.enabled=true -debugger.kotlin.inline.stack.trace.enabled.description=Enable inline stack trace -debugger.kotlin.show.coroutines.in.threadDumpPanel=true -debugger.kotlin.show.coroutines.in.threadDumpPanel.description=Merge coroutine dump with thread dump in ThreadDumpPanel -debugger.kotlin.auto.show.coroutines.view=false -debugger.kotlin.auto.show.coroutines.view.description=Automatically open coroutines view if stack trace contains coroutine stack frames debugger.memory.agent.action.timeout.description=Set debugger memory agent action timeout in millis debugger.memory.agent.use.in.memory.view=true debugger.memory.agent.use.in.memory.view.description=Use memory agent to estimate retained sizes in the Instances Window -debugger.enable.kotlin.evaluator.in.java.context=false -debugger.enable.kotlin.evaluator.in.java.context.description=Enable Kotlin evaluation features when hitting/suspending in Java frames \ - (suggest Kotlin language in evaluation prompts, conditional breakpoints and "Evaluate and log" field; support Kotlin watches) -debugger.kotlin.multiline.lambda.breakpoints=false -debugger.kotlin.multiline.lambda.breakpoints.description=Allow installing breakpoint on multiline lambdas -debugger.kotlin.report.smart.step.into.targets.detection.failure=true -debugger.kotlin.report.smart.step.into.targets.detection.failure.description=Report an error when smart targets detection in bytecode failed (leads to a failure in tests) -debugger.kotlin.step.through.inline.lambdas=true -debugger.kotlin.step.through.inline.lambdas.description=Step over action steps into inline lambdas (e.g., calls of let/run/synchronized/...) debugger.tree.states.depth=100 +debugger.tree.states.depth.description=Maximum depth for remembering expanded/collapsed state in debugger trees debugger.new.threads.view=false +debugger.new.threads.view.description=Enable the experimental threads view in the debugger debugger.new.tool.window.layout=false debugger.new.tool.window.layout.restartRequired=true -debugger.new.tool.window.layout.description=Enable new debugger UI (works only in the old UI) +debugger.new.tool.window.layout.description=Enable the new debugger UI (works only in the old UI) debugger.new.tool.window.layout.toolbar=[After*|Vertical] -debugger.new.tool.window.layout.toolbar.description=Change toolbar position in new debugger UI +debugger.new.tool.window.layout.toolbar.description=Change toolbar position in the new debugger UI debugger.new.tool.window.layout.dnd=false debugger.new.tool.window.layout.dnd.restartRequired=true debugger.new.tool.window.layout.dnd.description=Enable drag and drop of the debugger tabs in the new debugger UI debugger.new.tool.window.layout.single.content=false debugger.new.tool.window.layout.single.content.description=Collapse the debug toolwindow header if there is only one active session debugger.toolbar.before.tabs=true -debugger.toolbar.before.tabs.description=Show debugger toolbar before tabs +debugger.toolbar.before.tabs.description=Show the debugger toolbar before tabs debugger.default.selected.view.key=Default +debugger.default.selected.view.key.description=Selects which debugger view is shown by default (e.g., Variables, Console) debugger.track.instrumentation=true +debugger.track.instrumentation.description=Track class instrumentation events during debugging debugger.variablesView.rss=true debugger.variablesView.rss.description=Enable node expanding in the Variables view speed search debugger.variablesView.rss.depth=4 -debugger.variablesView.rss.depth.description=Depth of search in variables view +debugger.variablesView.rss.depth.description=Depth of search in the variables view debugger.attach.to.process.action=false +debugger.attach.to.process.action.description=Show the "Attach to Process" action in the run/debug tool window debugger.sa.jdwp.debug=false +debugger.sa.jdwp.debug.description=Enable additional JDWP debug logging for the shared agent debugger.add.rt.jar=true debugger.add.rt.jar.description=Add rt.jar to the classpath debugger.evaluate.single.threaded.timeout=1000 @@ -700,61 +740,61 @@ debugger.evaluate.single.threaded.timeout.description=Number of milliseconds to debugger.evaluate.load.helper.in.separate.classloader=false debugger.evaluate.load.helper.in.separate.classloader.description=Always load helper classes in a new classloader debugger.new.invocation.watcher=true -debugger.new.invocation.watcher.description=When resume all, take into account the debugger model state of contexts and theirs resumed threads +debugger.new.invocation.watcher.description=When resuming all, take into account the debugger model state of contexts and their resumed threads debugger.retry.conditional.breakpoints=true -debugger.retry.conditional.breakpoints.description=Retry to evaluate a condition in conditional breakpoints in case of ObjectCollectedException +debugger.retry.conditional.breakpoints.description=Retry to evaluate a condition in conditional breakpoints in the case of ObjectCollectedException debugger.call.tracing=false +debugger.call.tracing.description=Enable call tracing in the debugger debugger.call.tracing.arguments=true +debugger.call.tracing.arguments.description=Include method arguments in call tracing debugger.renderers.annotations=true +debugger.renderers.annotations.description=Enable rendering values using annotations when available debugger.smart.step.inplace=true +debugger.smart.step.inplace.description=Show smart step into options inline in the editor debugger.field.pin.to.top=true +debugger.field.pin.to.top.description=Pin important fields to the top in the Variables view debugger.jb.jdi=true debugger.jb.jdi.description=Use the new forked JDI implementation debugger.async.jdi=true debugger.async.jdi.description=Use async JDI to speed up JDWP communication debugger.async.frames=true +debugger.async.frames.description=Collect and show asynchronous call frames in stack traces debugger.library.frames.fold.instead.of.hide=true debugger.library.frames.fold.instead.of.hide.description=Fold library frames into a single placeholder instead of completely hiding them debugger.merge.pause.and.resume.description=Merge "Pause" and "Resume" buttons on the toolbar debugger.auto.attach.from.console=false -debugger.auto.attach.from.console.description=Auto attach to all processes available for debug from the console output +debugger.auto.attach.from.console.description=Auto-attach to all processes available for debug from the console output debugger.auto.attach.from.any.console=false -debugger.auto.attach.from.any.console.description=Auto attach to all processes from any console output +debugger.auto.attach.from.any.console.description=Auto-attach to all processes from any console output debugger.preload.event.info=true +debugger.preload.event.info.description=Preload frequently used JDWP event info to reduce latency debugger.preload.types.async=true debugger.preload.types.hierarchy=true debugger.preload.types.hierarchy.description=Request superclass and interfaces for each loaded class in advance to speed up further operations debugger.sync.commands.reschedule.delay=50 debugger.sync.commands.max.retries=5 debugger.attach.dialog.enabled=true -debugger.attach.dialog.enabled.description=Show new "Attach to Process" dialog instead of popup +debugger.attach.dialog.enabled.description=Show the new "Attach to Process" dialog instead of a popup debugger.stack.frame.show.code.index=false -debugger.stack.frame.show.code.index.description=Show bytecode index next to line number for stack frames +debugger.stack.frame.show.code.index.description=Show the bytecode index next to the line number for stack frames debugger.show.breakpoints.inline=true debugger.show.breakpoints.inline.description=Show breakpoints as inline inlays (e.g., before lambdas) debugger.show.breakpoints.inline.even.trivial=false -debugger.show.breakpoints.inline.even.trivial.description=Show inline inlay for line breakpoint even if it's the only breakpoint +debugger.show.breakpoints.inline.even.trivial.description=Show inline inlay for a line breakpoint even if it's the only breakpoint debugger.report.non.default.inline.breakpoint=true debugger.report.non.default.inline.breakpoint.description=Report as an error scenario when a user adds a breakpoint via gutter and then enables another one in the same line debugger.inlayRunToCursor.hover.area=4 -debugger.inlayRunToCursor.hover.area.description=The size of hover area in the editor to show inlay Run-To-Cursor popup (counting in characters) +debugger.inlayRunToCursor.hover.area.description=The size of the hover area in the editor to show the inlay Run-To-Cursor popup (counting in characters) debugger.anti.flickering.delay=200 debugger.anti.flickering.delay.description=Use an anti-flickering panel atop of debugger variable tree and freeze repainting on specified ms \ after stepping. Zero value means do not use an anti-flickering panel. Need to restart debug after setting it to zero and back. debugger.stop.on.graceful.exit=true -debugger.stop.on.graceful.exit.description=Immediately detach debugger when process is about to terminate +debugger.stop.on.graceful.exit.description=Immediately detach the debugger when a process is about to terminate -rider.debugger.enable.collections.visualizers=true -rider.debugger.enable.collections.visualizers.description=Enable a link next to a collection in local variable's pane and in a hint, which let explore the collection in a table view - -execution.java.always.debug=false -execution.java.always.debug.description=Always run java processes with the debug agent debugger.save.and.compare=false debugger.save.and.compare.description=Enable "Save & Compare" feature in the debugger debugger.show.offsets.in.frames=false -debugger.show.offsets.in.frames.description=Show instruction offset in frames view -debugger.log.jdi.in.unit.tests=false -debugger.log.jdi.in.unit.tests.description=Enable full JDI logging under DebugProcessImpl.LOG.debug for unit tests +debugger.show.offsets.in.frames.description=Show instruction offset in the frames view debugger.hotswap.floating.toolbar=true debugger.hotswap.floating.toolbar.description=Show a floating toolbar with a hot swap button on source change @@ -774,16 +814,16 @@ build.toolwindow.show.inline.statistics=true build.toolwindow.show.inline.statistics.description=Show execution time of the build and build steps compiler.process.jdk= -compiler.process.jdk.description=Path to a JDK home. Force build process to be run using the JDK specified. It is assumed that the jdk is at least of version 11. +compiler.process.jdk.description=Path to a JDK home. Force the build process to be run using the JDK specified. It is assumed that the jdk is at least of version 11. compiler.process.preload=true -compiler.process.preload.description=After build finishes, the IDE will preload and leave waiting in memory the build process for the next compilation. This will save time for process start and project model loading (may be noticeable for big projects). +compiler.process.preload.description=After the build finishes, the IDE will preload and leave waiting in memory the build process for the next compilation. This will save time for process start and project model loading (may be noticeable for big projects). compiler.gc.wsl=true compiler.gc.wsl.description=Run Build compiler.process.debug.port= -compiler.process.debug.port.description=Specifies build port at which the build process will wait for debugger connections\n\ +compiler.process.debug.port.description=Specifies the build port at which the build process will wait for debugger connections\n\ if 'Debug Build Process' (available via Find Action) is enabled.\n\ -1 means that a random available port will be chosen. @@ -797,7 +837,7 @@ compiler.automake.build.while.idle.timeout=60000 compiler.automake.build.while.idle.timeout.description=Application inactivity period, after which automatic builds may be triggered, if necessary, for all currently opened projects. Set to negative value to disable the feature. compiler.automake.allow.parallel=true -compiler.automake.allow.parallel.description=The option allows to force automatically started builds to run in single-threaded mode even if 'Compile independent modules in parallel' is on\n\ +compiler.automake.allow.parallel.description=The option allows forcing automatically started builds to run in single-threaded mode even if 'Compile independent modules in parallel' is on\n\ This might help to save CPU resources for the foreground processes. compiler.document.save.enabled=false @@ -809,33 +849,26 @@ compiler.build.data.unused.threshold=30 compiler.build.data.unused.threshold.description=If a project is not opened for the specified number of days, its build data will be cleared to save disk space compiler.build.report.statistics=false -compiler.build.report.statistics.description=Turn on to display builder execution times as 'Information' messages in compiler toolwindow. - -compiler.build.can.use.eel=true -compiler.build.can.use.eel.description=Allow using Eel API to run build processes. - -compiler.external.javac.keep.alive.timeout=300000 -compiler.external.javac.keep.alive.timeout.description=If not used for the specified period of time or longer, the IDE will shut down the \ - cached javac compilation process +compiler.build.report.statistics.description=Turn on to display builder execution times as 'Information' messages in the compiler tool window. compiler.natural.int.multimap.impl=false compiler.natural.int.multimap.impl.description=Experimental implementation for class-to-class dependency multimap in JPS caches.\nIf turned off after being turned on, project rebuild is required. compiler.process.use.portable.caches=false compiler.process.use.portable.caches.restartRequired=true -compiler.process.use.portable.caches.description=Warning: for Intellij project only! \nTurn on to download compilation caches for reducing build time. +compiler.process.use.portable.caches.description=Warning: for Intellij project only! \nTurn on to download compilation caches to reduce build time. lvcs.show.activity.view=true lvcs.show.activity.view.description=Show new ui for the "Local History" lvcs.open.diff.automatically=true -lvcs.open.diff.automatically.description=Open diff tab (if it is configured to be opened in the editor) when "Local History" tab is shown. +lvcs.open.diff.automatically.description=Open the diff tab (if it is configured to be opened in the editor) when the "Local History" tab is shown. vcs.showConsole=true -vcs.showConsole.description=Show 'Console' tab in VCS toolwindow that logs all write-commands performed by IDE. +vcs.showConsole.description=Show the 'Console' tab in the VCS toolwindow that logs all write-commands performed by IDE. vcs.log.bek.sort.disabled=false vcs.log.bek.sort.disabled.description=Disable IntelliSort vcs.log.linear.bek.sort=false -vcs.log.linear.bek.sort.description=Third kind of IntelliSort that simplifies merges and tries to show history as linear +vcs.log.linear.bek.sort.description=A third kind of IntelliSort that simplifies merges and tries to show history as linear vcs.log.recent.commits.count=1000 vcs.log.recent.commits.count.description=The number of recent commits is loaded to be shown quickly (before the full log is loaded, which can take some time) vcs.log.recent.commits.count.restartRequired=true @@ -871,7 +904,7 @@ vcs.log.index.postpone.on.heavy.activity.or.power.save.description=Postpone Vcs vcs.log.index.durable.enumerator=true vcs.log.index.durable.enumerator.description=Use DurableEnumerator in the Vcs Log paths index instead of PersistentBTreeEnumerator. vcs.log.speedsearch=false -vcs.log.speedsearch.description=Enable speedsearch in Log (warning: performance is not excellent) +vcs.log.speedsearch.description=Enable speedsearch in the Log (warning: performance is not excellent) vcs.log.filter.messages.by.hash=true vcs.log.filter.messages.by.hash.description=Find hashes in commit messages when filtering by hash. vcs.log.copy.filters.to.new.tab=true @@ -894,20 +927,20 @@ vcs.log.max.tags.shown.description=Limit for showing tags in the details panel. vcs.log.max.tags.shown.restartRequired=true vcs.log.use.indexed.details=false vcs.log.use.indexed.details.description=Load commit details directly from the index in the EDT thread. \ - Enabling this key allows to load commit information in the Vcs Log instantly, but it may negatively affect table scrolling performance. + Enabling this key allows loading commit information in the Vcs Log instantly, but it may negatively affect table scrolling performance. vcs.log.git.committers.robots= vcs.log.git.committers.robots.description=Non-human git committers. E-mail addresses, comma delimited. vcs.log.git.committers.robots.restartRequired=true vcs.new.history=true -vcs.new.history.description=Enable new file history that uses Log +vcs.new.history.description=Enable the new file history that uses Log vcs.history.refine=true vcs.history.refine.description=Use a refining algorithm on file history that eliminates commits for different files having the same name. vcs.history.remove.trivial.merges=true -vcs.history.remove.trivial.merges.description=Remove trivial merge commits from file history. +vcs.history.remove.trivial.merges.description=Remove trivial merge commits from the file history. vcs.history.use.index=true -vcs.history.use.index.description=Use index to show new file history +vcs.history.use.index.description=Use index to show the new file history vcs.show.affected.files.as.tab=true -vcs.show.affected.files.as.tab.description=Use 'Changes' toolwindow instead of dialogs for 'Show Affected Files' and similar actions +vcs.show.affected.files.as.tab.description=Use the 'Changes' tool window instead of dialogs for 'Show Affected Files' and similar actions vcs.blame.show.affected.files.in.log=true vcs.blame.show.affected.files.in.log.description=Jump to the corresponding commit in the 'Log' view when clicking an entry in the 'Annotate with Git Blame' pane instead of opening a dialog with affected files. @@ -926,12 +959,12 @@ vcs.root.auto.add.nofity=false vcs.root.auto.add.nofity.description=Display a notification that we detected and added some Git roots. vcs.enable.partial.changelists.persist.file.contents=false -vcs.enable.partial.changelists.persist.file.contents.description=Persist file contents on disk to restore partial changes for files modified while IDE is closed +vcs.enable.partial.changelists.persist.file.contents.description=Persist file contents on disk to restore partial changes for files modified while the IDE is closed vcs.enable.partial.changelists.undo=true vcs.enable.partial.changelists.undo.description=Restore original markers state on Undo action vcs.enable.partial.changelists.undo.restartRequired=true vcs.enable.partial.changelists.redo=false -vcs.enable.partial.changelists.redo.description=Restore original markers state on Redo action +vcs.enable.partial.changelists.redo.description=Restore the original markers state on Redo action vcs.enable.partial.changelists.redo.restartRequired=true vcs.force.non.modal.commit=false @@ -941,10 +974,10 @@ vcs.disable.changelists.description=Disable tracking assigned changelists for Lo vcs.non.modal.commit.legend.compact=false vcs.non.modal.commit.legend.compact.description=Use compact commit legend presentation in Local Changes vcs.changes.tree.use.fixed.height.renderer=true -vcs.changes.tree.use.fixed.height.renderer.description=Use faster fixed-height tree renderer for VCS trees +vcs.changes.tree.use.fixed.height.renderer.description=Use a faster fixed-height tree renderer for VCS trees vcs.prefer.checkboxes.over.selection=false -vcs.prefer.checkboxes.over.selection.description=Prefer changes checked with checkboxes over usual selection. \ - Currently, it is only used for Rollback action. +vcs.prefer.checkboxes.over.selection.description=Prefer changes checked with checkboxes over the usual selection. \ + Currently, it is only used for the Rollback action. vcs.unversioned.files.max.intree=1000 vcs.unversioned.files.max.intree.description=Maximum number of unversioned and ignored files displayed in the Local Changes file tree. \ If there are more unversioned or ignored files, they are substituted with a link which opens a dialog. @@ -952,8 +985,8 @@ vcs.code.author.inlay.hints=false vcs.code.author.inlay.hints.description=Show inlay hints with code author (based on vcs annotations) for classes, methods vcs.code.analysis.before.checkin.show.only.new.threshold=-1 -vcs.code.analysis.before.checkin.show.only.new.threshold.description=Show only newly introduced warnings and errors in results \ - of before checkin analysis if the number of changed files is less or equals than the threshold +vcs.code.analysis.before.checkin.show.only.new.threshold.description=Show only newly introduced warnings and errors in the results \ + of before checkin analysis if the number of changed files is less or equal than the threshold vcs.code.analysis.before.checkin.check.unused.only.changed.properties=true vcs.code.analysis.before.checkin.check.unused.only.changed.properties.description=Perform the check if properties are used \ @@ -969,7 +1002,7 @@ vcs.process.externally.added.files.description=Process externally added files. \ vcs.enable.add.ignored.directories.to.exclude=true vcs.enable.add.ignored.directories.to.exclude.description=Enables synchronization of ignored (but not excluded) directories with excluded directories on VCS ignored files rescan. vcs.propose.add.ignored.directories.to.exclude=true -vcs.propose.add.ignored.directories.to.exclude.description=Show notification with a proposal to add ignored (but not excluded) directories to exclude. +vcs.propose.add.ignored.directories.to.exclude.description=Show a notification with a proposal to add ignored (but not excluded) directories to exclude. vcs.resolve.symlinks.for.vcs.operations=prefer_target vcs.resolve.symlinks.for.vcs.operations.restartRequired=true @@ -1009,7 +1042,7 @@ keymap.windows.as.meta=false keymap.windows.as.meta.description='Windows' key press/release events would be processed as 'Meta' modifier for shortcuts read.system.shortcuts=true -read.system.shortcuts.description=Read system shortcuts and check conflicts with selected keymap +read.system.shortcuts.description=Read system shortcuts and check conflicts with the selected keymap caches.scanningThreadsCount=-1 caches.scanningThreadsCount.description=Specify the number of threads used for scanning files to index. -1 means the default value ("all available threads count" minus 1). @@ -1024,13 +1057,13 @@ projectView.showHierarchyErrors=true projectView.hide.dot.idea=true projectView.show.base.dir=true -projectView.show.base.dir.description=Include files from project's base dir even if they do not belong to any content root. +projectView.show.base.dir.description=Include files from the project's base dir even if they do not belong to any content root. projectView.choose.directory.on.compacted.middle.packages=false projectView.choose.directory.on.compacted.middle.packages.description=Show "Choose destination directory" when creating a new file with a node selected that corresponds to multiple compacted packages projectView.always.show.inner.classes=false -projectView.always.show.inner.classes.description=Shows inner classes in Project View even if Show Members is switched off +projectView.always.show.inner.classes.description=Shows inner classes in the Project View even if Show Members is switched off show.live.templates.in.completion=true show.live.templates.in.completion.description=Show live templates in completion @@ -1071,10 +1104,10 @@ documentation.component.highlighting.saturation.for.rendered.docs=65 documentation.component.highlighting.saturation.for.rendered.docs.description=Allows reducing saturation of highlighting colors in documentation rendered view hints to lower distraction from the main code. Here 100 is the normal saturation (bright tones) and 0 is zero saturation (gray tones) documentation.show.toolbar=false -documentation.show.toolbar.description=Show navigation toolbar in the documentation window +documentation.show.toolbar.description=Show the navigation toolbar in the documentation window documentation.v2.tw.focus.invocation.timeout=500 documentation.v2.tw.focus.invocation.timeout.description=How many milliseconds to wait for action invocation, which should focus the TW. \ - When "Auto-Update from Source" is turned off, the action invocation will refresh the TW content without focusing the TW right away. \ + When "Auto-Update from Source" is turned off, the action invocation will refresh the TW content without focusing on the TW right away. \ During this timeout the subsequent action invocation will focus the TW. \ When "Auto-Update from Source" is turned on, there is no need to invoke the action to refresh the TW content, \ so the action invocation moves focus to the TW without the need for this timeout. @@ -1090,7 +1123,7 @@ ide.completion.show.live.templates.on.top=false ide.completion.show.live.templates.on.top.description=In completion, show suitable live templates before other suggestions ide.lookup.preview.insertion=false -ide.lookup.preview.insertion.description=Show preview of the approximate insertion result for the currently selected lookup suggestion. +ide.lookup.preview.insertion.description=Show a preview of the approximate insertion result for the currently selected lookup suggestion. ide.completion.middle.matching=true ide.completion.middle.matching.description=Suggest items in completion that contain the entered string somewhere in the middle. @@ -1102,14 +1135,11 @@ ide.completion.variant.limit=1000 ide.completion.variant.limit.description=The completion list will never show a number of (most relevant) items >= this number ide.completion.max.width=99999 -ide.completion.max.width.description=Maximum width for completion popup in pixels. It can be used for making screenshots for blog posts and tweets. +ide.completion.max.width.description=Maximum width for the completion popup in pixels. It can be used for making screenshots for blog posts and tweets. ide.completion.show.visibility.icon=false ide.completion.show.visibility.icon.description=Show member visibility type icon on the right of the base icon -ide.java.completion.suggest.static.after.instance=false -ide.java.completion.suggest.static.after.instance.description=Suggest static methods/fields after instance qualifier in Java completion - ide.goto.middle.matching=true ide.goto.middle.matching.description=Suggest items in goto actions that contain the entered string somewhere in the middle. @@ -1126,7 +1156,7 @@ ide.structural.navigation.visit.fields=false ide.structural.navigation.visit.fields.description=Whether fields should be stopped at when navigating to the nex/previous structural member by Alt+Down/Up. ide.optimize.inspection.visitors=true -ide.optimize.inspection.visitors.description=Whether to skip elements that are not suitable for a visitor during inspections run +ide.optimize.inspection.visitors.description=Whether to skip elements that are not suitable for a visitor during the inspections run ide.dfa.time.limit.online=1000 ide.dfa.time.limit.online.description=Time limit (in milliseconds) that is allowed to analyze data flow for one method @@ -1144,7 +1174,7 @@ idea.report.nullity.missing.in.generated.overriders=true idea.report.nullity.missing.in.generated.overriders.description=Whether "@NotNull/@Nullable problems" inspection should complain about overriding methods or parameters missing @NotNull, which occur in generated code ide.ignore.call.result.inspection.honor.inferred.pure=true -ide.ignore.call.result.inspection.honor.inferred.pure.description=Whether inferred @Contract(pure=true) annotations should be taken into account in "Result of method call ignored" inspection +ide.ignore.call.result.inspection.honor.inferred.pure.description=Whether inferred @Contract(pure=true) annotations should be taken into account in the "Result of method call ignored" inspection ide.enable.toolwindow.stack=false ide.enable.toolwindow.stack.description=Remember which toolwindows were shown before the currently active one on the same side, and show them when the active one is hidden @@ -1177,7 +1207,7 @@ editor.loading.spinner.delay.ms=300 editor.loading.spinner.delay.ms.description=Delay before the loading icon is shown if the editor is not shown (in milliseconds) refactorings.use.updated.conflicts.detected.dialog=true -refactorings.use.updated.conflicts.detected.dialog.description=Show updated dialog when conflicts are detected during refactoring. Shows the old dialog when false. +refactorings.use.updated.conflicts.detected.dialog.description=Show an updated dialog when conflicts are detected during refactoring. Shows the old dialog when false. ide.find.show.tooltip.in.preview=true ide.find.show.tooltip.in.preview.description=Show tooltip balloons in the find toolwindow preview when a tooltip is set for the usage @@ -1185,20 +1215,22 @@ ide.find.show.tooltip.in.preview.description=Show tooltip balloons in the find t floating.codeToolbar.verticalOffset=2 floating.codeToolbar.verticalOffset.description=Vertical offset between line and floating toolbar for code floating.codeToolbar.showBelow=true -floating.codeToolbar.showBelow.description=Always show floating code toolbar below the selection when possible +floating.codeToolbar.showBelow.description=Always show the floating code toolbar below the selection when possible ide.goto.implementation.show.interfaces=true ide.goto.implementation.show.interfaces.description=Whether to show sub-interfaces when invoking the \ 'Go to Implementation' action (Ctrl+Alt+B) on an interface. actionSystem.force.alt.gr=false +actionSystem.force.alt.gr.description=Force treating AltGraph as Ctrl+Alt on Windows/Linux actionSystem.fix.alt.gr=true +actionSystem.fix.alt.gr.description=Work around layout-specific issues with AltGraph key handling completion.show.intention.icon=true always.show.intention.above.current.line=false -always.show.intention.above.current.line.description=Avoid showing intention popup on the line where caret stays (e.g., show it above current line) +always.show.intention.above.current.line.description=Avoid showing the intention popup on the line where caret stays (e.g., show it above current line) introduce.variable.prefer.statements=false -introduce.variable.prefer.statements.description=If true, select line at caret if there is no selection on introduce; \ +introduce.variable.prefer.statements.description=If true, select the line at caret if there is no selection on introduce; \ when false expressions chooser would be shown add.past.participle.to.suggested.names=true @@ -1255,19 +1287,19 @@ ui.inspector.accessibility.audit.description=Show accessibility audit functional comment.by.line.bulk.lines.trigger=100 diff.divider.repainting.disable.blitting=true -diff.divider.repainting.disable.blitting.description=Fix painting glitch on scrolling in diff - disable BLIT_SCROLL_MODE to force repainting with RepaintManager +diff.divider.repainting.disable.blitting.description=Fix the painting glitch on scrolling in diff - disable BLIT_SCROLL_MODE to force repainting with RepaintManager diff.divider.width=24 -diff.divider.width.description=Width of the divider area in side-by-side diff viewer +diff.divider.width.description=Width of the divider area in the side-by-side diff viewer enable.patch.file.diff.viewer=true -enable.patch.file.diff.viewer.description=Enable patch file viewer that allows to show changed files as a diff-like panel +enable.patch.file.diff.viewer.description=Enable patch file viewer that allows showing changed files as a diff-like panel combined.diff.files.limit=1000 -combined.diff.files.limit.description=If the limit is reached, show simple diff instead "All in One Diff".\ Set "-1" to turn the limit off. +combined.diff.files.limit.description=If the limit is reached, show the simple diff instead of "All in One Diff".\ Set "-1" to turn the limit off. combined.diff.loaded.content.limit=20 combined.diff.loaded.content.limit.description=Max number of loaded files diff contents to keep.\ Set "-1" to turn limit off and keep contents only inside viewport (+ viewport.delta). combined.diff.visible.viewport.delta=3 -combined.diff.visible.viewport.delta.description=Set additionally number of blocks which need to be loaded before and beyond the viewport. +combined.diff.visible.viewport.delta.description=Set additionally the number of blocks which need to be loaded before and beyond the viewport. show.diff.as.frame=false -show.diff.as.frame.description=Always force diff in separate window frame implementation in places, where editor diff preview is not available. Overrides the "Open Diff as Editor Tab" action. +show.diff.as.frame.description=Always force diff in a separate window frame implementation in places, where editor diff preview is not available. Overrides the "Open Diff as Editor Tab" action. show.diff.preview.as.editor.tab.with.single.click=false show.diff.preview.as.editor.tab.with.single.click.description=If enabled, and editor diff preview is used for Local Changes and Shelf, then \ it is opened with single click. @@ -1278,21 +1310,10 @@ blank.diff.history.max.memory.description=Amount of memory (approximate, order-o diff.external.tool.file.limit=20 diff.external.tool.file.limit.description=Limit the opening of too many files in an external diff tool simultaneously diff.use.aggressive.text.file.detection=false -diff.use.aggressive.text.file.detection.description=Ignore IDE 'File Types' setting when trying to show files in a diff viewer. +diff.use.aggressive.text.file.detection.description=Ignore the IDE 'File Types' setting when trying to show files in a diff viewer. diff.unified.enable.imaginary.editor=true diff.unified.enable.imaginary.editor.description=Enable syntax highlighting in 'Unified Diff Viewer' -jupyter.diff.viewer=true -jupyter.diff.viewer.output=false -jupyter.diff.viewer.output.description=Show outputs in the Jupyter diff viewer -jupyter.diff.viewer.description=Implementation of diff tool for Jupyter Notebooks, which instead of diff for JSON shows a diff for notebook representations -jupyter.merge.viewer=false -jupyter.merge.viewer.description=Implementation of merge tool for Jupyter Notebooks, which helps to deal with conflicts during git activities -jupyter.llm.autopilot=true -jupyter.llm.autopilot.description=Enable Autopilot for Jupyter Notebooks -dataspell.interpreter.widget=true -dataspell.interpreter.widget.description=Interpreter widget for lightweight interpreter management - llm.welcome.guide=true llm.welcome.guide.description=Enables Welcome Guide feature @@ -1322,15 +1343,6 @@ ide.message.dialogs.as.swing.alert.show.title.bar.description=Show platform wind windows.jumplist=true windows.jumplist.description=Enables JumpLists on Windows -# via ExternalSystemApiUtil.isInProcessMode -# suppress inspection "UnusedProperty" -GRADLE.system.in.process=true -GRADLE.system.in.process.description=Whether IDEA should use 'in-process' mode for interaction with gradle api - -# via ExternalSystemApiUtil.isInProcessMode -# suppress inspection "UnusedProperty" -MAVEN.system.in.process=true -MAVEN.system.in.process.description=Whether IDEA should use 'in-process' mode for interaction with maven api vcs.ignorefile.generation=false vcs.ignorefile.generation.description=Whether native VCS ignored files (e.g., .gitignore) are generated on first project open \ @@ -1345,7 +1357,7 @@ ide.notification.visible.count=2 ide.notification.visible.count.description=Max number of visible notifications ide.open.file.in.temp.project.dir=true -ide.open.file.in.temp.project.dir.description=Enable file opening in temp project directory +ide.open.file.in.temp.project.dir.description=Enable file opening in the temp project directory embed.scene.builder=true embed.scene.builder.description=Show SceneBuilder embedded into IntelliJ IDEA frame @@ -1357,10 +1369,10 @@ ide.scratch.buffers=5 ide.scratch.buffers.description=Number of scratch buffer files project.tree.structure.show.url=true -project.tree.structure.show.url.description=Show URL in the ProjectView nodes for project and libraries +project.tree.structure.show.url.description=Show URL in the Project View nodes for a project and libraries ide.show.progress.without.status.bar=true -ide.show.progress.without.status.bar.description=Disables transparent progress indicator when the status bar is switched off +ide.show.progress.without.status.bar.description=Disables the transparent progress indicator when the status bar is switched off editor.injected.highlighting.enabled=true editor.injected.highlighting.enabled.description=Disables injected fragments highlighting (requires project reopening) @@ -1374,7 +1386,7 @@ run.processes.with.pty=FALSE run.processes.with.pty.description=Allocate pseudo-terminal for running and debugging programs. \ This affects the result of isatty() native call, which is usually consulted by various command-line tools \ to check if they can operate in an interactive mode.\n\ - Note that not all IDEs check this option and support PTY at all.\ + Note that not all IDEs check this option and support PTY completely.\ Currently, this option is used in CLion and PyCharm. run.processes.using.pty.helper.on.mac.intel=true @@ -1384,7 +1396,7 @@ terminal.use.conpty.on.windows=true terminal.use.conpty.on.windows.description=Use ConPTY API to run processes with PTY on Windows run.processes.with.redirectedErrorStream=false -run.processes.with.redirectedErrorStream.description=Merge processes standard streams (error and output). \ +run.processes.with.redirectedErrorStream.description=Merge standard streams of processes (error and output). \ This makes it easier to correlate error messages with the corresponding output. \ However, as a result, it won't be possible to distinguish between stdout and stderr. \ This option is supported only for certain run/debug configurations. \ @@ -1411,9 +1423,9 @@ allow.dialog.based.popups.description=Allows using a JDialog as popup toplevel popup.resize.zone.macos=8 popup.resize.zone.macos.description=The size of mouse resize zone on macOS popup.resize.zone.windows=4 -popup.resize.zone.windows.description=The size of mouse resize zone on Windows +popup.resize.zone.windows.description=The size of the mouse resize zone on Windows popup.resize.zone.linux=4 -popup.resize.zone.linux.description=The size of mouse resize zone on Linux +popup.resize.zone.linux.description=The size of the mouse resize zone on Linux popup.fix.ide.frame.owner=false popup.fix.ide.frame.owner.description=Use a correct owner for IdeFrame, but can break some popups @@ -1432,10 +1444,10 @@ enable.rename.options.inplace=true enable.rename.options.inplace.description=Show search in comments/text occurrences as inlays in the editor ide.event.queue.dispatch.threshold=10 -ide.event.queue.dispatch.threshold.description=A positive number of milliseconds starts to measure time of event processing. +ide.event.queue.dispatch.threshold.description=A positive number of milliseconds starts to measure the time of event processing. ide.settings.keymap.input.method.enabled=false -ide.settings.keymap.input.method.enabled.description=Use input method instead of simple key event to enter shortcuts. +ide.settings.keymap.input.method.enabled.description=Use an input method instead of a simple key event to enter shortcuts. ide.settings.configurable.loading.threshold=0 ide.settings.configurable.loading.threshold.description=A positive number of milliseconds starts debugging of Settings loading. @@ -1444,10 +1456,15 @@ ide.settings.replace.group.with.single.configurable=true ide.settings.replace.group.with.single.configurable.description=Used to generate a more compact tree for Settings/Preferences. ide.titled.border.top=7 +ide.titled.border.top.description=Top inset of titled borders (pixels) ide.titled.border.left=0 +ide.titled.border.left.description=Left inset of titled borders (pixels) ide.titled.border.right=0 +ide.titled.border.right.description=Right inset of titled borders (pixels) ide.titled.border.bottom=10 +ide.titled.border.bottom.description=Bottom inset of titled borders (pixels) ide.titled.border.indent=20 +ide.titled.border.indent.description=Horizontal indentation for titled border content (pixels) html.prefer.short.notation.of.boolean.attributes=true @@ -1529,7 +1546,7 @@ idea.editor.smooth.scrolling.navigation.duration=100 idea.editor.smooth.scrolling.navigation.duration.description=Change the duration of animated scrolling in the editor using Page Up/Page Down keys idea.latching.scrolling.enabled=true -idea.latching.scrolling.enabled.description=When enabled, scroll sticks vertically or horizontally when precise scroll is on +idea.latching.scrolling.enabled.description=When enabled, scroll sticks vertically or horizontally when the precise scroll is on idea.latching.scrolling.time=200 idea.latching.scrolling.time.description=Maximum event lifetime in event series (ms) idea.latching.scrolling.angle=45.0 @@ -1583,13 +1600,13 @@ lcd.contrast.value=0 lcd.contrast.value.description=Set LCD text contrast value from 100 to 250 autocheck.availability.welcome.screen.projects=true -autocheck.availability.welcome.screen.projects.description=Auto-checks recent projects availability in the Welcome screen +autocheck.availability.welcome.screen.projects.description=Auto-checks recent projects' availability on the Welcome screen ide.find.use.search.in.selection.keyboard.shortcut.for.replace=false ide.find.use.search.in.selection.keyboard.shortcut.for.replace.description=Always use the keyboard shortcut for Search in Selection instead of the Replace keyboard shortcut when in Replace mode ide.find.word.based.preserve.case=true ide.find.word.based.preserve.case.description=New word-based preserve case implementation ide.find.max.rows=3 -ide.find.max.rows.description=Max rows to be shown in search and replace fields in 'Find in Files' / 'Replace in Files' popup and Find / Replace in editor +ide.find.max.rows.description=Max rows to be shown in search and replace fields in the 'Find in Files' / 'Replace in Files' popup and Find / Replace in the editor ide.find.result.count.warning.limit=1000 ide.find.result.count.warning.limit.restartRequired=true ide.find.result.count.warning.limit.description=The limit at which the "Too many results found" warning is shown @@ -1604,15 +1621,15 @@ ide.find.select.word.at.caret.description=Select and find word at caret when the ide.find.escape.selected.text.for.regex=true ide.find.escape.selected.text.for.regex.description=Automatically escapes selected text for Find / Find in Files when the Regex option in ON ide.find.use.editor.font=false -ide.find.use.editor.font.description=Switch search fields in editor and in 'Find in Files' to using editor font +ide.find.use.editor.font.description=Switch search fields in the editor and in 'Find in Files' to using editor font ide.remove.help.button.from.dialogs=false -ide.remove.help.button.from.dialogs.description=Removes help button from dialogs +ide.remove.help.button.from.dialogs.description=Removes the help button from dialogs ide.find.in.file.highlight.usages=true ide.find.in.file.highlight.usages.description=Delegate FindUsagesInFile action to HighlightUsagesInFile ide.symbol.url.references=false -ide.symbol.url.references.description=Symbol API based issue references +ide.symbol.url.references.description=Symbol API-based issue references ide.rename.preview.dialog=false -ide.rename.preview.dialog.description=Show preview dialog after Usage View +ide.rename.preview.dialog.description=Show the preview dialog after Usage View ide.instant.theme.switch=true ide.instant.theme.switch.description=Switch theme in the quick switcher instantly, upon selection ide.instant.theme.switch.delay=500 @@ -1628,12 +1645,13 @@ ide.usages.popup.analyzing.indicator.enable=true ide.usages.popup.analyzing.indicator.enable.description=Show analyzing indicator in the show usages popup ide.ruler.guide.allowed.gap=2 -ide.ruler.guide.allowed.gap.description=Gap in pixels considered as an error in the layout +ide.ruler.guide.allowed.gap.description=Gap in pixels that is considered as an error in the layout ide.debug.in.title=false +ide.debug.in.title.description=Show the debug session name in the title bar ide.tab.tooltip.module=false -ide.tab.tooltip.module.description=Show module name in editor tab tooltip +ide.tab.tooltip.module.description=Show the module name in the editor tab tooltip darcula.fix.native.flickering=false darcula.fix.native.flickering.description=Fixes white flash on first window appearance @@ -1642,10 +1660,10 @@ terminal.persistent.tabs=true terminal.persistent.tabs.description=Save state of terminal tabs between IDE restarts string.value.max.number.of.strings=132 -string.value.max.number.of.strings.description=Limit for number of different strings in StringValue +string.value.max.number.of.strings.description=Limit for the number of different strings in StringValue search.everywhere.pattern.checking=true -search.everywhere.pattern.checking.description=Avoid searching classes and symbols if pattern contains only one dot +search.everywhere.pattern.checking.description=Avoid searching for classes and symbols if a pattern contains only one dot search.everywhere.fuzzy.file.search.enabled=false search.everywhere.fuzzy.file.search.enabled.description=Enable fuzzy file search @@ -1657,7 +1675,7 @@ custom.folding.max.lookup.depth=50 custom.folding.max.lookup.depth.description=Max lookup depth for custom folding comments in a PSI tree. project.structure.add.tools.jar.to.new.jdk=false -project.structure.add.tools.jar.to.new.jdk.description=Automatically add tools.jar to 'Classpath' when new JDK is created +project.structure.add.tools.jar.to.new.jdk.description=Automatically add tools.jar to 'Classpath' when a new JDK is created project.qualified.module.names=true project.qualified.module.names.description=Organize module nodes accordingly to their qualified (dot-separated) names @@ -1666,17 +1684,9 @@ external.system.configuration.import.enabled=true external.system.configuration.import.enabled.description=Enable import of IntelliJ specific settings from an external build system external.system.auto.import.disabled=false external.system.auto.import.disabled.description=Disable auto-import build system model to idea -gradle.auto.auto.jdk.fix.disabled=false -gradle.auto.auto.jdk.fix.disabled.description=Disable attempt to fix unknown JDK in Gradle configuration on project open application.deactivation.timeout=1500 -android.gradle.project.startup.activity.disabled=false -android.gradle.project.startup.activity.disabled.description = Disable gradle post-startup activity for Android projects - -android.sdk.local.properties.update.disabled=false -android.sdk.local.properties.update.disabled.description=Disable update android sdk for local.properties - editor.breadcrumbs.highlight.on.hover=false editor.breadcrumbs.highlight.on.hover.description=Highlight corresponding ranges in the editor on breadcrumb item hover (requires project reopening) @@ -1727,7 +1737,7 @@ ide.new.run.config.junit.description=New JUnit Configuration UI select.run.configuration.from.context=true select.run.configuration.from.context.description=Select run configuration when run from context (gutter icon, popup menu) run.popup.move.folders.to.top=false -run.popup.move.folders.to.top.description=Move folders in run popup to top or spread them by type groups +run.popup.move.folders.to.top.description=Move folders in the run popup to the top or spread them by type groups run.popup.show.inlines.for.active.configurations=true run.popup.show.inlines.for.active.configurations.description=Always show inline Run/Debug actions for active configurations update.run.configuration.actions.from.cache=false @@ -1735,22 +1745,22 @@ update.run.configuration.actions.from.cache.description=Disable run actions on t run.configuration.update.timeout=100 run.configuration.update.timeout.description=The timeout in milliseconds for context run configuration actions to determine their applicability run.configuration.max.name.length=80 -run.configuration.max.name.length.description=When the name of run configuration exceeds the limit, it will be trimmed in UI like 'LongNamedRunCo...' +run.configuration.max.name.length.description=When the name of the run configuration exceeds the limit, it will be trimmed in UI like 'LongNamedRunCo...' run.configuration.use.tree.popup.to.add.new=true run.configuration.use.tree.popup.to.add.new.description=Enables tree structure instead of a plain list in the "Add New Configuration" popup suggest.all.run.configurations.from.context=false -suggest.all.run.configurations.from.context.description=With the option enabled, all run configuration from context will be suggested to\ +suggest.all.run.configurations.from.context.description=With the option enabled, all run configurations from context will be suggested to\ run or create in spite of existing run configuration in the context run.current.file.item.in.run.configurations.combobox=false -run.current.file.item.in.run.configurations.combobox.description=Adds 'Current File' item to the run configurations list in the combobox on the toolbar +run.current.file.item.in.run.configurations.combobox.description=Adds the 'Current File' item to the run configurations list in the combobox on the toolbar allow.macros.for.run.configurations=true allow.macros.for.run.configurations.description=Enables context-dependent macros like $FileName$ or $SelectedText$ in Run Configurations lock.run.executor.for.before.run.tasks=false -lock.run.executor.for.before.run.tasks.description=Locks execution of 'before run' tasks to standard 'run' executor (e.g., prevents debugging) +lock.run.executor.for.before.run.tasks.description=Locks execution of 'before run' tasks to the standard 'run' executor (e.g., prevents debugging) focus.follows.mouse.workarounds=false focus.follows.mouse.workarounds.description=Enables some workarounds for Focus-follows-mouse issues, like IDEA-112015 tests.view.node.expanding.search=true -tests.view.node.expanding.search.description=Enable node expanding in tests view speed search +tests.view.node.expanding.search.description=Enable node expanding in the tests view speed search test.view.hide.progress.bar.on.tests.finished=false test.view.hide.progress.bar.on.tests.finished.description=Hides the colored progress bar when tests have finished running @@ -1760,19 +1770,6 @@ ide.enable.edt.lock.load.monitor.description=Enable monitoring of lock usage on ide.enable.edt.lock.load.fus.monitor=true ide.enable.edt.lock.load.fus.monitor.description=Enable monitoring of lock usage on EDT for reporting to FUS -cidr.xcode.derived.data.override=true -cidr.xcode.derived.data.override.restartRequired=true -cidr.xcode.derived.data.override.description=Ignore Xcode DerivedData location settings -appcode.always.use.xcodebuild=false -appcode.always.use.xcodebuild.description=Force AppCode to always use xcodebuild tool for building -appcode.simulator.debugger.launch.timeout=180 -appcode.simulator.debugger.launch.timeout.description=How long (in seconds) the debugger session should wait for the simulator to launch the process -appcode.compiler.invocations.disk.cache=false -appcode.compiler.invocations.disk.cache.description=Force AppCode to cache the result of compiler invocations on disk -appcode.resolve.effective.platform=true -appcode.resolve.effective.platform.restartRequired=true -appcode.resolve.effective.platform.description=Enable resolving according to effective platform - ide.text.effect.new=true ide.text.effect.new.description=Enables new effect painter for text highlighting.delay.invalid.psi.info.kill.ms=300 @@ -1805,9 +1802,6 @@ ide.remote.interpreters.rsync.enabled.on.windows=false ide.remote.interpreters.use.rsync=true ide.remote.interpreters.use.rsync.description=Use rsync utility to synchronize remote interpreters and local stubs -javafx.fxml.controller.from.loader=false -javafx.fxml.controller.from.loader.description=Resolve FXML controllers specified with FXMLLoader.setController() - inplace.change.signature=false inplace.change.signature.description=Enables change signature without a dialog, inplace in the editor @@ -1892,19 +1886,19 @@ document.async.commit.with.coroutines=true document.async.commit.with.coroutines.description=Enables the use of coroutines for asynchronous document commit. Non-modal commit will use background write action document.save.in.background.allowed=true -document.save.in.background.allowed.description=Allows saving documents in background +document.save.in.background.allowed.description=Allows saving documents in the background filetype.reparse.with.coroutines=true filetype.reparse.with.coroutines.description=Whether to reparse files asynchronously with background write action file.system.trace.loading=/proc/cpuinfo -file.system.trace.loading.description=Semicolon-separated paths that should not be loaded into IDE's file system. Their loading stack traces will be logged +file.system.trace.loading.description=Semicolon-separated paths that should not be loaded into the IDE's file system. Their loading stack traces will be logged file.system.trace.loading.restartRequired=true ssr.template.from.selection.builder=false ssr.template.from.selection.builder.description=Allows building template with live-template-like placeholders when invoking SSR from the editor with selection ssr.in.editor.problem.highlighting=true -ssr.in.editor.problem.highlighting.description=Show location of search template problems in the editor +ssr.in.editor.problem.highlighting.description=Show the location of search template problems in the editor jdk.regex.soe.workaround=true jdk.regex.soe.workaround.description=In regular expression pattern replace choice \\n|. with . (and DOT_ALL option) to prevent stack overflow during matching @@ -1932,18 +1926,13 @@ duplicates.inspection.only.extractable=false duplicates.inspection.only.extractable.description=Highlight duplicate code only if it can be extracted into a method duplicates.index.file.maxSize=102400 -duplicates.index.file.maxSize.description=Max size of a file indexed by duplicates analysis +duplicates.index.file.maxSize.description=Max size of a file indexed by duplicate analysis duplicates.index.file.test.enable=false duplicates.index.file.test.enable.description=Enable duplicate index in test files -ide.jvm.run.marker=false -ide.jvm.run.marker.description=Enable JVM-lang based run line markers ide.create.field.enable.shortening=true ide.create.field.enable.shortening.description=Always enable shortening of FQNs in templates in Create Field fixes -ide.jvm.command.line.clear.all.environments=true -ide.jvm.command.line.clear.all.environments.description=Clear all environments when running a JVM command line application - editor.adjust.right.margin=false editor.adjust.right.margin.description=Whether the right margin position for each visual line is shifted according to folded regions' and \ inlays' widths on that line @@ -1957,9 +1946,9 @@ editor.text.fractional.metrics.description=Do not round glyph advances to intege editor.text.xcode.vertical.spacing=false editor.text.xcode.vertical.spacing.description=Enable the same vertical spacing as Xcode editor uses editor.text.vertical.spacing.correct.rounding=false -editor.text.vertical.spacing.correct.rounding.description=Make line spacing closer to that in other applications, when scaling factor is set +editor.text.vertical.spacing.correct.rounding.description=Make line spacing closer to that in other applications when the scaling factor is set editor.disable.drag.with.right.button=false -editor.disable.drag.with.right.button.description=Disable any mouse drag processing in the editor with right mouse button pressed +editor.disable.drag.with.right.button.description=Disable any mouse drag processing in the editor with the right mouse button pressed editor.drag.sensitivity=5 editor.drag.sensitivity.description=How far away must the mouse be moved for the move to be interpreted as a drag @@ -1967,30 +1956,30 @@ idea.invalidate.caches.invalidates.vfs=false idea.invalidate.caches.invalidates.vfs.description=Invalidate caches tries to invalidate everything, including Local History editor.keep.completion.hints.longer=false -editor.keep.completion.hints.longer.description=Don't remove hints for parameter names added on method call completion until caret moves \ +editor.keep.completion.hints.longer.description=Don't remove hints for parameter names added on method call completion until the caret moves \ to another line editor.completion.hints.per.call.limit=256 editor.completion.hints.per.call.limit.description=Limits the number or parameter placeholders/hints, populated on method call completion. \ Further parameters appear on Tab presses. editor.completion.hints.virtual.comma=true -editor.completion.hints.virtual.comma.description=Don't insert commas between completion hints for parameters, display comma as part of hint +editor.completion.hints.virtual.comma.description=Don't insert commas between completion hints for parameters, display comma as part of the hint editor.codeVision.new=true editor.codeVision.new.description=New code vision hints (inlays) editor.codeVision.more.inlay=false -editor.codeVision.more.inlay.description=Show `More\u2026` hint on hover over code vision entities +editor.codeVision.more.inlay.description=Show the `More \u2026` hint on hover over code vision entities folding.signature.validation=false folding.signature.validation.description=Validate that signature for a PSI element, for which a folding region is generated, can be used to \ find exactly the same PSI element try.starting.dumb.mode.where.many.files.changed=true -try.starting.dumb.mode.where.many.files.changed.description=Enables automatic start of dumb mode for all opened projects once more than certain number of files is changed +try.starting.dumb.mode.where.many.files.changed.description=Enables automatic start of dumb mode for all opened projects once more than a certain number of files is changed editor.highlight.foldings=true editor.highlight.foldings.description=Highlight folded regions containing certain highlighters (e.g., for search results) ide.hide.real.module.name=false -ide.hide.real.module.name.description=Removes real module name from Project View in square brackets +ide.hide.real.module.name.description=Removes the real module name from the Project View in square brackets ide.show.folder.name.instead.of.module.name=false ide.show.folder.name.instead.of.module.name.description=Use module folder name instead of module name in renderers @@ -2001,27 +1990,24 @@ skip.move.resize.events.restartRequired=true skip.move.resize.events.description=Skip move/resize events from list/table/tree renderers during painting ide.content.manager.listeners.order.fix=true -ide.content.manager.listeners.order.fix.description=Fixes historically reversed order of ContentManager listeners. +ide.content.manager.listeners.order.fix.description=Fixes historically reversed the order of ContentManager listeners. run.anything.use.pty=true run.anything.use.pty.description=Determines if a PTY should be used for executing commands within the 'Run Anything' feature. ast.loading.filter=false -ast.loading.filter.description=Enables the mechanism that disables AST loading in particular thread +ast.loading.filter.description=Enables the mechanism that disables AST loading in a particular thread marketplace.certificate.signature.check=true marketplace.certificate.signature.check.description=Verify signatures for plugins downloaded from plugins.jetbrains.com custom-repository.certificate.signature.check=false custom-repository.certificate.signature.check.description=Verify signatures for plugins downloaded from custom plugin repositories and installed from disk -batch.inspections.ignored.jvm.languages=Groovy,Scala -batch.inspections.ignored.jvm.languages.description=Disable UAST processing for JVM languages; a comma separated list of UAST capable language ids. - batch.inspections.startup.activities.timeout=180 batch.inspections.startup.activities.timeout.description=Timeout of processing all startup activities in a batch inspections run (in minutes) ide.project.view.bookmarks.icon.before=false -ide.project.view.bookmarks.icon.before.description=Shows bookmark icon before the Project View node icon +ide.project.view.bookmarks.icon.before.description=Shows the bookmark icon before the Project View node icon ide.project.view.bookmarks.icon.hide=true ide.project.view.bookmarks.icon.hide.description=Hides bookmark icon for bookmarked nodes in the Project View @@ -2039,7 +2025,7 @@ ide.project.view.persist.cached.presentation=true ide.project.view.persist.cached.presentation.description=Save and restore Project View presentation to quickly show nodes after restart before they're actually loaded ide.linux.enter.on.dialog.triggers.focused.button=true -ide.linux.enter.on.dialog.triggers.focused.button.description=IDEA-125537 Pushing Enter in dialog windows chooses default focus instead of manually selected one on Linux +ide.linux.enter.on.dialog.triggers.focused.button.description=IDEA-125537 Pushing Enter in dialog windows chooses the default focus instead of the manually selected one on Linux ide.linux.hide.native.title=true ide.linux.hide.native.title.description=Enables hiding native title feature for Linux @@ -2071,18 +2057,18 @@ ide.edt.update.context.only.on.key.pressed.event=false ide.edt.update.context.only.on.key.pressed.event.description=Update context in IdeKeyEventDispatcher#updateCurrentContext only on key press new.search.everywhere.font.size.delta=0 -new.search.everywhere.font.size.delta.description=Sets how much font size should be increased in Search Everywhere popup text field +new.search.everywhere.font.size.delta.description=Sets how much font size should be increased in the Search Everywhere popup text field new.search.everywhere.use.editor.font=false -new.search.everywhere.use.editor.font.description=Switch Search Everywhere dialog to using editor font +new.search.everywhere.use.editor.font.description=Switch the Search Everywhere dialog to using editor font search.everywhere.disable.history.for.all=false -search.everywhere.disable.history.for.all.description=Disable saving previous search string for All tab +search.everywhere.disable.history.for.all.description=Disable saving the previous search string for the All tab search.everywhere.show.weights=false -search.everywhere.show.weights.description=Show item weights in description bar +search.everywhere.show.weights.description=Show item weights in the description bar search.everywhere.detect.slow.contributors=true search.everywhere.detect.slow.contributors.description=Enables logging info about slow SE contributors toolwindow.active.tab.use.contrast.background=false -toolwindow.active.tab.use.contrast.background.description=When enabled contrast color is used for selected tab background in tool windows +toolwindow.active.tab.use.contrast.background.description=When enabled, contrast color is used for the selected tab background in tool windows toolwindow.active.tab.contrast.background.color=808080 toolwindow.active.tab.contrast.background.color.description=Contrast color for the selected tab background in tool windows toolwindow.disable.overlay.by.double.key=false @@ -2091,20 +2077,15 @@ toolwindow.open.tab.in.editor=false toolwindow.open.tab.in.editor.description=Enables "Open in Editor Tab" action linux.kernel.variables.validate.every.time=true -linux.kernel.variables.validate.every.time.description=Check that kernel variables required to run profiler are set before every profiler start +linux.kernel.variables.validate.every.time.description=Check that kernel variables required to run the profiler are set before every profiler start -idea.profiler.jvm.dtrace=false -idea.profiler.jvm.dtrace.description=Enable JVM DTrace based profiler on macOS -idea.profiler.jfr.max.events.of.type.per.chunk=100 -idea.profiler.jfr.max.events.of.type.per.chunk.description=Maximum number of events of a particular type (i.e. "JVM Information") \ - per chunk to be shown in the Events tab idea.profiler.hprof.autoload.snapshot.size=1000 idea.profiler.hprof.autoload.snapshot.size.description=Enable snapshot analysis on-the-fly when hprof snapshot is less than the specified value in MB idea.profiler.hprof.show.objectAddress=false idea.profiler.hprof.show.objectAddress.description=Whether to attach object snapshot's address in UI: tables, trees, etc. idea.profiler.hprof.show.objectAddress.restartRequired=true idea.profiler.hprof.show.duplicated.strings=true -idea.profiler.hprof.show.duplicated.strings.description=Show 'Duplicated Strings' tab in the Profiler Tool Window +idea.profiler.hprof.show.duplicated.strings.description=Show the 'Duplicated Strings' tab in the Profiler Tool Window idea.profiler.hprof.show.duplicated.strings.restartRequired=false idea.profiler.kotlin.inline=true idea.profiler.kotlin.inline.description=Enable Kotlin inline functions support @@ -2131,7 +2112,7 @@ idea.profiler.allocations.in.bytes=false idea.profiler.allocations.in.bytes.description=Show allocations in bytes (without conversion to kB/MB/GB) recent.locations.lines.before.and.after=2 -recent.locations.lines.before.and.after.description=Defines the number of lines that are shown before and after recent location. +recent.locations.lines.before.and.after.description=Defines the number of lines that are shown before and after a recent location. clipboard.history.max.items=100 clipboard.history.max.items.description=Maximum number of items in clipboard history @@ -2142,10 +2123,10 @@ plugins.show.implementation.details=false plugins.show.implementation.details.description=Show plugins which are marked as implementation-detail error.navigation.show.tooltip=true -error.navigation.show.tooltip.description=Show error description after navigating to next/previous error +error.navigation.show.tooltip.description=Show error description after navigating to the next / previous error copy.tbx.reference.enabled=false -copy.tbx.reference.enabled.description=Enable "Copy TBX Reference URL" action +copy.tbx.reference.enabled.description=Enable the "Copy TBX Reference URL" action ide.project.frame.as.splash=false ide.project.frame.as.splash.description=Whether to replace the splash screen with a project frame @@ -2153,16 +2134,16 @@ ide.project.frame.as.splash.description=Whether to replace the splash screen wit ide.project.frame.screen.bounds.tolerance=10 ide.project.frame.screen.bounds.tolerance.description=How far (in pixels) beyond the screen the IDE frame is allowed to extend when restoring its previously saved bounds ide.project.frame.auto.fix.size.windows=true -ide.project.frame.auto.fix.size.windows.description=Automatically fix the frame size and location on Windows in case it becomes very small +ide.project.frame.auto.fix.size.windows.description=Automatically fix the frame size and location on Windows in case it becomes tiny ide.project.loading.show.last.state=false -ide.project.loading.show.last.state.description=Show last state screenshot instead of project loading progress +ide.project.loading.show.last.state.description=Show the last state screenshot instead of project loading progress contrast.scrollbars.alpha.level=-1 -contrast.scrollbars.alpha.level.description=Alpha level for contrast scrollbars (accessibility option). If alpha<0 default value will be used +contrast.scrollbars.alpha.level.description=Alpha level for contrast scrollbars (accessibility option). If alpha < 0 default value will be used ide.background.post.startup.activity.delay=5000 -ide.background.post.startup.activity.delay.description=Delay in milliseconds between the end of normal IDE startup and the start of executing background post-startup activities +ide.background.post.startup.activity.delay.description=Delay in milliseconds between the end of a normal IDE startup and the start of executing background post-startup activities ide.plugins.allow.unload=true ide.plugins.allow.unload.description=Allow loading and unloading plugins without a restart @@ -2173,7 +2154,7 @@ ide.plugins.snapshot.on.unload.fail.description=Capture memory snapshot when unl ide.plugins.analyze.snapshot=true ide.plugins.analyze.snapshot.description=Automatically analyze snapshots produced when plugin unload fails ide.plugins.unload.timeout=5000 -ide.plugins.unload.timeout.description=Time to wait for classloader to be garbage-collected on plugin unloading (ms) +ide.plugins.unload.timeout.description=Time to wait for a classloader to be garbage-collected on plugin unloading (ms) ide.plugins.load.automatically=false ide.plugins.load.automatically.description=Automatically load required plugins when possible ide.plugins.suggest.install.optional.dependencies=false @@ -2192,7 +2173,7 @@ ide.activity.tracking.enable.debug=false ide.activity.tracking.enable.debug.description=Record debug information for `Observation.awaitConfiguration` light.edit.file.open.enabled=true -light.edit.file.open.enabled.description=Open files in off-project editor instead of creating a temporary project +light.edit.file.open.enabled.description=Open files in the off-project editor instead of creating a temporary project system.file.type.associations.enabled=true system.file.type.associations.enabled.description=Enable Tools|Associate File Types with... action @@ -2232,7 +2213,7 @@ ide.animate.toolwindows.description=Use slide animation when showing/hiding unpi editor.show.notification.after.reformat=true editor.show.notification.after.reformat.description=Show hint notification after Reformat Code action editor.show.notification.after.optimize.imports=true -editor.show.notification.after.optimize.imports.description=Show hint notification after Optimize Imports action +editor.show.notification.after.optimize.imports.description=Show hint notification after the Optimize Imports action editor.scope.based.formatting.ui=false editor.scope.based.formatting.ui.description=Enables UI for deprecated scope-based formatting @@ -2245,17 +2226,17 @@ ide.widget.toolbar.description=Widget toolbar UI ide.widget.toolbar.logging=false ide.widget.toolbar.logging.description=Log startup info for the run widget ide.widget.toolbar.experimentalUpdating=true -ide.widget.toolbar.experimentalUpdating.description=If enabled, run widget startup will be faster, but update will become unstable +ide.widget.toolbar.experimentalUpdating.description=If enabled, run widget startup will be faster, but the update will become unstable ide.widget.toolbar.is.settings.available=false ide.widget.toolbar.is.settings.available.description=If enabled, run widget settings will be shown in the preferences ide.lookup.shrink=false -ide.lookup.shrink.description=Reduce completion popup width automatically while scrolling, if visible items all fit +ide.lookup.shrink.description=Reduce completion popup width automatically while scrolling if visible items all fit ide.undo.fallback=false -ide.undo.fallback.description=Fallback on local stack if global command is blocking +ide.undo.fallback.description=Fallback on the local stack if the global command is blocking ide.undo.transparent.caret.movement=false -ide.undo.transparent.caret.movement.description=When enabled caret movement will no longer be a separate undo step +ide.undo.transparent.caret.movement.description=When enabled, caret movement will no longer be a separate undo step ide.undo.frontend.if.possible=true ide.undo.frontend.if.possible.restartRequired=true ide.undo.frontend.if.possible.description=Feature flag for IJPL-185748 (WIP!). If enabled, undo/redo is performed on the frontend side if possible. @@ -2270,7 +2251,7 @@ ide.experimental.ui.inter.font.description=Use Inter font for context menus in E ide.experimental.ui.animations=true ide.experimental.ui.animations.description=Animate resizing of editor splits ide.experimental.ui.meetNewUi=true -ide.experimental.ui.meetNewUi.description=Turns on Meet New UI tool window +ide.experimental.ui.meetNewUi.description=Turns on the Meet New UI tool window ide.experimental.ui.meetNewUi.restartRequired=true ide.experimental.ui.tree.selection=true ide.experimental.ui.tree.selection.description=New UI for selected tree nodes @@ -2283,7 +2264,7 @@ ide.experimental.ui.editor.tabs.scrollbar.description=Add scrollbar to editor ta ide.web.preview.enabled=true ide.web.preview.enabled.restartRequired=true -ide.web.preview.enabled.description=Ability to preview HTML content directly in IDE +ide.web.preview.enabled.description=Ability to preview HTML content directly in the IDE ide.color.scheme.for.descriptor.preview.enabled=false ide.color.scheme.for.descriptor.preview.enabled.description=Use IDE colors for the editor preview in toolwindows @@ -2330,7 +2311,7 @@ ide.slow.operations.assertion.vcs.line-status-tracker-provider=false ide.slow.operations.assertion.vcs.line-status-tracker-provider.description=An `ide.slow.operations.assertion` sub-flag for LineStatusTrackerManager ide.show.open.button.in.tip.dialog=false -ide.show.open.button.in.tip.dialog.description=Shows Open button in Tip of the Day dialog to load a custom HTML file to preview +ide.show.open.button.in.tip.dialog.description=Shows the Open button in the Tip of the Day dialog to load a custom HTML file to preview ide.save.missing.jb.colors=false ide.save.missing.jb.colors.description=A missing named color will be added to UIDefaults with !!! suffix @@ -2354,13 +2335,13 @@ ide.disable.intellij.project.analytics=false ide.disable.intellij.project.analytics.description=Option to disable fine-tuned analytics for IntelliJ project ide.text.editor.with.preview.show.floating.toolbar=true -ide.text.editor.with.preview.show.floating.toolbar.description=Show floating toolbar with layouts instead of permanent toolbar +ide.text.editor.with.preview.show.floating.toolbar.description=Show a floating toolbar with layouts instead of a permanent toolbar ide.project.icon.size=20 ide.project.icon.size.description=Icon size for project icons ide.problems.view.empty.status.actions=true -ide.problems.view.empty.status.actions.description=Project Errors in Problems View shows predefined actions in empty text +ide.problems.view.empty.status.actions.description=Project Errors in the Problems View show predefined actions in empty text ide.problems.view.notify.later=true ide.problems.view.notify.later.description=Problems View notifies listeners later on EDT @@ -2372,9 +2353,6 @@ ide.problems.view.provider.class.filter.description=Specify qualified class name ide.presentation.assistant.theme.enabled=false ide.presentation.assistant.theme.enabled.description=If true, Presentation Assistant popups will use alternative colors from their color scheme -eclipse.scan.home.directory=true -eclipse.scan.home.directory.description=Scan home directory for existing Eclipse projects - ide.diagnostics.show.context.menu.invocation.time=false ide.diagnostics.show.context.menu.invocation.time.description=Show time in ms between mouse event and context menu appearance on screen @@ -2396,7 +2374,7 @@ editor.text.field.init.on.shown=true editor.text.field.init.on.shown.description=Initialize EditorTextField when first shown ide.gtd.show.error=true -ide.gtd.show.error.description=Whether to show "Cannot find declaration to go" error by "Go To Declaration" action +ide.gtd.show.error.description=Whether to show the "Cannot find declaration to go" error by "Go To Declaration" action ide.ui.new.file.chooser=false ide.ui.new.file.chooser.description=Experimental file chooser implementation (IJPL-338) @@ -2407,22 +2385,19 @@ ide.editor.tab.selection.animation=false ide.editor.tab.selection.animation.description=Animate tab underline when switching editor tabs calculate.gutter.actions.always=false -calculate.gutter.actions.always.description=Calculate all gutter icons/actions even if Editor|General|Gutter Icons|Show icons is set to "off", to be able to show these actions in Alt-Enter +calculate.gutter.actions.always.description=Calculate all gutter icons/actions even if Editor|General|Gutter Icons|Show icons is set to "off" to be able to show these actions in Alt-Enter ide.debug.gutter.area=false ide.debug.gutter.area.description=Shows gutter areas on hover ide.file.colors.at.left=false -ide.file.colors.at.left.description=Paints file colors as stripe at left instead of background +ide.file.colors.at.left.description=Paints file colors as stripe at left instead of the background ide.wsl.use_wsl_sync=true -ide.wsl.use_wsl_sync.description=Use wsl sync engine to copy data between Linux and Windows +ide.wsl.use_wsl_sync.description=Use the WSL sync engine to copy data between Linux and Windows ide.heartbeat.delay=60000 -ide.heartbeat.delay.description=Delay before heartbeat thread starts reporting performance data - -static.analysis.use.js.configurator=false -static.analysis.use.js.configurator.description=Whether to autoconfigure java-script project +ide.heartbeat.delay.description=Delay before the heartbeat thread starts reporting performance data ide.ui.wrap.colors=true ide.ui.wrap.colors.description=Wrap UI colors to colors with name @@ -2443,7 +2418,7 @@ ide.scale.discrete.take.floor=false ide.scale.discrete.take.floor.description=Use floor from Float when discretion the ide scale value. That would make icon scaling more aligned with font scaling. ide.popup.align.by.content=true -ide.popup.align.by.content.description=When popup is shown under a component use vertical alignment not by their border but by their content +ide.popup.align.by.content.description=When a popup is shown under a component, use vertical alignment not by their border but by their content ide.mac.stage.manager.support=false ide.mac.stage.manager.support.description=Support for Stage Manager in macOS Ventura. If enabled, dialogs stop jumping @@ -2473,7 +2448,7 @@ idea.ignore.disabled.plugins.description=Ignores disabled plugin state editor.readerMode.vfmt.removeZombies=true editor.readerMode.vfmt.removeZombies.description=Enables fix for IDEA-313274. It prevents erroneously cached visual formattingfoldings \ - from being loaded into editor. + from being loaded into the editor. editor.readerMode.vfmt.removeZombies.restartRequired=true ide.speed.search.allow.custom.location=false @@ -2486,7 +2461,7 @@ ide.colorful.toolbar.gradient.type=[Just Icon|Linear Gradient and Icon|Circular ide.colorful.toolbar.gradient.type.description=Defines types of main toolbar decorations ide.colorful.toolbar.gradient.radius.description=Radius of the gradient in Main Toolbar ide.colorful.toolbar.gradient.saturation=0.85 -ide.colorful.toolbar.gradient.saturation.description=Saturation of the gradient color in Main Toolbar +ide.colorful.toolbar.gradient.saturation.description=Saturation of the gradient color in the Main Toolbar ide.colorful.toolbar.force=true ide.colorful.toolbar.force.description=Show 'Colorful toolbar' even if there is one project open (works only if the feature is enabled). \ Reopen project or restart IDE to apply the setting diff --git a/platform/util/src/com/intellij/util/EnvironmentUtil.java b/platform/util/src/com/intellij/util/EnvironmentUtil.java index 59604316f374..1782ffc61bf9 100644 --- a/platform/util/src/com/intellij/util/EnvironmentUtil.java +++ b/platform/util/src/com/intellij/util/EnvironmentUtil.java @@ -112,7 +112,8 @@ public final class EnvironmentUtil { ourEnvGetter.set(loader); } - private static Map getSystemEnv() { + @ApiStatus.Internal + public static Map getSystemEnv() { if (OS.CURRENT == OS.Windows) { return Collections.unmodifiableMap(CollectionFactory.createCaseInsensitiveStringMap(System.getenv())); } diff --git a/platform/util/testSrc/com/intellij/util/lang/test/UrlClassLoaderTest.java b/platform/util/testSrc/com/intellij/util/lang/test/UrlClassLoaderTest.java index f8151cbe8f2c..d42dc59679dc 100644 --- a/platform/util/testSrc/com/intellij/util/lang/test/UrlClassLoaderTest.java +++ b/platform/util/testSrc/com/intellij/util/lang/test/UrlClassLoaderTest.java @@ -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 { diff --git a/platform/util/ui/src/com/intellij/util/ui/WaylandUtil.kt b/platform/util/ui/src/com/intellij/util/ui/WaylandUtil.kt new file mode 100644 index 000000000000..58859a898ad2 --- /dev/null +++ b/platform/util/ui/src/com/intellij/util/ui/WaylandUtil.kt @@ -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() diff --git a/platform/vcs-impl/api-dump.txt b/platform/vcs-impl/api-dump.txt index 3cf7f15b4611..13c0e7f122c1 100644 --- a/platform/vcs-impl/api-dump.txt +++ b/platform/vcs-impl/api-dump.txt @@ -755,22 +755,6 @@ a:com.intellij.openapi.vcs.changes.ChangeViewDiffRequestProcessor$Wrapper f:com.intellij.openapi.vcs.changes.ChangesPreprocess - ():V - s:preprocessChangesRemoveDeletedForDuplicateMoved(java.util.List):java.util.List -a:com.intellij.openapi.vcs.changes.ChangesTreeEditorDiffPreview -- com.intellij.openapi.vcs.changes.EditorTabDiffPreview -- (com.intellij.openapi.vcs.changes.ui.ChangesTree,javax.swing.JComponent):V -- b:(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 -- (com.intellij.openapi.project.Project):V +- (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 -- (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 - (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 - (com.intellij.openapi.vcs.changes.ui.ChangesTree,com.intellij.openapi.vcs.changes.ui.ChangesTreeDiffPreviewHandler):V - (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 - (com.intellij.openapi.project.Project):V +- (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 diff --git a/platform/vcs-impl/frontend/resources/intellij.platform.vcs.impl.frontend.xml b/platform/vcs-impl/frontend/resources/intellij.platform.vcs.impl.frontend.xml index 1372026c7620..e45ac31736c8 100644 --- a/platform/vcs-impl/frontend/resources/intellij.platform.vcs.impl.frontend.xml +++ b/platform/vcs-impl/frontend/resources/intellij.platform.vcs.impl.frontend.xml @@ -11,7 +11,8 @@ - + + diff --git a/platform/vcs-impl/frontend/src/com/intellij/platform/vcs/impl/frontend/changes/ChangesViewSplitComponentProvider.kt b/platform/vcs-impl/frontend/src/com/intellij/platform/vcs/impl/frontend/changes/ChangesViewSplitComponentProvider.kt new file mode 100644 index 000000000000..8872df310abe --- /dev/null +++ b/platform/vcs-impl/frontend/src/com/intellij/platform/vcs/impl/frontend/changes/ChangesViewSplitComponentProvider.kt @@ -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 { + override val binding: SplitComponentBinding = 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, unversionedFiles: List) { + } + }) + + scope.launch { + ChangeListsViewModel.getInstance(project).changeLists.collect { + panel.scheduleRefresh() + } + } + + return panel + } +} diff --git a/platform/vcs-impl/frontend/src/com/intellij/platform/vcs/impl/frontend/changes/LocalChangesViewContentProvider.kt b/platform/vcs-impl/frontend/src/com/intellij/platform/vcs/impl/frontend/changes/LocalChangesViewContentProvider.kt deleted file mode 100644 index 0f840e9c552a..000000000000 --- a/platform/vcs-impl/frontend/src/com/intellij/platform/vcs/impl/frontend/changes/LocalChangesViewContentProvider.kt +++ /dev/null @@ -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, unversionedFiles: List) { - } - }) - - content.component = panel - - project.service().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) -} diff --git a/platform/vcs-impl/shared/src/com/intellij/openapi/vcs/changes/ChangesViewId.kt b/platform/vcs-impl/shared/src/com/intellij/openapi/vcs/changes/ChangesViewId.kt new file mode 100644 index 000000000000..4afb5759033c --- /dev/null +++ b/platform/vcs-impl/shared/src/com/intellij/openapi/vcs/changes/ChangesViewId.kt @@ -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 = SplitComponentBinding("ChangesView", ::ChangesViewId) + +@Serializable +@ApiStatus.Internal +data class ChangesViewId(override val uid: UID) : Id diff --git a/platform/vcs-impl/shared/src/com/intellij/openapi/vcs/changes/ChangesViewPanel.kt b/platform/vcs-impl/shared/src/com/intellij/openapi/vcs/changes/ChangesViewPanel.kt index b9e0cbf3dc3d..2c6e912b0546 100644 --- a/platform/vcs-impl/shared/src/com/intellij/openapi/vcs/changes/ChangesViewPanel.kt +++ b/platform/vcs-impl/shared/src/com/intellij/openapi/vcs/changes/ChangesViewPanel.kt @@ -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 } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewUtil.kt b/platform/vcs-impl/shared/src/com/intellij/openapi/vcs/changes/ChangesViewUtil.kt similarity index 58% rename from platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewUtil.kt rename to platform/vcs-impl/shared/src/com/intellij/openapi/vcs/changes/ChangesViewUtil.kt index 937a325475e8..60e99deb0cd2 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewUtil.kt +++ b/platform/vcs-impl/shared/src/com/intellij/openapi/vcs/changes/ChangesViewUtil.kt @@ -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, unversionedFiles: List, - showIgnoredFiles: Boolean, + ignoredFiles: List, 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 = changeListManager.getIgnoredFilePaths() - treeModelBuilder.setIgnored(ignoredFilePaths) - } - if (shouldShowUntrackedLoading) { - treeModelBuilder.insertSubtreeRoot(ChangesBrowserUnversionedLoadingPendingNode()) - } + treeModelBuilder.setIgnored(ignoredFiles) for (extension in ChangesViewModifier.KEY.getExtensions(project)) { try { diff --git a/platform/vcs-impl/shared/src/com/intellij/openapi/vcs/changes/CommitChangesViewWithToolbarPanel.kt b/platform/vcs-impl/shared/src/com/intellij/openapi/vcs/changes/CommitChangesViewWithToolbarPanel.kt index 060a63b083ba..4213b1bf9b2a 100644 --- a/platform/vcs-impl/shared/src/com/intellij/openapi/vcs/changes/CommitChangesViewWithToolbarPanel.kt +++ b/platform/vcs-impl/shared/src/com/intellij/openapi/vcs/changes/CommitChangesViewWithToolbarPanel.kt @@ -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().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, unversionedFiles: List) - class ExtendedTreeModel(val changeLists: List, val unversionedFiles: List, val treeModel: DefaultTreeModel) + class ModelData(val changeLists: List, + val unversionedFiles: List, + val ignoredFiles: List, + val isAllowExcludeFromCommit: () -> Boolean) } interface Initializer { @@ -206,7 +215,4 @@ class CommitChangesViewWithToolbarPanel(changesView: ChangesListView, parentDisp internal val EP_NAME = ExtensionPointName("com.intellij.vcs.commitChangesViewInitializer") } } - - @Service(Service.Level.PROJECT) - internal class ScopeProvider(val cs: CoroutineScope) -} \ No newline at end of file +} diff --git a/platform/vcs-impl/shared/src/com/intellij/platform/vcs/impl/shared/SingleTaskRunner.kt b/platform/vcs-impl/shared/src/com/intellij/platform/vcs/impl/shared/SingleTaskRunner.kt index dff47ebea0ed..326a614c45ce 100644 --- a/platform/vcs-impl/shared/src/com/intellij/platform/vcs/impl/shared/SingleTaskRunner.kt +++ b/platform/vcs-impl/shared/src/com/intellij/platform/vcs/impl/shared/SingleTaskRunner.kt @@ -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(capacity = Channel.CONFLATED) + fun getIdleFlow(): Flow = 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() + + fun delayedTaskRunner(cs: CoroutineScope, delay: Duration, task: suspend () -> Unit): SingleTaskRunner = SingleTaskRunner(cs) { + delay(delay) + task() + } } } \ No newline at end of file diff --git a/platform/vcs-impl/shared/src/com/intellij/platform/vcs/impl/shared/changes/ChangeListsViewModel.kt b/platform/vcs-impl/shared/src/com/intellij/platform/vcs/impl/shared/changes/ChangeListsViewModel.kt index 3b6d91481528..7b6f4206dc8c 100644 --- a/platform/vcs-impl/shared/src/com/intellij/platform/vcs/impl/shared/changes/ChangeListsViewModel.kt +++ b/platform/vcs-impl/shared/src/com/intellij/platform/vcs/impl/shared/changes/ChangeListsViewModel.kt @@ -22,7 +22,7 @@ class ChangeListsViewModel( ) { val areChangeListsEnabled: StateFlow = changeListsApiFlow(checkRegistry = false) { api, projectId -> emitAll(api.areChangeListsEnabled(projectId)) - }.stateIn(cs, SharingStarted.Eagerly, false) + }.stateIn(cs, SharingStarted.Eagerly, true) val changeListManagerState: StateFlow = changeListsApiFlow(checkRegistry = false) { api, projectId -> emitAll(api.getChangeListManagerState(projectId)) diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java index 00df025bb963..0f1b4d0e1dc8 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangeListManagerImpl.java @@ -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()); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesTreeEditorDiffPreview.kt b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesTreeEditorDiffPreview.kt deleted file mode 100644 index 7ebc64eb9df3..000000000000 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesTreeEditorDiffPreview.kt +++ /dev/null @@ -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() - } - } -} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewI.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewI.java index a346c1c66e26..2d8eef78d365 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewI.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewI.java @@ -27,7 +27,6 @@ public interface ChangesViewI { void selectFile(VirtualFile vFile); void selectChanges(@NotNull List changes); void updateProgressComponent(@NotNull List> progress); - void setBusy(boolean b); void setGrouping(@NotNull String groupingKey); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewManager.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewManager.java index 0dbc571a5acc..209822edfbc7 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewManager.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewManager.java @@ -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 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 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 changes) { - List 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 changeLists = changeListManager.getChangeLists(); - List 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 changeLists, - @NotNull List unversionedFiles) { - ChangesViewCommitWorkflowHandler commitWorkflowHandler = commitWorkflowHandlerSupplier.get(); - if (commitWorkflowHandler != null) { - commitWorkflowHandler.synchronizeInclusion(changeLists, unversionedFiles); - } - } - } - private static boolean isAllowExcludeFromCommit(@Nullable ChangesViewCommitWorkflowHandler handler) { return handler != null && handler.isActive(); } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewWorkflowManagerImpl.kt b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewWorkflowManagerImpl.kt index 96d29e0a784f..4f25ed86856e 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewWorkflowManagerImpl.kt +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ChangesViewWorkflowManagerImpl.kt @@ -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() diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/DummyChangesView.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/DummyChangesView.java index f1bc1734e2a7..176e5b2f1737 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/DummyChangesView.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/DummyChangesView.java @@ -48,10 +48,6 @@ class DummyChangesView implements ChangesViewEx { public void updateProgressComponent(@NotNull List> progress) { } - @Override - public void setBusy(boolean b) { - } - @Override public void setGrouping(@NotNull String groupingKey) { } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/BackendChangesViewModelBuilderService.kt b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/BackendChangesViewModelBuilderService.kt index 8118685a9390..8b18454502af 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/BackendChangesViewModelBuilderService.kt +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/BackendChangesViewModelBuilderService.kt @@ -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) diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/BackendCommitChangesViewWithToolbarInitializer.kt b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/BackendCommitChangesViewWithToolbarInitializer.kt index 8cfc7c82c501..6166a61f9400 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/BackendCommitChangesViewWithToolbarInitializer.kt +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/BackendCommitChangesViewWithToolbarInitializer.kt @@ -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()) diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/TreeHandlerDiffRequestProcessor.kt b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/TreeHandlerDiffRequestProcessor.kt index 79452a6da280..2a650762398c 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/TreeHandlerDiffRequestProcessor.kt +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/TreeHandlerDiffRequestProcessor.kt @@ -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()) { diff --git a/platform/vcs-impl/src/com/intellij/vcs/changes/BackendChangesView.kt b/platform/vcs-impl/src/com/intellij/vcs/changes/BackendChangesView.kt new file mode 100644 index 000000000000..1d4261dc700f --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/vcs/changes/BackendChangesView.kt @@ -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) +} \ No newline at end of file diff --git a/platform/vcs-impl/src/com/intellij/vcs/changes/BackendChangesViewValueIdType.kt b/platform/vcs-impl/src/com/intellij/vcs/changes/BackendChangesViewValueIdType.kt new file mode 100644 index 000000000000..46c5548adf83 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/vcs/changes/BackendChangesViewValueIdType.kt @@ -0,0 +1,8 @@ +// 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.vcs.changes.ChangesViewId +import com.intellij.platform.kernel.ids.BackendValueIdType +import com.intellij.vcs.changes.viewModel.BackendCommitChangesViewModel + +internal object BackendChangesViewValueIdType : BackendValueIdType(::ChangesViewId) \ No newline at end of file diff --git a/platform/vcs-impl/src/com/intellij/vcs/changes/viewModel/BackendCommitChangesViewModel.kt b/platform/vcs-impl/src/com/intellij/vcs/changes/viewModel/BackendCommitChangesViewModel.kt new file mode 100644 index 000000000000..ff0535956cda --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/vcs/changes/viewModel/BackendCommitChangesViewModel.kt @@ -0,0 +1,52 @@ +// 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.viewModel + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.vcs.FilePath +import com.intellij.openapi.vcs.changes.Change +import com.intellij.openapi.vcs.changes.InclusionModel +import com.intellij.openapi.vcs.changes.ui.ChangesListView +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.vcs.commit.ChangesViewCommitWorkflowHandler +import kotlinx.coroutines.flow.SharedFlow +import org.jetbrains.annotations.ApiStatus +import javax.swing.JComponent + +// TODO IJPL-173924 cleanup methods returning tree/component +internal interface BackendCommitChangesViewModel { + val inclusionChanged: SharedFlow + + fun initPanel() + + fun setCommitWorkflowHandler(handler: ChangesViewCommitWorkflowHandler?) + + fun setToolbarHorizontal(horizontal: Boolean) + fun getActions(): List + fun isModelUpdateInProgress(): Boolean + + fun scheduleRefreshNow(callback: Runnable?) + fun scheduleDelayedRefresh() + fun setGrouping(groupingKey: String) + fun resetViewImmediatelyAndRefreshLater() + + fun setInclusionModel(model: InclusionModel?) + fun setShowCheckboxes(value: Boolean) + + fun getDisplayedChanges(): List + fun getIncludedChanges(): List + fun getDisplayedUnversionedFiles(): List + fun getIncludedUnversionedFiles(): List + + fun expand(item: Any) + fun select(item: Any) + fun selectFirst(items: Collection) + + fun selectFile(vFile: VirtualFile?) + fun selectChanges(changes: List) + + @ApiStatus.Obsolete + fun getTree(): ChangesListView + + @ApiStatus.Obsolete + fun getPreferredFocusableComponent(): JComponent +} diff --git a/platform/vcs-impl/src/com/intellij/vcs/changes/viewModel/BackendLocalCommitChangesViewModel.kt b/platform/vcs-impl/src/com/intellij/vcs/changes/viewModel/BackendLocalCommitChangesViewModel.kt new file mode 100644 index 000000000000..c860cf1f89e7 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/vcs/changes/viewModel/BackendLocalCommitChangesViewModel.kt @@ -0,0 +1,126 @@ +// 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.viewModel + +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.vcs.FilePath +import com.intellij.openapi.vcs.changes.* +import com.intellij.openapi.vcs.changes.ui.ChangesBrowserNode.UNVERSIONED_FILES_TAG +import com.intellij.openapi.vcs.changes.ui.ChangesListView +import com.intellij.openapi.vcs.changes.ui.VcsTreeModelData.* +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.platform.vcs.impl.shared.changes.ChangesViewSettings +import com.intellij.util.ui.tree.TreeUtil.* +import com.intellij.vcs.commit.ChangesViewCommitWorkflowHandler +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import javax.swing.JComponent +import javax.swing.tree.TreePath + +internal class BackendLocalCommitChangesViewModel(private val panel: CommitChangesViewWithToolbarPanel) : BackendCommitChangesViewModel { + private var commitWorkflowHandler: ChangesViewCommitWorkflowHandler? = null + private val _inclusionChanged = MutableSharedFlow() + + override val inclusionChanged = _inclusionChanged.asSharedFlow() + + override fun setInclusionModel(model: InclusionModel?) { + panel.changesView.setInclusionModel(model) + } + + override fun initPanel() { + panel.initPanel(ModelProvider()) + panel.changesView.setInclusionListener { _inclusionChanged.tryEmit(Unit) } + } + + override fun setCommitWorkflowHandler(handler: ChangesViewCommitWorkflowHandler?) { + commitWorkflowHandler = handler + } + + override fun setToolbarHorizontal(horizontal: Boolean) { + panel.isToolbarHorizontal = horizontal + } + + override fun getActions(): List = panel.toolbarActionGroup.getChildren(ActionManager.getInstance()).toList() + + override fun isModelUpdateInProgress(): Boolean = panel.changesView.isModelUpdateInProgress + + override fun scheduleRefreshNow(callback: Runnable?) { + panel.scheduleRefreshNow(callback) + } + + override fun scheduleDelayedRefresh() { + panel.scheduleRefresh() + } + + override fun setGrouping(groupingKey: String) { + panel.setGrouping(groupingKey) + } + + override fun resetViewImmediatelyAndRefreshLater() { + panel.resetViewImmediatelyAndRefreshLater() + } + + override fun setShowCheckboxes(value: Boolean) { + panel.changesView.isShowCheckboxes = value + } + + override fun getDisplayedChanges(): List = all(panel.changesView).userObjects(Change::class.java) + + override fun getIncludedChanges(): List = included(panel.changesView).userObjects(Change::class.java) + + override fun getDisplayedUnversionedFiles(): List = allUnderTag(panel.changesView, UNVERSIONED_FILES_TAG).userObjects(FilePath::class.java) + + override fun getIncludedUnversionedFiles(): List = includedUnderTag(panel.changesView, UNVERSIONED_FILES_TAG).userObjects(FilePath::class.java) + + override fun expand(item: Any) { + val node = panel.changesView.findNodeInTree(item) + node?.let { panel.changesView.expandSafe(it) } + } + + override fun select(item: Any) { + val path = panel.changesView.findNodePathInTree(item) + path?.let { selectPath(panel.changesView, it, false) } + } + + override fun selectFirst(items: Collection) { + if (items.isEmpty()) return + val path = treePathTraverser(panel.changesView).preOrderDfsTraversal().find { getLastUserObject(it) in items } + path?.let { selectPath(panel.changesView, it, false) } + } + + override fun selectFile(vFile: VirtualFile?) { + panel.changesView.selectFile(vFile) + } + + override fun selectChanges(changes: List) { + val paths: MutableList = ArrayList() + for (change in changes) { + panel.changesView.findNodePathInTree(change)?.let { paths.add(it) } + } + selectPaths(panel.changesView, paths) + } + + override fun getTree(): ChangesListView = panel.changesView + + override fun getPreferredFocusableComponent(): JComponent = panel.changesView.preferredFocusedComponent + + private inner class ModelProvider : CommitChangesViewWithToolbarPanel.ModelProvider { + override fun getModelData(): CommitChangesViewWithToolbarPanel.ModelProvider.ModelData { + val project = panel.project + val changeListManager = ChangeListManagerImpl.getInstanceImpl(project) + val changeLists = changeListManager.changeLists + val unversionedFiles = changeListManager.unversionedFilesPaths + + val ignoredFiles = if (ChangesViewSettings.getInstance(project).showIgnored) changeListManager.ignoredFilePaths else emptyList() + return CommitChangesViewWithToolbarPanel.ModelProvider.ModelData( + changeLists, + unversionedFiles, + ignoredFiles, + ) { commitWorkflowHandler?.isActive == true } + } + + override fun synchronizeInclusion(changeLists: List, unversionedFiles: List) { + commitWorkflowHandler?.synchronizeInclusion(changeLists, unversionedFiles) + } + } +} diff --git a/platform/vcs-impl/src/com/intellij/vcs/changes/viewModel/BackendRemoteCommitChangesViewModel.kt b/platform/vcs-impl/src/com/intellij/vcs/changes/viewModel/BackendRemoteCommitChangesViewModel.kt new file mode 100644 index 000000000000..283eb59320a2 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/vcs/changes/viewModel/BackendRemoteCommitChangesViewModel.kt @@ -0,0 +1,83 @@ +// 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.viewModel + +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.project.Project +import com.intellij.openapi.vcs.FilePath +import com.intellij.openapi.vcs.changes.Change +import com.intellij.openapi.vcs.changes.InclusionModel +import com.intellij.openapi.vcs.changes.LocalChangesListView +import com.intellij.openapi.vcs.changes.ui.ChangesListView +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.vcs.commit.ChangesViewCommitWorkflowHandler +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import javax.swing.JComponent + +// TODO IJPL-173924 Propper RPC-based implementation +internal class BackendRemoteCommitChangesViewModel(private val project: Project) : BackendCommitChangesViewModel { + private var horizontal: Boolean = true + private val treeView: ChangesListView by lazy { LocalChangesListView(project) } + override val inclusionChanged = MutableSharedFlow() + + private val inclusionModel = MutableStateFlow(null) + + override fun setInclusionModel(model: InclusionModel?) { + inclusionModel.value = model + } + + override fun initPanel() { + } + + override fun setCommitWorkflowHandler(handler: ChangesViewCommitWorkflowHandler?) { + } + + override fun setToolbarHorizontal(horizontal: Boolean) { + this.horizontal = horizontal + } + + override fun getActions(): List = emptyList() + + override fun isModelUpdateInProgress(): Boolean = false + + override fun scheduleRefreshNow(callback: Runnable?) { + } + + override fun scheduleDelayedRefresh() { + } + + override fun setGrouping(groupingKey: String) { + } + + override fun resetViewImmediatelyAndRefreshLater() { + } + + override fun setShowCheckboxes(value: Boolean) {} + + override fun getDisplayedChanges(): List = emptyList() + + override fun getIncludedChanges(): List = emptyList() + + override fun getDisplayedUnversionedFiles(): List = emptyList() + + override fun getIncludedUnversionedFiles(): List = emptyList() + + override fun expand(item: Any) { + } + + override fun select(item: Any) { + } + + override fun selectFirst(items: Collection) { + } + + override fun selectFile(vFile: VirtualFile?) { + } + + override fun selectChanges(changes: List) { + } + + override fun getTree(): ChangesListView = treeView + + override fun getPreferredFocusableComponent(): JComponent = treeView.preferredFocusedComponent +} diff --git a/platform/vcs-impl/src/com/intellij/vcs/commit/ChangeListViewCommitPanel.kt b/platform/vcs-impl/src/com/intellij/vcs/commit/ChangeListViewCommitPanel.kt index f41cc07cb1d7..37c9050947dd 100644 --- a/platform/vcs-impl/src/com/intellij/vcs/commit/ChangeListViewCommitPanel.kt +++ b/platform/vcs-impl/src/com/intellij/vcs/commit/ChangeListViewCommitPanel.kt @@ -16,9 +16,7 @@ import com.intellij.openapi.vcs.changes.ui.ChangesListView import com.intellij.openapi.vcs.changes.ui.EditChangelistSupport import com.intellij.openapi.vcs.changes.ui.VcsTreeModelData.* import com.intellij.platform.vcs.impl.shared.commit.EditedCommitPresentation -import com.intellij.ui.EditorTextComponent import com.intellij.util.application -import com.intellij.util.ui.JBUI import com.intellij.util.ui.UIUtil import com.intellij.util.ui.tree.TreeUtil.* import org.jetbrains.annotations.ApiStatus @@ -29,7 +27,7 @@ abstract class ChangeListViewCommitPanel @ApiStatus.Internal constructor( project: Project, private val changesView: ChangesListView, ) : NonModalCommitPanel(project), ChangesViewCommitWorkflowUi { - private val progressPanel = ChangeListViewCommitProgressPanel(project, this, commitMessage.editorField) + private val progressPanel = CommitProgressPanel(project, this, commitMessage.editorField) private val commitActions = commitActionsPanel.createActions() private var rootComponent: JComponent? = null @@ -98,11 +96,9 @@ abstract class ChangeListViewCommitPanel @ApiStatus.Internal constructor( final override fun getIncludedUnversionedFiles(): List = includedUnderTag(changesView, UNVERSIONED_FILES_TAG).userObjects(FilePath::class.java) - final override var inclusionModel: InclusionModel? - get() = changesView.inclusionModel - set(value) { - changesView.setInclusionModel(value) - } + override fun setInclusionModel(model: InclusionModel?) { + changesView.setInclusionModel(model) + } final override val commitProgressUi: CommitProgressUi get() = progressPanel @@ -122,23 +118,3 @@ abstract class ChangeListViewCommitPanel @ApiStatus.Internal constructor( changesView.setInclusionListener(null) } } - -private class ChangeListViewCommitProgressPanel( - project: Project, - private val commitWorkflowUi: ChangesViewCommitWorkflowUi, - commitMessage: EditorTextComponent, -) : CommitProgressPanel(project) { - - private var oldInclusion: Set = emptySet() - - init { - setup(commitWorkflowUi, commitMessage, JBUI.Borders.empty()) - } - - override fun inclusionChanged() { - val newInclusion = commitWorkflowUi.inclusionModel?.getInclusion().orEmpty() - - if (oldInclusion != newInclusion) super.inclusionChanged() - oldInclusion = newInclusion - } -} diff --git a/platform/vcs-impl/src/com/intellij/vcs/commit/ChangesViewCommitPanel.kt b/platform/vcs-impl/src/com/intellij/vcs/commit/ChangesViewCommitPanel.kt index 8b6e4e48360a..b8371a5cc490 100644 --- a/platform/vcs-impl/src/com/intellij/vcs/commit/ChangesViewCommitPanel.kt +++ b/platform/vcs-impl/src/com/intellij/vcs/commit/ChangesViewCommitPanel.kt @@ -2,37 +2,135 @@ package com.intellij.vcs.commit import com.intellij.openapi.Disposable +import com.intellij.openapi.application.writeIntentReadAction +import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.vcs.FilePath import com.intellij.openapi.vcs.VcsBundle.message +import com.intellij.openapi.vcs.changes.Change import com.intellij.openapi.vcs.changes.ChangesViewManager -import com.intellij.openapi.vcs.changes.ui.ChangesListView -import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager +import com.intellij.openapi.vcs.changes.InclusionModel +import com.intellij.openapi.vcs.changes.LocalChangeList +import com.intellij.openapi.vcs.changes.ui.* import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager.Companion.LOCAL_CHANGES import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager.Companion.getToolWindowFor -import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManagerListener -import com.intellij.openapi.vcs.changes.ui.isCommitToolWindowShown import com.intellij.openapi.wm.ToolWindow +import com.intellij.platform.util.coroutines.childScope +import com.intellij.platform.vcs.impl.shared.commit.EditedCommitPresentation +import com.intellij.util.application +import com.intellij.util.ui.UIUtil +import com.intellij.vcs.VcsDisposable +import com.intellij.vcs.changes.viewModel.BackendCommitChangesViewModel import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import org.jetbrains.annotations.ApiStatus +import javax.swing.JComponent +import kotlin.properties.Delegates.observable -class ChangesViewCommitPanel @ApiStatus.Internal constructor( +class ChangesViewCommitPanel internal constructor( project: Project, - private val changesView: ChangesListView, -) : ChangeListViewCommitPanel(project, changesView) { + private val viewModel: BackendCommitChangesViewModel, +) : NonModalCommitPanel(project), ChangesViewCommitWorkflowUi { private var isHideToolWindowOnCommit = false + private val progressPanel = CommitProgressPanel(project, this, commitMessage.editorField) + private val commitActions = commitActionsPanel.createActions() + private var rootComponent: JComponent? = null + init { + val titleUpdater = ChangesViewCommitTabTitleUpdater(project) + titleUpdater.initSubscription(this) + titleUpdater.updateTitle() + + Disposer.register(this, commitMessage) + setProgressComponent(progressPanel) + + for (support in EditChangelistSupport.EP_NAME.getExtensionList(project)) { + support.installSearch(commitMessage.editorField, commitMessage.editorField) + } + + val scope = VcsDisposable.getInstance(project).coroutineScope.childScope("ChangesViewCommitPanel") + Disposer.register(this) { scope.cancel() } + viewModel.inclusionChanged.onEach { writeIntentReadAction { fireInclusionChanged() } }.launchIn(scope) + + commitActionsPanel.isCommitButtonDefault = { + !progressPanel.isDumbMode && UIUtil.isFocusAncestor(rootComponent ?: component) + } + ChangesViewCommitTabTitleUpdater(project).initSubscription(this) } override val isActive: Boolean get() = component.isVisible + @ApiStatus.Internal + fun registerRootComponent(newRootComponent: JComponent) { + logger().assertTrue(rootComponent == null) + rootComponent = newRootComponent + commitActions.forEach { it.registerCustomShortcutSet(newRootComponent, this) } + } + + override var editedCommit: EditedCommitPresentation? by observable(null) { _, _, newValue -> + ChangesViewManager.getInstanceEx(project).scheduleRefresh { + application.invokeLater { newValue?.let { expand(it) } } + } + } + + override fun expand(item: Any) { + viewModel.expand(item) + } + + override fun select(item: Any) { + viewModel.select(item) + } + + override fun selectFirst(items: Collection) { + viewModel.selectFirst(items) + } + + override fun setCompletionContext(changeLists: List) { + commitMessage.setChangesSupplier(ChangeListChangesSupplier(changeLists)) + } + + override fun getDisplayedChanges(): List = viewModel.getDisplayedChanges() + override fun getIncludedChanges(): List = viewModel.getIncludedChanges() + + override fun getDisplayedUnversionedFiles(): List = + viewModel.getDisplayedUnversionedFiles() + + override fun getIncludedUnversionedFiles(): List = + viewModel.getIncludedUnversionedFiles() + + override fun setInclusionModel(model: InclusionModel?) { + viewModel.setInclusionModel(model) + } + + override val commitProgressUi: CommitProgressUi get() = progressPanel + + override fun endExecution(): Unit = closeEditorPreviewIfEmpty() + + private fun closeEditorPreviewIfEmpty() { + val changesViewManager = ChangesViewManager.getInstance(project) as? ChangesViewManager ?: return + ChangesViewManager.getInstanceEx(project).scheduleRefresh { + application.invokeLater { + changesViewManager.closeEditorPreview(true) + } + } + } + + override fun dispose() { + super.dispose() + viewModel.setShowCheckboxes(false) + } + override fun activate(): Boolean { val toolWindow = getVcsToolWindow() ?: return false val contentManager = ChangesViewContentManager.getInstance(project) saveToolWindowState() - changesView.isShowCheckboxes = true + viewModel.setShowCheckboxes(true) component.isVisible = true commitActionsPanel.isActive = true @@ -49,7 +147,7 @@ class ChangesViewCommitPanel @ApiStatus.Internal constructor( } clearToolWindowState() - changesView.isShowCheckboxes = false + viewModel.setShowCheckboxes(false) component.isVisible = false commitActionsPanel.isActive = false @@ -81,9 +179,18 @@ private class ChangesViewCommitTabTitleUpdater(private val project: Project) : C } override fun toolWindowMappingChanged() { + updateTitle() + } + + fun updateTitle() { val tabContent = ChangesViewContentManager.getInstance(project).findContent(LOCAL_CHANGES) if (tabContent != null) { - tabContent.displayName = if (project.isCommitToolWindowShown) message("tab.title.commit") else message("local.changes.tab") - } + val contentsCount = getToolWindowFor(project, LOCAL_CHANGES)?.contentManager?.contentCount ?: 0 + + tabContent.displayName = when { + contentsCount == 1 -> null + project.isCommitToolWindowShown -> message("tab.title.commit") + else ->message("local.changes.tab") + }} } } diff --git a/platform/vcs-impl/src/com/intellij/vcs/commit/ChangesViewCommitWorkflowHandler.kt b/platform/vcs-impl/src/com/intellij/vcs/commit/ChangesViewCommitWorkflowHandler.kt index 749470195150..505844f4911b 100644 --- a/platform/vcs-impl/src/com/intellij/vcs/commit/ChangesViewCommitWorkflowHandler.kt +++ b/platform/vcs-impl/src/com/intellij/vcs/commit/ChangesViewCommitWorkflowHandler.kt @@ -66,8 +66,8 @@ class ChangesViewCommitWorkflowHandler( ui.addExecutorListener(this, this) ui.addDataProvider(EdtNoGetDataProvider { sink -> uiDataSnapshot(sink) }) ui.addInclusionListener(this, this) - ui.inclusionModel = inclusionModel - Disposer.register(inclusionModel, Disposable { ui.inclusionModel = null }) + ui.setInclusionModel(inclusionModel) + Disposer.register(inclusionModel, Disposable { ui.setInclusionModel(null) }) ui.setCompletionContext(changeListManager.changeLists) setupDumbModeTracking() diff --git a/platform/vcs-impl/src/com/intellij/vcs/commit/ChangesViewCommitWorkflowUi.kt b/platform/vcs-impl/src/com/intellij/vcs/commit/ChangesViewCommitWorkflowUi.kt index eb432cc8da88..b83bc3b31ee6 100644 --- a/platform/vcs-impl/src/com/intellij/vcs/commit/ChangesViewCommitWorkflowUi.kt +++ b/platform/vcs-impl/src/com/intellij/vcs/commit/ChangesViewCommitWorkflowUi.kt @@ -12,7 +12,7 @@ interface ChangesViewCommitWorkflowUi : NonModalCommitWorkflowUi { suspend fun refreshChangesViewBeforeCommit() - var inclusionModel: InclusionModel? + fun setInclusionModel(model: InclusionModel?) fun expand(item: Any) fun select(item: Any) diff --git a/platform/vcs-impl/src/com/intellij/vcs/commit/CommitProgressPanel.kt b/platform/vcs-impl/src/com/intellij/vcs/commit/CommitProgressPanel.kt index 983e36a642d1..eed7e9de4fe0 100644 --- a/platform/vcs-impl/src/com/intellij/vcs/commit/CommitProgressPanel.kt +++ b/platform/vcs-impl/src/com/intellij/vcs/commit/CommitProgressPanel.kt @@ -81,6 +81,10 @@ private fun JBLabel.setWarning(@NlsContexts.Label warningText: String) { open class CommitProgressPanel(project: Project) : CommitProgressUi, InclusionListener, DocumentListener, Disposable { private val scope = VcsDisposable.getInstance(project).coroutineScope.childScope("CommitProgressPanel", Dispatchers.EDT) + constructor(project: Project, commitWorkflowUi: CommitWorkflowUi, commitMessage: EditorTextComponent) : this(project) { + setup(commitWorkflowUi, commitMessage, empty()) + } + private val taskInfo = CommitChecksTaskInfo() private val progressFlow = MutableStateFlow(null) private var progress: InlineCommitChecksProgressIndicator? by progressFlow::value diff --git a/platform/vcs-impl/testSrc/com/intellij/vcsUtil/SingleTaskRunnerTest.kt b/platform/vcs-impl/testSrc/com/intellij/vcsUtil/SingleTaskRunnerTest.kt index 4ccd0c69c7d0..543b38099827 100644 --- a/platform/vcs-impl/testSrc/com/intellij/vcsUtil/SingleTaskRunnerTest.kt +++ b/platform/vcs-impl/testSrc/com/intellij/vcsUtil/SingleTaskRunnerTest.kt @@ -5,18 +5,13 @@ import com.intellij.platform.util.coroutines.childScope import com.intellij.platform.vcs.impl.shared.SingleTaskRunner import com.intellij.testFramework.LoggedErrorProcessor import com.intellij.testFramework.common.timeoutRunBlocking -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.cancel +import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.first -import kotlinx.coroutines.withTimeout import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -import kotlin.time.Duration -import kotlin.time.Duration.Companion internal class SingleTaskRunnerTest { @Test @@ -43,41 +38,13 @@ internal class SingleTaskRunnerTest { } } - @Test - fun `test instant execution`() = timeoutRunBlocking { - var counter = 0 - withRunner({ counter++ }, delay = Duration.INFINITE) { - start() - request() - requestNow() - awaitNotBusy() - assertEquals(1, counter) - } - } - - @Test - fun `test delayed execution after instant`() = timeoutRunBlocking { - var counter = 0 - withRunner({ counter++ }, delay = Duration.INFINITE) { - start() - request() - requestNow() - awaitNotBusy() - request() - assertThrows { - withTimeout(100) { - awaitNotBusy() - } - } - assertEquals(1, counter) - } - } - @Test fun `test execution debounced`() = timeoutRunBlocking { var counter = 0 + val taskStarted = CompletableDeferred() val runAllowed = MutableStateFlow(false) withRunner({ + taskStarted.complete(Unit) runAllowed.first { it } counter++ }) { @@ -85,11 +52,13 @@ internal class SingleTaskRunnerTest { repeat(10) { request() + taskStarted.await() } runAllowed.value = true awaitNotBusy() - assertEquals(1, counter) + assertEquals(2, counter) + counter = 0 runAllowed.value = false request() assertThrows { @@ -97,10 +66,10 @@ internal class SingleTaskRunnerTest { awaitNotBusy() } } - assertEquals(1, counter) + assertEquals(0, counter) runAllowed.value = true awaitNotBusy() - assertEquals(2, counter) + assertEquals(1, counter) } } @@ -131,11 +100,10 @@ internal class SingleTaskRunnerTest { private inline fun CoroutineScope.withRunner( noinline task: suspend () -> Unit, - delay: Duration = Companion.ZERO, consumer: SingleTaskRunner.() -> Unit, ) { val cs = childScope("BG runner") - SingleTaskRunner(cs, delay, task).also(consumer) + SingleTaskRunner(cs, task).also(consumer) cs.cancel() } } \ No newline at end of file diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/mixedmode/XMixedModeCombinedDebugProcess.kt b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/mixedmode/XMixedModeCombinedDebugProcess.kt index b1b262aa1195..2580c0974fdc 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/mixedmode/XMixedModeCombinedDebugProcess.kt +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/mixedmode/XMixedModeCombinedDebugProcess.kt @@ -115,6 +115,11 @@ class XMixedModeCombinedDebugProcess( } override fun startPausing() { + if (!session.isMixedModeHighProcessReady) { + low.startPausing() + return + } + stateMachine.set(PauseRequested) } diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/XDebuggerExpressionComboBox.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/XDebuggerExpressionComboBox.java index e30820563c8b..ef410177f0a6 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/XDebuggerExpressionComboBox.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/XDebuggerExpressionComboBox.java @@ -7,6 +7,7 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.application.impl.InternalUICustomization; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.ex.EditorEx; @@ -63,6 +64,10 @@ public class XDebuggerExpressionComboBox extends XDebuggerEditorBase { initEditor(showEditor, languageInside); fillComboBox(); myComponent = JBUI.Panels.simplePanel().addToTop(myComboBox); + var customization = InternalUICustomization.getInstance(); + if (customization != null) { + customization.installEditorBackground(myComponent); + } setExpression(myExpression); } @@ -204,7 +209,7 @@ public class XDebuggerExpressionComboBox extends XDebuggerEditorBase { XDebuggerComboBoxEditor(boolean showMultiline, boolean languageInside) { myDelegate = new EditorComboBoxEditor(getProject(), getEditorsProvider().getFileType()) { @Override - protected void onEditorCreate(EditorEx editor) { + protected void onEditorCreate(@NotNull EditorEx editor) { editor.putUserData(DebuggerCopyPastePreprocessor.REMOVE_NEWLINES_ON_PASTE, true); prepareEditor(editor); if (showMultiline) { diff --git a/plugins/built-in-help/src/com/jetbrains/builtInHelp/BuiltInHelpManager.kt b/plugins/built-in-help/src/com/jetbrains/builtInHelp/BuiltInHelpManager.kt index 23d7def4fe29..729394357d4c 100644 --- a/plugins/built-in-help/src/com/jetbrains/builtInHelp/BuiltInHelpManager.kt +++ b/plugins/built-in-help/src/com/jetbrains/builtInHelp/BuiltInHelpManager.kt @@ -4,6 +4,7 @@ package com.jetbrains.builtInHelp import com.intellij.ide.BrowserUtil import com.intellij.ide.browsers.BrowserLauncher import com.intellij.ide.browsers.WebBrowserManager +import com.intellij.l10n.LocalizationStateService import com.intellij.openapi.application.IdeUrlTrackingParametersProvider import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.help.HelpManager @@ -17,7 +18,12 @@ import java.lang.String.valueOf import java.net.* import java.nio.charset.StandardCharsets -private val LOG = Logger.getInstance(BuiltInHelpManager::class.java) +val LOG: Logger = Logger.getInstance(BuiltInHelpManager::class.java) + +//Later we'll add more languages here, for now it's Chinese only +val localesToUrls: Map = mapOf( + "zh-cn" to "zh-cn", +) class BuiltInHelpManager : HelpManager() { @@ -64,9 +70,18 @@ class BuiltInHelpManager : HelpManager() { } else { - val activeKeymapParam = if (activeKeymap == null) "" else "&keymap=${URLEncoder.encode(activeKeymap.presentableName, StandardCharsets.UTF_8)}" + val activeKeymapParam = if (activeKeymap == null) "" + else + "&keymap=${URLEncoder.encode(activeKeymap.presentableName, StandardCharsets.UTF_8)}" - "http://127.0.0.1:${BuiltInServerOptions.getInstance().effectiveBuiltInServerPort}/help/?${ + //Determine if we need to try loading localized docs first + val selectedLocale = LocalizationStateService.getInstance()?.selectedLocale?.lowercase() + val langPart = if (localesToUrls.containsKey(selectedLocale)) { + "/" + localesToUrls[selectedLocale] + } + else "" + + "http://127.0.0.1:${BuiltInServerOptions.getInstance().effectiveBuiltInServerPort}${langPart}/help/?${ helpIdToUse }$activeKeymapParam" } diff --git a/plugins/built-in-help/src/com/jetbrains/builtInHelp/HelpContentRequestHandler.kt b/plugins/built-in-help/src/com/jetbrains/builtInHelp/HelpContentRequestHandler.kt index f5fd103defcd..41ca294bceb9 100644 --- a/plugins/built-in-help/src/com/jetbrains/builtInHelp/HelpContentRequestHandler.kt +++ b/plugins/built-in-help/src/com/jetbrains/builtInHelp/HelpContentRequestHandler.kt @@ -28,6 +28,8 @@ class HelpContentRequestHandler : HelpRequestHandlerBase() { request: FullHttpRequest, context: ChannelHandlerContext, ): Boolean { + val locale = getRequestLocalePath(request) + for (name: String in urlDecoder.parameters().keys) { val param = urlDecoder.parameters()[name] if (param != null && (param.isEmpty() || StringUtil.isEmpty(param[0]))) { @@ -43,13 +45,14 @@ class HelpContentRequestHandler : HelpRequestHandlerBase() { val response = DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.PERMANENT_REDIRECT) - var location = "http://127.0.0.1:${BuiltInServerOptions.getInstance().effectiveBuiltInServerPort}/help/${ + var location = "http://127.0.0.1:${BuiltInServerOptions.getInstance().effectiveBuiltInServerPort}${if (locale.isNotEmpty()) "/$locale" else ""}/help/${ map.getUrlForId( name ) }" - if (urlDecoder.parameters().containsKey("keymap")) location += "?keymap=${URLEncoder.encode(urlDecoder.parameters()["keymap"]!![0], StandardCharsets.UTF_8)}" + if (urlDecoder.parameters().containsKey("keymap")) + location += "?keymap=${URLEncoder.encode(urlDecoder.parameters()["keymap"]!![0], StandardCharsets.UTF_8)}" response.headers().add( "Location", @@ -63,14 +66,12 @@ class HelpContentRequestHandler : HelpRequestHandlerBase() { when (val resourceName: String = urlDecoder.path().substringAfterLast("/")) { "config.json" -> { - val configStream = ResourceUtil.getResourceAsStream( - HelpContentRequestHandler::class.java.classLoader, - "topics", "config.json" - ) + + val configContent = Utils.getResourceWithFallback("topics", resourceName, locale) @Suppress("UNCHECKED_CAST") val configJson: LinkedHashMap = jacksonObjectMapper().readValue( - configStream, + configContent, LinkedHashMap::class.java ) as LinkedHashMap diff --git a/plugins/built-in-help/src/com/jetbrains/builtInHelp/HelpRequestHandlerBase.kt b/plugins/built-in-help/src/com/jetbrains/builtInHelp/HelpRequestHandlerBase.kt index c4cf0cef2424..ca7d39f1e42e 100644 --- a/plugins/built-in-help/src/com/jetbrains/builtInHelp/HelpRequestHandlerBase.kt +++ b/plugins/built-in-help/src/com/jetbrains/builtInHelp/HelpRequestHandlerBase.kt @@ -1,7 +1,6 @@ // 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. package com.jetbrains.builtInHelp -import com.intellij.util.ResourceUtil import io.netty.channel.Channel import io.netty.handler.codec.http.FullHttpRequest import io.netty.handler.codec.http.HttpHeaders @@ -18,11 +17,17 @@ import kotlin.io.path.nameWithoutExtension abstract class HelpRequestHandlerBase : HttpRequestHandler() { @NonNls open val prefix: String = "/help/" + val supportedLocales: Set = setOf("zh-cn") override fun isAccessible(request: HttpRequest): Boolean { return super.isAccessible(request) && request.uri().contains(prefix) } + fun getRequestLocalePath(request: HttpRequest): String { + val requestLocale = request.uri().substringBefore(prefix).substringAfter("/") + return if (supportedLocales.contains(requestLocale)) requestLocale else "" + } + protected fun sendResource( resourceName: String, resourceLocation: String, @@ -32,20 +37,22 @@ abstract class HelpRequestHandlerBase : HttpRequestHandler() { ): Boolean { val isImage = resourceLocation.contains("/img/") + + //We don't ship dark images because of storage concerns, yet frontend might still request them, and to avoid 404 we provide fallback val retrieveName = when (isImage) { true -> { val base = Path(resourceName) val baseName = base.nameWithoutExtension """${baseName.substringBeforeLast("_dark")}.${base.extension}""" } + //For non-images it's just a base name else -> resourceName } - val resStream = ResourceUtil.getResourceAsStream( - HelpRequestHandlerBase::class.java.classLoader, - if (isImage) "images" else "topics", retrieveName - ) + + val content = Utils.getResourceWithFallback(if (isImage) "images" else "topics", + retrieveName, getRequestLocalePath(request)) return sendData( - resStream?.readAllBytes() ?: throw Exception("$resourceName not found in $resourceLocation via ${request.uri()}"), resourceName, + content ?: throw Exception("$resourceName not found in $resourceLocation via ${request.uri()}"), resourceName, request, channel, extraHeaders ) diff --git a/plugins/built-in-help/src/com/jetbrains/builtInHelp/HelpSearchRequestHandler.kt b/plugins/built-in-help/src/com/jetbrains/builtInHelp/HelpSearchRequestHandler.kt index 3a8266ae2a16..c004b885ba80 100644 --- a/plugins/built-in-help/src/com/jetbrains/builtInHelp/HelpSearchRequestHandler.kt +++ b/plugins/built-in-help/src/com/jetbrains/builtInHelp/HelpSearchRequestHandler.kt @@ -11,16 +11,24 @@ import org.jetbrains.annotations.NonNls class HelpSearchRequestHandler : HelpRequestHandlerBase() { @NonNls override val prefix: String = "/search/" - override fun process(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): Boolean { + + override fun process( + urlDecoder: QueryStringDecoder, + request: FullHttpRequest, + context: ChannelHandlerContext, + ): Boolean { val query = urlDecoder.parameters()["query"]?.get(0) val maxHitsParam = urlDecoder.parameters()["maxHits"]?.get(0) - sendData(HelpSearch.search(query, if (maxHitsParam != null) Integer.parseInt(maxHitsParam) else 100).encodeToByteArray(), - "data.json", - request, - context.channel(), - request.headers()) + sendData( + HelpSearch.search(query, + if (maxHitsParam != null) Integer.parseInt(maxHitsParam) else 100, + getRequestLocalePath(request)).encodeToByteArray(), + "data.json", + request, + context.channel(), + request.headers()) return true } diff --git a/plugins/built-in-help/src/com/jetbrains/builtInHelp/Utils.kt b/plugins/built-in-help/src/com/jetbrains/builtInHelp/Utils.kt index 01693f6e0cab..87cbe9284945 100644 --- a/plugins/built-in-help/src/com/jetbrains/builtInHelp/Utils.kt +++ b/plugins/built-in-help/src/com/jetbrains/builtInHelp/Utils.kt @@ -5,6 +5,7 @@ import com.intellij.credentialStore.CredentialAttributes import com.intellij.credentialStore.Credentials import com.intellij.ide.passwordSafe.PasswordSafe import com.intellij.ide.util.PropertiesComponent +import com.intellij.util.ResourceUtil import com.jetbrains.builtInHelp.settings.SettingsPage import org.jetbrains.annotations.NonNls @@ -12,11 +13,43 @@ class Utils { companion object { @NonNls private const val EMPTY_STRING = "" + @NonNls - const val BASE_HELP_URL = "https://www.jetbrains.com/" + const val BASE_HELP_URL: String = "https://www.jetbrains.com/" private val secureKey = CredentialAttributes("Web Help Bundle") + fun getResourceWithFallback(resPath: String, resName: String, localePath: String): ByteArray? { + + val resStream = ResourceUtil.getResourceAsStream( + HelpRequestHandlerBase::class.java.classLoader, + "${if (localePath.isNotEmpty()) "$localePath/" else ""}$resPath", resName + ) + + return resStream.use { res -> + try { + res.readAllBytes() + } + catch (e: Exception) { + LOG.warn("$localePath resource failed", e) + //So we couldn't find any localized resource, try the default English one + ResourceUtil.getResourceAsStream( + HelpRequestHandlerBase::class.java.classLoader, + resPath, resName + ).use { res -> + try { + res.readAllBytes() + } + catch (e: Exception) { + LOG.warn("Fallback resource failed", e) + null + } + } + } + } + } + + @JvmStatic @NonNls fun getStoredValue(key: SettingsPage.SettingKey, default: String): String { @@ -30,14 +63,14 @@ class Utils { } @JvmStatic - fun setStoredValue(key: SettingsPage.SettingKey, value: String?): Boolean { + fun setStoredValue(key: SettingsPage.SettingKey, value: String?) { if (!key.second) { PropertiesComponent.getInstance().setValue(key.first, value, EMPTY_STRING) - return true } - val passwordSafe = PasswordSafe.instance - passwordSafe.set(secureKey, Credentials(key.first, value)) - return true + else { + val passwordSafe = PasswordSafe.instance + passwordSafe.set(secureKey, Credentials(key.first, value)) + } } } } \ No newline at end of file diff --git a/plugins/built-in-help/src/com/jetbrains/builtInHelp/search/HelpSearch.kt b/plugins/built-in-help/src/com/jetbrains/builtInHelp/search/HelpSearch.kt index 05b6c89cabcc..9b970acabd71 100644 --- a/plugins/built-in-help/src/com/jetbrains/builtInHelp/search/HelpSearch.kt +++ b/plugins/built-in-help/src/com/jetbrains/builtInHelp/search/HelpSearch.kt @@ -1,10 +1,12 @@ // Copyright 2000-2022 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. +@file:OptIn(ExperimentalPathApi::class) + package com.jetbrains.builtInHelp.search import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.intellij.openapi.diagnostic.Logger -import com.intellij.util.ResourceUtil import com.intellij.util.io.safeOutputStream +import com.jetbrains.builtInHelp.Utils import org.apache.lucene.analysis.standard.StandardAnalyzer import org.apache.lucene.index.DirectoryReader import org.apache.lucene.queryparser.simple.SimpleQueryParser @@ -17,11 +19,14 @@ import org.apache.lucene.store.NIOFSDirectory import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NotNull import java.io.BufferedReader +import java.io.ByteArrayInputStream import java.io.InputStreamReader import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths import java.util.* +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.deleteRecursively class HelpSearch { @@ -32,10 +37,10 @@ class HelpSearch { private val analyzer: StandardAnalyzer = StandardAnalyzer() @NotNull - fun search(query: String?, maxHits: Int): String { + fun search(query: String?, maxHits: Int, locale: String): String { if (query != null) { - val indexDir: Path? = Files.createTempDirectory("search-index") + val indexDir: Path? = Files.createTempDirectory("${locale}-search-index") var indexDirectory: NIOFSDirectory? = null var reader: DirectoryReader? = null @@ -44,22 +49,23 @@ class HelpSearch { val indexDirPath = indexDir.toAbsolutePath().toString() Files.createDirectories(indexDir) - //Read required names from rlist and then load resources based off of them - ResourceUtil.getResourceAsStream(HelpSearch::class.java.classLoader, "search", "rlist") - .use { resourceList -> - BufferedReader(InputStreamReader(resourceList)).useLines { lines -> - lines.forEach { line -> - val path = Paths.get(indexDirPath, line) - ResourceUtil.getResourceAsStream(HelpSearch::class.java.classLoader, - "search", line) - ?.use { resourceStream -> - path.safeOutputStream().use { resourceOutput -> - resourceOutput.write(resourceStream.readAllBytes()) - } - } - } + //Read required names from the rlist file and then load resources based off of them + val searchList = Utils.getResourceWithFallback("search", "rlist", locale) + ?: return NOT_FOUND + + BufferedReader(InputStreamReader(ByteArrayInputStream(searchList))).useLines { lines -> + lines.forEach { line -> + val path = Paths.get(indexDirPath, line) + + val dataToDeploy = Utils.getResourceWithFallback("search", line, locale) + ?: return@forEach + + path.safeOutputStream().use { resourceOutput -> + resourceOutput.write(dataToDeploy) } + } + } indexDirectory = NIOFSDirectory(indexDir) reader = DirectoryReader.open(indexDirectory) @@ -111,7 +117,7 @@ class HelpSearch { finally { indexDirectory?.close() reader?.close() - indexDir.toFile().deleteRecursively() + indexDir.deleteRecursively() } } return NOT_FOUND diff --git a/plugins/devkit/devkit-core/resources/inspectionDescriptions/PluginKeysAreProhibitedInPlatformRegistry.html b/plugins/devkit/devkit-core/resources/inspectionDescriptions/PluginKeysAreProhibitedInPlatformRegistry.html new file mode 100644 index 000000000000..78dcc4d4e356 --- /dev/null +++ b/plugins/devkit/devkit-core/resources/inspectionDescriptions/PluginKeysAreProhibitedInPlatformRegistry.html @@ -0,0 +1,5 @@ + + +Highlights keys in `registry.properties` that are not allowed in the platform registry and must be moved to plugins. + + \ No newline at end of file diff --git a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml index b55e00b76652..f72d620f78ab 100644 --- a/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml +++ b/plugins/devkit/devkit-core/resources/intellij.devkit.core.xml @@ -551,7 +551,8 @@ key="inspection.content.module.namespace.name"/> - + () { return findDescriptorInModuleLibraries(currentModule, descriptorFileName) } + override fun getPsiElement(resolvedValue: IdeaPlugin?): PsiElement? { + return resolvedValue?.xmlElement?.containingFile + } + + override fun handleElementRename(genericValue: GenericDomValue?, context: ConvertContext?, newElementName: String) { + super.handleElementRename(genericValue, context, newElementName.removeSuffix(".xml")) + } + override fun toString(plugin: IdeaPlugin?, context: ConvertContext): String? { if (plugin == null) return null return getDisplayName(plugin) diff --git a/plugins/devkit/devkit-core/src/inspections/RegistryPropertiesAnnotator.kt b/plugins/devkit/devkit-core/src/inspections/RegistryPropertiesAnnotator.kt index 29434df24d81..ba42373d2f55 100644 --- a/plugins/devkit/devkit-core/src/inspections/RegistryPropertiesAnnotator.kt +++ b/plugins/devkit/devkit-core/src/inspections/RegistryPropertiesAnnotator.kt @@ -2,36 +2,27 @@ package org.jetbrains.idea.devkit.inspections -import com.intellij.codeInsight.intention.IntentionAction import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo import com.intellij.codeInsight.intention.preview.IntentionPreviewUtils -import com.intellij.lang.annotation.AnnotationHolder -import com.intellij.lang.annotation.Annotator -import com.intellij.lang.annotation.HighlightSeverity -import com.intellij.lang.properties.psi.PropertiesFile +import com.intellij.codeInspection.* +import com.intellij.lang.properties.PropertiesImplUtil import com.intellij.lang.properties.psi.impl.PropertyImpl import com.intellij.lang.properties.psi.impl.PropertyKeyImpl -import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.IntelliJProjectUtil import com.intellij.openapi.project.Project import com.intellij.openapi.ui.Messages import com.intellij.psi.PsiElement +import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiFile -import com.intellij.util.IncorrectOperationException import com.intellij.util.PsiNavigateUtil import org.jetbrains.annotations.Nls import org.jetbrains.annotations.NonNls import org.jetbrains.idea.devkit.DevKitBundle import java.util.* -@NonNls internal const val REGISTRY_PROPERTIES_FILENAME = "registry.properties" - -@NonNls internal const val DESCRIPTION_SUFFIX = ".description" - -@NonNls internal const val RESTART_REQUIRED_SUFFIX = ".restartRequired" internal fun isImplicitUsageKey(keyName: String): Boolean { @@ -39,120 +30,120 @@ internal fun isImplicitUsageKey(keyName: String): Boolean { keyName.endsWith(RESTART_REQUIRED_SUFFIX) } +private val PLUGIN_GROUP_NAMES = setOf( + "appcode", "cidr", "clion", + "cvs", "git", "github", "svn", "hg4idea", "tfs", + "dart", "markdown", + "java", "javac", "uast", "junit4", "dsm", + "js", "javascript", "typescript", "nodejs", "eslint", "jest", + "ruby", "rubymine", + "groovy", "grails", "python", "php", + "kotlin", "spring", "jupyter", "dataspell", "javafx", + "maven", "gradle", "android", "eclipse", + "rider", "compiler" +) + internal fun isRegistryPropertiesFile(psiFile: PsiFile): Boolean { return IntelliJProjectUtil.isIntelliJPlatformProject(psiFile.project) && psiFile.name == REGISTRY_PROPERTIES_FILENAME } -/** - * Highlights key in `registry.properties` without matching `key.description` entry + corresponding quickfix. - */ -private class RegistryPropertiesAnnotator : Annotator, DumbAware { - @NonNls - private val PLUGIN_GROUP_NAMES = setOf( - "appcode", "cidr", "clion", - "cvs", "git", "github", "svn", "hg4idea", "tfs", - "dart", "markdown", - "java", "javac", "uast", "junit4", "dsm", - "js", "javascript", "typescript", "nodejs", "eslint", "jest", - "ruby", "rubymine", - "groovy", "grails", "python", "php", - "kotlin", "spring", "jupyter", "dataspell", "javafx", - "maven", "gradle", "android", "eclipse" - ) +internal class RegistryPropertiesInspection : LocalInspectionTool() { + override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor { + if (!isRegistryPropertiesFile(holder.file)) return PsiElementVisitor.EMPTY_VISITOR - override fun annotate(element: PsiElement, holder: AnnotationHolder) { - if (element !is PropertyKeyImpl) return + val propertiesFile = PropertiesImplUtil.getPropertiesFile(holder.file) - val file = holder.currentAnnotationSession.file - if (!isRegistryPropertiesFile(file)) { - return - } + return object : PsiElementVisitor() { + override fun visitElement(element: PsiElement) { + if (element !is PropertyKeyImpl) return - val propertyName = element.text - if (isImplicitUsageKey(propertyName)) { - return - } + val propertyName = element.text + if (isImplicitUsageKey(propertyName)) { + return + } - val groupName = propertyName.substringBefore('.').lowercase(Locale.getDefault()) + val groupName = propertyName.substringBefore('.').lowercase(Locale.getDefault()) - if (PLUGIN_GROUP_NAMES.contains(groupName) || - propertyName.startsWith("editor.config.") || - propertyName.startsWith("debugger.kotlin.") || - propertyName.startsWith("debugger.enable.kotlin.") || - propertyName.startsWith("ide.java.") || - propertyName.startsWith("execution.java.") || - propertyName.startsWith("debugger.log.jdi.")) { + if (PLUGIN_GROUP_NAMES.contains(groupName) || + propertyName.startsWith("editor.config.") || + propertyName.startsWith("debugger.kotlin.") || + propertyName.startsWith("debugger.enable.kotlin.") || + propertyName.startsWith("ide.java.") || + propertyName.startsWith("ide.jvm.") || + propertyName.startsWith("idea.profiler.") || + propertyName.startsWith("execution.java.") || + propertyName.startsWith("debugger.log.jdi.")) { - holder.newAnnotation(HighlightSeverity.ERROR, DevKitBundle.message("registry.properties.annotator.plugin.keys.use.ep")) - .withFix(ShowEPDeclarationIntention(propertyName)).create() - } + holder.registerProblem( + element, + DevKitBundle.message("registry.properties.annotator.plugin.keys.use.ep"), + ShowEPDeclarationIntention(propertyName, element) + ) + } - val propertiesFile = file as PropertiesFile - val descriptionProperty = propertiesFile.findPropertyByKey(propertyName + DESCRIPTION_SUFFIX) - if (descriptionProperty == null) { - holder.newAnnotation(HighlightSeverity.WARNING, - DevKitBundle.message("registry.properties.annotator.key.no.description.key", propertyName)) - .withFix(AddDescriptionKeyIntention(propertyName)).create() + val descriptionProperty = propertiesFile?.findPropertyByKey(propertyName + DESCRIPTION_SUFFIX) + if (descriptionProperty == null) { + holder.registerProblem( + element, + DevKitBundle.message("registry.properties.annotator.key.no.description.key", propertyName), + ProblemHighlightType.WARNING, + AddDescriptionKeyIntention(propertyName, element) + ) + } + } } } +} - private class ShowEPDeclarationIntention(private val propertyName: String) : IntentionAction, DumbAware { - override fun startInWriteAction(): Boolean = false +private class ShowEPDeclarationIntention(private val propertyName: String, element: PsiElement) : LocalQuickFixOnPsiElement(element), DumbAware { + override fun startInWriteAction(): Boolean = false + override fun getFamilyName(): String = DevKitBundle.message("registry.properties.annotator.show.ep.family.name") + override fun getText(): String = DevKitBundle.message("registry.properties.annotator.show.ep.name", propertyName) - override fun generatePreview(project: Project, editor: Editor, psiFile: PsiFile): IntentionPreviewInfo { - return IntentionPreviewInfo.EMPTY + override fun invoke(project: Project, psiFile: PsiFile, startElement: PsiElement, endElement: PsiElement) { + val propertiesFile = psiFile.let { PropertiesImplUtil.getPropertiesFile(it) } ?: return + val defaultValue = propertiesFile.findPropertyByKey(propertyName)?.value + val description = propertiesFile.findPropertyByKey(propertyName + DESCRIPTION_SUFFIX)?.value + @NonNls var restartRequiredText = "" + if (propertiesFile.findPropertyByKey(propertyName + RESTART_REQUIRED_SUFFIX) != null) { + restartRequiredText = "restartRequired=\"true\"" } - override fun getFamilyName(): String = DevKitBundle.message("registry.properties.annotator.show.ep.family.name") - - override fun isAvailable(project: Project, editor: Editor?, psiFile: PsiFile?): Boolean = true - - override fun getText(): String = DevKitBundle.message("registry.properties.annotator.show.ep.name", propertyName) - - override fun invoke(project: Project, editor: Editor?, psiFile: PsiFile?) { - val propertiesFile = psiFile as PropertiesFile - val defaultValue = propertiesFile.findPropertyByKey(propertyName)!!.value - val description = propertiesFile.findPropertyByKey(propertyName + DESCRIPTION_SUFFIX)?.value - @NonNls var restartRequiredText = "" - if (propertiesFile.findPropertyByKey(propertyName + RESTART_REQUIRED_SUFFIX) != null) { - restartRequiredText = "restartRequired=\"true\"" - } - - val epText = """ + val epText = """ """.trimIndent() - Messages.showMultilineInputDialog(project, - DevKitBundle.message("registry.properties.annotator.show.ep.message"), - DevKitBundle.message("registry.properties.annotator.show.ep.title"), - epText, null, null) - } + Messages.showMultilineInputDialog(project, + DevKitBundle.message("registry.properties.annotator.show.ep.message"), + DevKitBundle.message("registry.properties.annotator.show.ep.title"), + epText, null, null) } - private class AddDescriptionKeyIntention(private val myPropertyName: String) : IntentionAction, DumbAware { + override fun generatePreview(project: Project, previewDescriptor: ProblemDescriptor): IntentionPreviewInfo = IntentionPreviewInfo.EMPTY +} - @Nls - override fun getText(): String = DevKitBundle.message("registry.properties.annotator.add.description.text", myPropertyName) +private class AddDescriptionKeyIntention(private val myPropertyName: String, element: PsiElement) : LocalQuickFixOnPsiElement(element), DumbAware { + override fun startInWriteAction(): Boolean = true + override fun getText(): @Nls String = DevKitBundle.message("registry.properties.annotator.add.description.text", myPropertyName) + override fun getFamilyName(): @Nls String = DevKitBundle.message("registry.properties.annotator.add.description.family.name") - @Nls - override fun getFamilyName(): String = DevKitBundle.message("registry.properties.annotator.add.description.family.name") + override fun invoke(project: Project, psiFile: PsiFile, startElement: PsiElement, endElement: PsiElement) { + val propertiesFile = psiFile.let { PropertiesImplUtil.getPropertiesFile(it) } ?: return - override fun isAvailable(project: Project, editor: Editor, psiFile: PsiFile): Boolean = true + val originalProperty = propertiesFile.findPropertyByKey(myPropertyName) + val descriptionProperty = propertiesFile.addPropertyAfter(myPropertyName + DESCRIPTION_SUFFIX, "Description", originalProperty) - @Throws(IncorrectOperationException::class) - override fun invoke(project: Project, editor: Editor, psiFile: PsiFile) { - val propertiesFile = psiFile as PropertiesFile - - val originalProperty = propertiesFile.findPropertyByKey(myPropertyName) as PropertyImpl? - val descriptionProperty = propertiesFile.addPropertyAfter(myPropertyName + DESCRIPTION_SUFFIX, "Description", originalProperty) - - val valueNode = (descriptionProperty.psiElement as PropertyImpl).valueNode!! - if (!IntentionPreviewUtils.isPreviewElement(valueNode.psi)) { - PsiNavigateUtil.navigate(valueNode.psi) + val psi = descriptionProperty.psiElement + if (psi is PropertyImpl) { + val valuePsi = psi.valueNode?.psi ?: psi + if (!IntentionPreviewUtils.isPreviewElement(valuePsi)) { + PsiNavigateUtil.navigate(valuePsi) } } - - override fun startInWriteAction(): Boolean = true + else if (!IntentionPreviewUtils.isPreviewElement(psi)) { + PsiNavigateUtil.navigate(psi) + } } + override fun generatePreview(project: Project, previewDescriptor: ProblemDescriptor): IntentionPreviewInfo = IntentionPreviewInfo.EMPTY } \ No newline at end of file diff --git a/plugins/devkit/devkit-core/src/inspections/eel/UseOptimizedEelFunctions.kt b/plugins/devkit/devkit-core/src/inspections/eel/UseOptimizedEelFunctions.kt index c637650b915a..28cc83d6efb6 100644 --- a/plugins/devkit/devkit-core/src/inspections/eel/UseOptimizedEelFunctions.kt +++ b/plugins/devkit/devkit-core/src/inspections/eel/UseOptimizedEelFunctions.kt @@ -14,12 +14,16 @@ import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementVisitor import com.intellij.psi.search.GlobalSearchScope import com.intellij.uast.UastVisitorAdapter +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.VisibleForTesting +import org.jetbrains.idea.devkit.DevKitBundle import org.jetbrains.uast.* import org.jetbrains.uast.generate.getUastElementFactory import org.jetbrains.uast.generate.replace import org.jetbrains.uast.visitor.AbstractUastNonRecursiveVisitor -@Suppress("HardCodedStringLiteral") +@ApiStatus.Internal +@VisibleForTesting class UseOptimizedEelFunctions : LocalInspectionTool() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor = UastVisitorAdapter(object : AbstractUastNonRecursiveVisitor() { @@ -72,13 +76,13 @@ class UseOptimizedEelFunctions : LocalInspectionTool() { val methodPsi = node.methodIdentifier?.sourcePsi ?: return if (fqn == "java.nio.file.Files.readAllBytes") { holder.registerProblem( - methodPsi, "Works ineffectively with remote Eel", ProblemHighlightType.WARNING, + methodPsi, DevKitBundle.message("inspection.message.works.ineffectively.with.remote.eel"), ProblemHighlightType.WARNING, ReplaceWithEelFunction(holder.project, "com.intellij.platform.eel.fs.EelFiles", "readAllBytes"), ) } if (fqn == "java.nio.file.Files.readString") { holder.registerProblem( - methodPsi, "Works ineffectively with remote Eel", ProblemHighlightType.WARNING, + methodPsi, DevKitBundle.message("inspection.message.works.ineffectively.with.remote.eel"), ProblemHighlightType.WARNING, ReplaceWithEelFunction(holder.project, "com.intellij.platform.eel.fs.EelFiles", "readString"), ) } @@ -90,7 +94,7 @@ class UseOptimizedEelFunctions : LocalInspectionTool() { node.valueArgumentCount == 1 ) { holder.registerProblem( - methodPsi, "Works ineffectively with remote Eel", ProblemHighlightType.WARNING, + methodPsi, DevKitBundle.message("inspection.message.works.ineffectively.with.remote.eel"), ProblemHighlightType.WARNING, ReplaceWithEelFunction(holder.project, "com.intellij.platform.eel.fs.EelFileUtils", "deleteRecursively"), ) } @@ -108,7 +112,7 @@ class UseOptimizedEelFunctions : LocalInspectionTool() { ?.name override fun getFamilyName(): @IntentionFamilyName String = - "Replace it with Eel API" + DevKitBundle.message("intention.family.name.replace.it.with.eel.api") override fun generatePreview(project: Project, previewDescriptor: ProblemDescriptor): IntentionPreviewInfo { doTheJob(project, previewDescriptor, preview = true) diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/findUsages/PluginModuleFindUsagesTest.kt b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/findUsages/PluginModuleFindUsagesTest.kt index 666a3085a231..3da3fe36d01a 100644 --- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/findUsages/PluginModuleFindUsagesTest.kt +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/findUsages/PluginModuleFindUsagesTest.kt @@ -13,8 +13,8 @@ class PluginModuleFindUsagesTest : JavaCodeInsightFixtureTestCase() { fun `test find modules`() { addModule("intellij.test.module1") - myFixture.addXmlFile("intellij.test.module1/intellij.test.module1.xml", """ - + val moduleFile = myFixture.addXmlFile("intellij.test.module1/intellij.test.module1.xml", """ + """.trimIndent() ) @@ -37,7 +37,7 @@ class PluginModuleFindUsagesTest : JavaCodeInsightFixtureTestCase() { """.trimIndent() ) - val usages = myFixture.testFindUsages("intellij.test.module1/intellij.test.module1.xml") + val usages = myFixture.findUsages(moduleFile) assertThat(usages) .hasSize(2) } diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java index e3c61f10b721..e65a8e631b5f 100644 --- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/DevkitInspectionsRegistrationCheckTest.java @@ -12,7 +12,7 @@ import java.util.stream.Stream; public class DevkitInspectionsRegistrationCheckTest extends BasePlatformTestCase { - private static final int EXPECTED_INSPECTIONS_NUMBER = 87; + private static final int EXPECTED_INSPECTIONS_NUMBER = 88; /** * Inspections that are finished and intentionally disabled. diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/PluginConfigReferenceTest.java b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/PluginConfigReferenceTest.java index efc49df5a69b..6b1d933dec1d 100644 --- a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/PluginConfigReferenceTest.java +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/inspections/PluginConfigReferenceTest.java @@ -71,7 +71,7 @@ public class PluginConfigReferenceTest extends JavaCodeInsightFixtureTestCase { assertEquals(AllIcons.Nodes.PluginRestart, plugin.getIcon()); final LookupElementPresentation properties = getLookupElementPresentation("vcs.showConsole"); - assertEquals(" Show 'Console' tab in VCS toolwindow that logs all write-commands performed by IDE.", properties.getTailText()); + assertEquals(" Show the 'Console' tab in the VCS toolwindow that logs all write-commands performed by IDE.", properties.getTailText()); assertEquals("true", properties.getTypeText()); assertEquals(AllIcons.Nodes.Plugin, properties.getIcon()); } diff --git a/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/rename/PluginModuleRenameTest.kt b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/rename/PluginModuleRenameTest.kt new file mode 100644 index 000000000000..76f4bb9001bb --- /dev/null +++ b/plugins/devkit/devkit-java-tests/testSrc/org/jetbrains/idea/devkit/rename/PluginModuleRenameTest.kt @@ -0,0 +1,73 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package org.jetbrains.idea.devkit.rename + +import com.intellij.psi.PsiFile +import com.intellij.testFramework.PsiTestUtil +import com.intellij.testFramework.fixtures.CodeInsightTestFixture +import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase +import org.assertj.core.api.Assertions.assertThat +import org.intellij.lang.annotations.Language +import org.jetbrains.idea.devkit.module.PluginModuleType + +class PluginModuleRenameTest : JavaCodeInsightFixtureTestCase() { + + fun `test rename module file`() { + addModule("intellij.test.module1") + val moduleFile = myFixture.addXmlFile("intellij.test.module1/intellij.test.module1.xml", """ + + + """.trimIndent() + ) + + addModule("intellij.test.plugin1") + myFixture.addXmlFile("intellij.test.plugin1/plugin.xml", """ + + + + + + """.trimIndent() + ) + + addModule("intellij.test.plugin2") + myFixture.addXmlFile("intellij.test.plugin2/plugin.xml", """ + + + + + + """.trimIndent() + ) + + myFixture.renameElement(moduleFile, "intellij.test.module.renamed") + myFixture.checkResult("intellij.test.plugin1/plugin.xml", """ + + + + + + """.trimIndent(), true) + myFixture.checkResult("intellij.test.plugin2/plugin.xml", """ + + + + + + """.trimIndent(), true + ) + } + + private fun addModule(moduleName: String) { + PsiTestUtil.addModule( + project, + PluginModuleType.getInstance(), + moduleName, + myFixture.tempDirFixture.findOrCreateDir(moduleName) + ) + } + + private fun CodeInsightTestFixture.addXmlFile(relativePath: String, @Language("XML") fileText: String): PsiFile { + return this.addFileToProject(relativePath, fileText) + } + +} diff --git a/plugins/eclipse/resources/META-INF/plugin.xml b/plugins/eclipse/resources/META-INF/plugin.xml index 420f2b9439ed..3e6d2a9d03e0 100644 --- a/plugins/eclipse/resources/META-INF/plugin.xml +++ b/plugins/eclipse/resources/META-INF/plugin.xml @@ -6,8 +6,8 @@ -

  • Open Eclipse project in IntelliJ IDEA
  • -
  • Import Eclipse project into IntelliJ IDEA
  • +
  • Open an Eclipse project in IntelliJ IDEA
  • +
  • Import an Eclipse project into IntelliJ IDEA
  • Export IntelliJ IDEA project into Eclipse
  • Convert IntelliJ IDEA module to Eclipse-compatible format
  • Synchronize Eclipse project files
  • @@ -48,6 +48,7 @@ +
    diff --git a/plugins/eclipse/src/org/jetbrains/idea/eclipse/detect/EclipseProjectDetector.java b/plugins/eclipse/src/org/jetbrains/idea/eclipse/detect/EclipseProjectDetector.java index 004542433c7b..4adc89407743 100644 --- a/plugins/eclipse/src/org/jetbrains/idea/eclipse/detect/EclipseProjectDetector.java +++ b/plugins/eclipse/src/org/jetbrains/idea/eclipse/detect/EclipseProjectDetector.java @@ -11,6 +11,7 @@ import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileSystemUtil; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.impl.welcomeScreen.ProjectDetector; import com.intellij.util.ArrayUtil; @@ -53,7 +54,7 @@ public final class EclipseProjectDetector extends ProjectDetector { for (String appLocation : getStandardAppLocations()) { collectProjects(projects, Path.of(appLocation)); } - if (PropertiesComponent.getInstance().getBoolean("eclipse.scan.home.directory", true)) { + if (Registry.is("eclipse.scan.home.directory", true)) { visitFiles(new File(home), file1 -> scanForProjects(file1.getPath(), projects), 2); } } @@ -80,7 +81,7 @@ public final class EclipseProjectDetector extends ProjectDetector { ProjectGroup group = ContainerUtil.find(manager.getGroups(), g -> groupName.equals(g.getName())); String property = "eclipse.projects.detected"; if (group == null && PropertiesComponent.getInstance().isValueSet(property)) { - // the group was removed by user + // the user removed the group return; } diff --git a/plugins/evaluation-plugin/languages/java/src/com/intellij/cce/java/test/JavaTestRunner.kt b/plugins/evaluation-plugin/languages/java/src/com/intellij/cce/java/test/JavaTestRunner.kt index 36751b754ff1..6eab216690d2 100644 --- a/plugins/evaluation-plugin/languages/java/src/com/intellij/cce/java/test/JavaTestRunner.kt +++ b/plugins/evaluation-plugin/languages/java/src/com/intellij/cce/java/test/JavaTestRunner.kt @@ -35,7 +35,8 @@ class JavaTestRunner : TestRunner { } .groupBy { it.first } .map { - ModuleTests(it.key, it.value + val module = if(it.key == "src") null else it.key + ModuleTests(module, it.value .map { it.second } .filter { it != "gradle_test_execution" }) // just a command to run all the tests (no specific) } diff --git a/plugins/evaluation-plugin/languages/java/src/com/intellij/cce/java/test/JavaTestRunnerForMaven.kt b/plugins/evaluation-plugin/languages/java/src/com/intellij/cce/java/test/JavaTestRunnerForMaven.kt index 44c5cc5e1bd9..219ce9fe495d 100644 --- a/plugins/evaluation-plugin/languages/java/src/com/intellij/cce/java/test/JavaTestRunnerForMaven.kt +++ b/plugins/evaluation-plugin/languages/java/src/com/intellij/cce/java/test/JavaTestRunnerForMaven.kt @@ -1,19 +1,16 @@ package com.intellij.cce.java.test -import com.intellij.cce.test.TestNotFoundException import com.intellij.cce.test.TestRunResult -import com.intellij.openapi.application.smartReadAction -import com.intellij.openapi.module.ModuleUtil import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir -import com.intellij.psi.JavaPsiFacade -import com.intellij.psi.search.GlobalSearchScope import org.jetbrains.idea.maven.execution.MavenRunConfigurationType import org.jetbrains.idea.maven.execution.MavenRunnerParameters import org.jetbrains.idea.maven.execution.MavenRunnerSettings -import org.jetbrains.idea.maven.project.MavenProjectsManager +/** + * Just a maven runner. + */ internal object JavaTestRunnerForMaven { suspend fun run(project: Project, moduleTests: List): TestRunResult { @@ -28,10 +25,8 @@ internal object JavaTestRunnerForMaven { params.cmdOptions = "-am" - val resolvedModuleTests = resolveMavenProjects(project, moduleTests) - // in multi-module projects tests will be prefixed with module:test - resolvedModuleTests + moduleTests .mapNotNull { it.module } .also { if (it.isNotEmpty()) { @@ -46,7 +41,7 @@ internal object JavaTestRunnerForMaven { "surefire.failIfNoSpecifiedTests" to "false", "failIfNoTests" to "false", "maven.gitcommitid.skip" to "true", - "test" to resolvedModuleTests.flatMap { it.tests }.joinToString(separator = ",") + "test" to moduleTests.flatMap { it.tests }.joinToString(separator = ",") ) } @@ -62,7 +57,7 @@ internal object JavaTestRunnerForMaven { results.exitCode, passed, failed, - resolvedModuleTests.flatMap { it.tests }, + moduleTests.flatMap { it.tests }, compilationSuccessful, projectIsResolvable, output @@ -129,42 +124,4 @@ object MavenOutputParser { } return res } -} - -private suspend fun resolveMavenProjects(project: Project, moduleTests: List): List { - val result = mutableMapOf>() - - smartReadAction(project) { - for ((priorProject, tests) in moduleTests) { - for (test in tests) { - val (guessedProject, test) = tryGuessProject(project, priorProject, test) - if (!result.containsKey(guessedProject)) { - result[guessedProject] = mutableListOf() - } - result[guessedProject]!!.add(test) - } - } - } - - return result.map { ModuleTests(it.key, it.value.distinct()) } -} - -private fun tryGuessProject(project: Project, priorProject: String?, test: String): Pair { - if (priorProject != null) { - return Pair(priorProject, test) - } - - val facade = JavaPsiFacade.getInstance(project) - val scope = GlobalSearchScope.projectScope(project) - val psiClass = facade.findClass(test, scope) ?: facade.findClass(test.split('.').dropLast(1).joinToString("."), scope) - - if (psiClass == null) { - throw TestNotFoundException("Not found class for test: $priorProject:$test") - } - - val module = ModuleUtil.findModuleForFile(psiClass.containingFile) - val mavenProject = module?.let { MavenProjectsManager.getInstance(project).findProject(it) } - - val guessedProject = if (mavenProject == null) null else ":${mavenProject.mavenId.artifactId}" - return Pair(guessedProject, psiClass.qualifiedName!!) } \ No newline at end of file diff --git a/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.kt b/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.kt index a6763757ca15..6f64c7997162 100644 --- a/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.kt +++ b/plugins/git4idea/src/git4idea/repo/GitUntrackedFilesHolder.kt @@ -69,7 +69,7 @@ class GitUntrackedFilesHolder internal constructor( get() = untrackedFiles.initialized init { - updateRunner = SingleTaskRunner(cs, 500.milliseconds, ::update) + updateRunner = SingleTaskRunner.delayedTaskRunner(cs, 500.milliseconds, ::update) cs.launch(start = CoroutineStart.UNDISPATCHED) { try { project.serviceAsync().awaitInitialVfsRefreshFinished() diff --git a/plugins/gradle/java/resources/META-INF/plugin.xml b/plugins/gradle/java/resources/META-INF/plugin.xml index ee5bd398e926..88ec4e9ef917 100644 --- a/plugins/gradle/java/resources/META-INF/plugin.xml +++ b/plugins/gradle/java/resources/META-INF/plugin.xml @@ -129,6 +129,10 @@ description="Specifies port at which Gradle target server process will wait for debugger connections. -1 means disabled feature."/> + + + + + diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/execution/GradleExecutionHelper.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/execution/GradleExecutionHelper.java index fd09485c31f7..ff6475f99b79 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/execution/GradleExecutionHelper.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/execution/GradleExecutionHelper.java @@ -1,6 +1,8 @@ // 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.plugins.gradle.service.execution; +import com.intellij.build.events.MessageEvent; +import com.intellij.build.events.impl.BuildIssueEventImpl; import com.intellij.execution.configurations.GeneralCommandLine; import com.intellij.execution.process.ProcessOutputType; import com.intellij.openapi.application.Application; @@ -12,6 +14,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.externalSystem.model.ExternalSystemException; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener; +import com.intellij.openapi.externalSystem.model.task.event.ExternalSystemBuildEvent; import com.intellij.openapi.externalSystem.service.execution.ExternalSystemExecutionAware; import com.intellij.openapi.externalSystem.service.execution.ExternalSystemJdkUtil; import com.intellij.openapi.externalSystem.service.execution.ExternalSystemRunConfiguration; @@ -35,6 +38,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.VisibleForTesting; import org.jetbrains.plugins.gradle.connection.GradleConnectorService; +import org.jetbrains.plugins.gradle.issue.DeprecatedGradleVersionIssue; import org.jetbrains.plugins.gradle.jvmcompat.GradleJvmSupportMatrix; import org.jetbrains.plugins.gradle.properties.GradlePropertiesFile; import org.jetbrains.plugins.gradle.service.execution.cmd.GradleCommandLineOptionsProvider; @@ -560,6 +564,7 @@ public final class GradleExecutionHelper { } checkThatGradleBuildEnvironmentIsSupportedByIdea(buildEnvironment); + checkThatGradleBuildEnvironmentIsDeprecatedByIdea(context, buildEnvironment); return buildEnvironment; } @@ -576,6 +581,23 @@ public final class GradleExecutionHelper { } } + private static void checkThatGradleBuildEnvironmentIsDeprecatedByIdea( + @NotNull GradleExecutionContext context, + @NotNull BuildEnvironment buildEnvironment + ) { + final GradleVersion gradleVersion = GradleVersion.version(buildEnvironment.getGradle().getGradleVersion()); + if (GradleJvmSupportMatrix.isGradleDeprecatedByIdea(gradleVersion)) { + final String projectPath = context.getProjectPath(); + final var issue = new DeprecatedGradleVersionIssue(gradleVersion, projectPath); + context.getListener().onStatusChange( + new ExternalSystemBuildEvent( + context.getTaskId(), + new BuildIssueEventImpl(context.getTaskId(), issue, MessageEvent.Kind.WARNING) + ) + ); + } + } + private static void checkThatGradleBuildEnvironmentIsSupportedByIdea(@NotNull BuildEnvironment buildEnvironment) { var gradleVersion = GradleVersion.version(buildEnvironment.getGradle().getGradleVersion()); LOG.debug("Gradle version: " + gradleVersion); diff --git a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolver.java b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolver.java index 66f7810a0ee2..97e5e0a59144 100644 --- a/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolver.java +++ b/plugins/gradle/src/org/jetbrains/plugins/gradle/service/project/GradleProjectResolver.java @@ -1,7 +1,6 @@ // 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.plugins.gradle.service.project; -import com.intellij.build.events.MessageEvent; import com.intellij.execution.configurations.ParametersList; import com.intellij.gradle.toolingExtension.impl.model.sourceSetDependencyModel.DefaultGradleSourceSetDependencyModel; import com.intellij.gradle.toolingExtension.impl.model.sourceSetModel.DefaultGradleSourceSetModel; @@ -48,15 +47,15 @@ import org.gradle.tooling.model.build.BuildEnvironment; import org.gradle.tooling.model.idea.IdeaModule; import org.gradle.tooling.model.idea.IdeaProject; import org.jetbrains.annotations.*; -import org.jetbrains.plugins.gradle.issue.DeprecatedGradleVersionIssue; -import org.jetbrains.plugins.gradle.jvmcompat.GradleJvmSupportMatrix; import org.jetbrains.plugins.gradle.model.*; import org.jetbrains.plugins.gradle.model.data.BuildParticipant; import org.jetbrains.plugins.gradle.model.data.BuildScriptClasspathData; import org.jetbrains.plugins.gradle.model.data.CompositeBuildData; import org.jetbrains.plugins.gradle.model.data.GradleSourceSetData; import org.jetbrains.plugins.gradle.remote.impl.GradleLibraryNamesMixer; -import org.jetbrains.plugins.gradle.service.execution.*; +import org.jetbrains.plugins.gradle.service.execution.GradleExecutionHelper; +import org.jetbrains.plugins.gradle.service.execution.GradleInitScriptUtil; +import org.jetbrains.plugins.gradle.service.execution.GradleWrapperHelper; import org.jetbrains.plugins.gradle.service.modelAction.GradleIdeaModelHolder; import org.jetbrains.plugins.gradle.service.modelAction.GradleModelFetchActionRunner; import org.jetbrains.plugins.gradle.service.syncAction.impl.GradleSyncProjectConfigurator; @@ -285,13 +284,6 @@ public final class GradleProjectResolver implements ExternalSystemProjectResolve try (Scope ignore = gradleCallSpan.makeCurrent()) { var modelFetchActionResultHandler = GradleSyncProjectConfigurator.createModelFetchResultHandler(resolverContext); GradleModelFetchActionRunner.runAndTraceBuildAction(connection, resolverContext, buildAction, modelFetchActionResultHandler); - - var gradleVersion = resolverContext.getGradleVersion(); - if (GradleJvmSupportMatrix.isGradleDeprecatedByIdea(gradleVersion)) { - var projectPath = resolverContext.getProjectPath(); - var issue = new DeprecatedGradleVersionIssue(gradleVersion, projectPath); - resolverContext.report(MessageEvent.Kind.WARNING, issue); - } } catch (Throwable t) { gradleCallErrorsCount += 1; diff --git a/plugins/javaFX/resources/intellij.javaFX.xml b/plugins/javaFX/resources/intellij.javaFX.xml index 0a2baab33fa7..e9f2aca565b5 100644 --- a/plugins/javaFX/resources/intellij.javaFX.xml +++ b/plugins/javaFX/resources/intellij.javaFX.xml @@ -105,6 +105,9 @@ + + diff --git a/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/hints/printType.kt b/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/hints/printType.kt index 314e0e9aed90..ec7d1f9a54f1 100644 --- a/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/hints/printType.kt +++ b/plugins/kotlin/code-insight/kotlin.code-insight.k2/src/org/jetbrains/kotlin/idea/k2/codeinsight/hints/printType.kt @@ -8,6 +8,7 @@ import org.jetbrains.annotations.ApiStatus import org.jetbrains.kotlin.analysis.api.KaExperimentalApi import org.jetbrains.kotlin.analysis.api.KaSession import org.jetbrains.kotlin.analysis.api.components.DefaultTypeClassIds +import org.jetbrains.kotlin.analysis.api.components.approximateToDenotableSupertypeOrSelf import org.jetbrains.kotlin.analysis.api.components.isMarkedNullable import org.jetbrains.kotlin.analysis.api.components.withNullability import org.jetbrains.kotlin.analysis.api.symbols.KaClassKind @@ -122,18 +123,16 @@ internal fun PresentationTreeBuilder.printKtType(type: KaType) { if (markedNullable) text("?") } +@OptIn(KaExperimentalApi::class) context(_: KaSession) private fun PresentationTreeBuilder.printNonErrorClassType(type: KaClassType, anotherType: KaClassType? = null) { - val classType = - when (val symbol = type.symbol) { - is KaClassSymbol if symbol.classKind == KaClassKind.ANONYMOUS_OBJECT -> { - (symbol.superTypes.firstOrNull() as? KaClassType) ?: type - } + val classType = when (val symbol = type.symbol) { + is KaClassSymbol if symbol.classKind == KaClassKind.ANONYMOUS_OBJECT -> + type.approximateToDenotableSupertypeOrSelf(allowLocalDenotableTypes = false) as? KaClassType ?: type - else -> { - type - } - } + else -> type + + } val truncatedName = truncatedName(classType) if (truncatedName.isNotEmpty()) { if (classType.classId.isLocal) { diff --git a/plugins/kotlin/code-insight/kotlin.code-insight.k2/test/org/jetbrains/kotlin/idea/k2/hints/KtReferenceTypeHintsProviderTestGenerated.java b/plugins/kotlin/code-insight/kotlin.code-insight.k2/test/org/jetbrains/kotlin/idea/k2/hints/KtReferenceTypeHintsProviderTestGenerated.java index 3125b2ba8501..16f944bb9f62 100644 --- a/plugins/kotlin/code-insight/kotlin.code-insight.k2/test/org/jetbrains/kotlin/idea/k2/hints/KtReferenceTypeHintsProviderTestGenerated.java +++ b/plugins/kotlin/code-insight/kotlin.code-insight.k2/test/org/jetbrains/kotlin/idea/k2/hints/KtReferenceTypeHintsProviderTestGenerated.java @@ -30,6 +30,11 @@ public class KtReferenceTypeHintsProviderTestGenerated extends AbstractKtReferen KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); } + @TestMetadata("AnonymousAnyCollectionElement.kt") + public void testAnonymousAnyCollectionElement() throws Exception { + runTest("../../idea/tests/testData/codeInsight/hints/types/AnonymousAnyCollectionElement.kt"); + } + @TestMetadata("AnonymousCollectionElement.kt") public void testAnonymousCollectionElement() throws Exception { runTest("../../idea/tests/testData/codeInsight/hints/types/AnonymousCollectionElement.kt"); diff --git a/plugins/kotlin/fir/src/org/jetbrains/kotlin/idea/fir/extensions/KtCompilerPluginsProviderIdeImpl.kt b/plugins/kotlin/fir/src/org/jetbrains/kotlin/idea/fir/extensions/KtCompilerPluginsProviderIdeImpl.kt index bec0fdce712a..e9d1cab87095 100644 --- a/plugins/kotlin/fir/src/org/jetbrains/kotlin/idea/fir/extensions/KtCompilerPluginsProviderIdeImpl.kt +++ b/plugins/kotlin/fir/src/org/jetbrains/kotlin/idea/fir/extensions/KtCompilerPluginsProviderIdeImpl.kt @@ -50,10 +50,10 @@ import org.jetbrains.kotlin.idea.base.projectStructure.openapiModule import org.jetbrains.kotlin.idea.base.projectStructure.sourceModuleKind import org.jetbrains.kotlin.idea.base.util.caching.getChanges import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder +import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerPluginsScriptConfigurationListener import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerSettingsListener import org.jetbrains.kotlin.idea.facet.KotlinFacet import org.jetbrains.kotlin.idea.facet.isKotlinFacet -import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCompilerPluginsScriptConfigurationListener import org.jetbrains.kotlin.idea.util.getOriginalOrDelegateFileOrSelf import org.jetbrains.kotlin.idea.workspaceModel.KotlinSettingsEntity import org.jetbrains.kotlin.scripting.compiler.plugin.impl.makeScriptCompilerArguments @@ -75,8 +75,7 @@ internal class KtCompilerPluginsProviderIdeImpl( private val pluginsCache: PluginsCache? get() = pluginsCacheCachedValue.value - private val onlyBundledPluginsEnabledRegistryValue: RegistryValue = - Registry.get("kotlin.k2.only.bundled.compiler.plugins.enabled") + private val onlyBundledPluginsEnabledRegistryValue: RegistryValue = Registry.get("kotlin.k2.only.bundled.compiler.plugins.enabled") private val onlyBundledPluginsEnabled: Boolean get() = onlyBundledPluginsEnabledRegistryValue.asBoolean() @@ -96,29 +95,25 @@ internal class KtCompilerPluginsProviderIdeImpl( } } val messageBusConnection = project.messageBus.connect(this) - messageBusConnection.subscribe(KotlinCompilerSettingsListener.TOPIC, - object : KotlinCompilerSettingsListener { + messageBusConnection.subscribe( + KotlinCompilerSettingsListener.TOPIC, object : KotlinCompilerSettingsListener { override fun settingsChanged(oldSettings: T?, newSettings: T?) { resetPluginsCache() } - } - ) + }) messageBusConnection.subscribe( - KotlinCompilerPluginsScriptConfigurationListener.TOPIC, - object : KotlinCompilerPluginsScriptConfigurationListener { + KotlinCompilerPluginsScriptConfigurationListener.TOPIC, object : KotlinCompilerPluginsScriptConfigurationListener { override fun scriptConfigurationsChanged() { resetScriptCache(pluginsCacheCachedValue.valueIfInitialized ?: return) } - } - ) + }) onlyBundledPluginsEnabledRegistryValue.addListener( object : RegistryValueListener { override fun afterValueChanged(value: RegistryValue) { resetPluginsCache() } - }, - this + }, this ) } @@ -135,8 +130,7 @@ internal class KtCompilerPluginsProviderIdeImpl( return PluginsCache( pluginsClassLoader, ContainerUtil.createConcurrentWeakMap(), - SynchronizedClearableLazy { ContainerUtil.createConcurrentWeakMap() } - ) + SynchronizedClearableLazy { ContainerUtil.createConcurrentWeakMap() }) } private class PluginsCache( @@ -158,19 +152,20 @@ internal class KtCompilerPluginsProviderIdeImpl( val registrarForModule = pluginsCache?.registrarForSourceModule ?: return emptyList() module.getExtensionsForModule(registrarForModule, module, extensionType) } + is KaScriptModule -> { val registrarForModule = pluginsCache?.registrarForScriptModule?.value ?: return emptyList() - val cacheKey = module.file.virtualFile - .getOriginalOrDelegateFileOrSelf() + val cacheKey = module.file.virtualFile.getOriginalOrDelegateFileOrSelf() module.getExtensionsForModule(registrarForModule, cacheKey, extensionType) } + else -> emptyList() } } @OptIn(KaExperimentalApi::class) - private fun KaModule.getExtensionsForModule( + private fun KaModule.getExtensionsForModule( registrarForModule: ConcurrentMap>, cacheKey: K, extensionType: ProjectExtensionDescriptor @@ -179,8 +174,7 @@ internal class KtCompilerPluginsProviderIdeImpl( Optional.ofNullable(computeExtensionStorage(this)) }.orNull() ?: return emptyList() val registrars = extensionStorage.registeredExtensions[extensionType] ?: return emptyList() - @Suppress("UNCHECKED_CAST") - return registrars as List + @Suppress("UNCHECKED_CAST") return registrars as List } override fun isPluginOfTypeRegistered(module: KaModule, pluginType: CompilerPluginType): Boolean { @@ -189,8 +183,7 @@ internal class KtCompilerPluginsProviderIdeImpl( else -> return false } - return getRegisteredExtensions(module, FirExtensionRegistrarAdapter) - .map { (it as FirExtensionRegistrar).configure() } + return getRegisteredExtensions(module, FirExtensionRegistrarAdapter).map { (it as FirExtensionRegistrar).configure() } .any { it.extensions[extension]?.isNotEmpty() == true } } @@ -207,6 +200,7 @@ internal class KtCompilerPluginsProviderIdeImpl( val providedOptions = scriptConfiguration[ScriptCompilationConfiguration.compilerOptions] ?: return null makeScriptCompilerArguments(providedOptions) } + else -> { return null } @@ -220,8 +214,7 @@ internal class KtCompilerPluginsProviderIdeImpl( val pluginRegistrars = logger.runAndLogException { ServiceLoaderLite.loadImplementations(pluginClasspaths, classLoader) } - ?.takeIf { it.isNotEmpty() } - ?: return null + ?.takeIf { it.isNotEmpty() } ?: return null ProgressManager.checkCanceled() @@ -231,23 +224,23 @@ internal class KtCompilerPluginsProviderIdeImpl( ProgressManager.checkCanceled() - val compilerConfiguration = CompilerConfiguration().apply { - // Temporary work-around for KTIJ-24320. Calls to 'setupCommonArguments()' and 'setupJvmSpecificArguments()' - // (or even a platform-agnostic alternative) should be added. - if (compilerArguments is K2JVMCompilerArguments && module is KaSourceModule) { - val compilerExtension = CompilerModuleExtension.getInstance(module.openapiModule) - val outputUrl = when (module.sourceModuleKind) { - KaSourceModuleKind.TEST -> compilerExtension?.compilerOutputUrlForTests - KaSourceModuleKind.PRODUCTION, null -> compilerExtension?.compilerOutputUrl + val compilerConfiguration = + CompilerConfiguration().apply { // Temporary work-around for KTIJ-24320. Calls to 'setupCommonArguments()' and 'setupJvmSpecificArguments()' + // (or even a platform-agnostic alternative) should be added. + if (compilerArguments is K2JVMCompilerArguments && module is KaSourceModule) { + val compilerExtension = CompilerModuleExtension.getInstance(module.openapiModule) + val outputUrl = when (module.sourceModuleKind) { + KaSourceModuleKind.TEST -> compilerExtension?.compilerOutputUrlForTests + KaSourceModuleKind.PRODUCTION, null -> compilerExtension?.compilerOutputUrl + } + + putIfNotNull(JVMConfigurationKeys.JVM_TARGET, compilerArguments.jvmTarget?.let(JvmTarget::fromString)) + putIfNotNull(JVMConfigurationKeys.OUTPUT_DIRECTORY, outputUrl?.let { File(it) }) } - putIfNotNull(JVMConfigurationKeys.JVM_TARGET, compilerArguments.jvmTarget?.let(JvmTarget::fromString)) - putIfNotNull(JVMConfigurationKeys.OUTPUT_DIRECTORY, outputUrl?.let { File(it) }) + processCompilerPluginsOptions(this, compilerArguments.pluginOptions?.toList(), commandLineProcessors) } - processCompilerPluginsOptions(this, compilerArguments.pluginOptions?.toList(), commandLineProcessors) - } - val storage = CompilerPluginRegistrar.ExtensionStorage() for (pluginRegistrar in pluginRegistrars) { ProgressManager.checkCanceled() @@ -258,11 +251,9 @@ internal class KtCompilerPluginsProviderIdeImpl( pluginRegistrar, compilerConfiguration ) ?: compilerConfiguration storage.registerExtensions(configuration) - } - catch (e : ProcessCanceledException) { + } catch (e: ProcessCanceledException) { throw e - } - catch (e: Throwable) { + } catch (e: Throwable) { LOG.error(e) } } @@ -294,9 +285,7 @@ internal class KtCompilerPluginsProviderIdeImpl( * compiler plugins jars. */ private fun collectSubstitutedPluginClasspaths(compilerArguments: List): List { - val combinedOriginalClasspaths = compilerArguments.asSequence() - .flatMap { it.getOriginalPluginClasspaths() } - .distinct() + val combinedOriginalClasspaths = compilerArguments.asSequence().flatMap { it.getOriginalPluginClasspaths() }.distinct() val substitutedClasspaths = combinedOriginalClasspaths.mapNotNull(::substitutePluginJar).distinct() @@ -319,8 +308,9 @@ internal class KtCompilerPluginsProviderIdeImpl( private fun Module.getCompilerArguments(): CommonCompilerArguments { - return KotlinFacet.get(this)?.configuration?.settings?.mergedCompilerArguments - ?: KotlinCommonCompilerArgumentsHolder.getInstance(project).settings + return KotlinFacet.get(this)?.configuration?.settings?.mergedCompilerArguments ?: KotlinCommonCompilerArgumentsHolder.getInstance( + project + ).settings } override fun dispose() { @@ -334,8 +324,7 @@ internal class KtCompilerPluginsProviderIdeImpl( private fun resetPluginsCache() { val droppedCache = pluginsCacheCachedValue.drop() ?: return - droppedCache.registrarForSourceModule - .values.mapNotNull { it.orNull() }.disposeAll() + droppedCache.registrarForSourceModule.values.mapNotNull { it.orNull() }.disposeAll() resetScriptCache(droppedCache) } @@ -363,6 +352,7 @@ internal class KtCompilerPluginsProviderIdeImpl( fun getInstance(project: Project): KtCompilerPluginsProviderIdeImpl { return KotlinCompilerPluginsProvider.getInstance(project) as KtCompilerPluginsProviderIdeImpl } + private val LOG = logger() } } diff --git a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinReferenceTypeHintsProviderTestGenerated.java b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinReferenceTypeHintsProviderTestGenerated.java index ed3e808bee5b..77c2205edcfc 100644 --- a/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinReferenceTypeHintsProviderTestGenerated.java +++ b/plugins/kotlin/idea/tests/test/org/jetbrains/kotlin/idea/codeInsight/hints/KotlinReferenceTypeHintsProviderTestGenerated.java @@ -30,6 +30,11 @@ public class KotlinReferenceTypeHintsProviderTestGenerated extends AbstractKotli KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); } + @TestMetadata("AnonymousAnyCollectionElement.kt") + public void testAnonymousAnyCollectionElement() throws Exception { + runTest("testData/codeInsight/hints/types/AnonymousAnyCollectionElement.kt"); + } + @TestMetadata("AnonymousCollectionElement.kt") public void testAnonymousCollectionElement() throws Exception { runTest("testData/codeInsight/hints/types/AnonymousCollectionElement.kt"); diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/hints/types/AnonymousAnyCollectionElement.k2.kt b/plugins/kotlin/idea/tests/testData/codeInsight/hints/types/AnonymousAnyCollectionElement.k2.kt new file mode 100644 index 000000000000..33c22c7eed62 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/codeInsight/hints/types/AnonymousAnyCollectionElement.k2.kt @@ -0,0 +1,8 @@ +// MODE: all +fun x() { + val map/*<# : |[kotlin.collections.List:kotlin.fqn.class]List|<|[kotlin.Any:kotlin.fqn.class]Any|> #>*/ = listOf(1, 2, 3).map { + object { + override fun toString(): String = it.toString() + } + } +} \ No newline at end of file diff --git a/plugins/kotlin/idea/tests/testData/codeInsight/hints/types/AnonymousAnyCollectionElement.kt b/plugins/kotlin/idea/tests/testData/codeInsight/hints/types/AnonymousAnyCollectionElement.kt new file mode 100644 index 000000000000..12e9ca5f1462 --- /dev/null +++ b/plugins/kotlin/idea/tests/testData/codeInsight/hints/types/AnonymousAnyCollectionElement.kt @@ -0,0 +1,8 @@ +// MODE: all +fun x() { + val map/*<# : |[kotlin.collections.List:kotlin.fqn.class]List|<|[x..:kotlin.fqn.class]``|> #>*/ = listOf(1, 2, 3).map { + object { + override fun toString(): String = it.toString() + } + } +} \ No newline at end of file diff --git a/plugins/kotlin/plugin/common/resources/META-INF/jvm-debugger.xml b/plugins/kotlin/plugin/common/resources/META-INF/jvm-debugger.xml index 2120e525780b..da9a5818e4cf 100644 --- a/plugins/kotlin/plugin/common/resources/META-INF/jvm-debugger.xml +++ b/plugins/kotlin/plugin/common/resources/META-INF/jvm-debugger.xml @@ -28,6 +28,8 @@ + @@ -38,6 +40,8 @@ + @@ -47,7 +51,7 @@ - + @@ -64,6 +68,8 @@ + @@ -100,5 +106,21 @@ description="Use Analysis API-based implementation for file applicability checker" defaultValue="false" restartRequired="false"/> + + + + diff --git a/plugins/kotlin/uast/uast-kotlin-fir/src/org/jetbrains/uast/kotlin/internal/FirKotlinUastLibraryPsiProviderService.kt b/plugins/kotlin/uast/uast-kotlin-fir/src/org/jetbrains/uast/kotlin/internal/FirKotlinUastLibraryPsiProviderService.kt index c701fe69caf7..86238bc50a0f 100644 --- a/plugins/kotlin/uast/uast-kotlin-fir/src/org/jetbrains/uast/kotlin/internal/FirKotlinUastLibraryPsiProviderService.kt +++ b/plugins/kotlin/uast/uast-kotlin-fir/src/org/jetbrains/uast/kotlin/internal/FirKotlinUastLibraryPsiProviderService.kt @@ -6,15 +6,26 @@ import com.intellij.openapi.components.service import com.intellij.psi.PsiElement import org.jetbrains.kotlin.analysis.api.KaSession import org.jetbrains.kotlin.analysis.api.symbols.KaSymbol +import org.jetbrains.kotlin.psi.KtElement /** * This service is required to add an ability to provide correct [PsiElement] for libraries in UAST/Lint CLI mode */ interface FirKotlinUastLibraryPsiProviderService { - fun KaSession.provide(symbol: KaSymbol): PsiElement? + /** + * Provide [PsiElement] from libraries that corresponds to the given [symbol]. + * + * If multiple candidates are available, an extra [context] will be used. + * For example, for the property symbol and its access, with a context like + * LHS or RHS of an assignment, either accessor would be appropriate, not both. + */ + fun KaSession.provide( + symbol: KaSymbol, + context: KtElement? = null, + ): PsiElement? class Default : FirKotlinUastLibraryPsiProviderService { - override fun KaSession.provide(symbol: KaSymbol): PsiElement? = symbol.psi + override fun KaSession.provide(symbol: KaSymbol, context: KtElement?): PsiElement? = symbol.psi } companion object { diff --git a/plugins/kotlin/uast/uast-kotlin-fir/src/org/jetbrains/uast/kotlin/internal/firKotlinInternalUastUtils.kt b/plugins/kotlin/uast/uast-kotlin-fir/src/org/jetbrains/uast/kotlin/internal/firKotlinInternalUastUtils.kt index 3a0f0a9b70ac..de8f2bf7a292 100644 --- a/plugins/kotlin/uast/uast-kotlin-fir/src/org/jetbrains/uast/kotlin/internal/firKotlinInternalUastUtils.kt +++ b/plugins/kotlin/uast/uast-kotlin-fir/src/org/jetbrains/uast/kotlin/internal/firKotlinInternalUastUtils.kt @@ -416,7 +416,8 @@ internal fun toPsiType( psiTypeParent, allowErrorTypes = false, config.typeMappingMode, - isAnnotationMethod = false + isAnnotationMethod = false, + allowNonJvmPlatforms = true, ) ?: UastErrorType } @@ -502,7 +503,7 @@ internal tailrec fun psiForUast( } val psiProvider = FirKotlinUastLibraryPsiProviderService.getInstance() - return with(psiProvider) { session.provide(symbol) } + return with(psiProvider) { session.provide(symbol, context) } } if (symbol is KaConstructorSymbol) { diff --git a/plugins/markdown/compose/src/main/kotlin/com/intellij/markdown/compose/preview/MarkdownComposePanel.kt b/plugins/markdown/compose/src/main/kotlin/com/intellij/markdown/compose/preview/MarkdownComposePanel.kt index e0c8f0ce1a22..def8ca63e645 100644 --- a/plugins/markdown/compose/src/main/kotlin/com/intellij/markdown/compose/preview/MarkdownComposePanel.kt +++ b/plugins/markdown/compose/src/main/kotlin/com/intellij/markdown/compose/preview/MarkdownComposePanel.kt @@ -108,6 +108,7 @@ internal class MarkdownComposePanel( AutolinkProcessorExtension, ), markdownMode, + parseEmbeddedHtml = true, ) } val tableRenderer = remember(markdownStyling) { diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProjectResolver.kt b/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProjectResolver.kt index 02eed85b3805..8206b1371d7e 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProjectResolver.kt +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProjectResolver.kt @@ -212,10 +212,12 @@ class MavenProjectResolver(private val myProject: Project) { val virtualFile = VirtualFileManager.getInstance().findFileByNioPath(file) ?: return@forEach val aggregatorProject = tree.findProject(virtualFile) ?: return@forEach modules.forEach modules@{ modulePath -> - val moduleFile = file.parent.resolve(modulePath).resolve("pom.xml") - val moduleVirtualFile = VirtualFileManager.getInstance().findFileByNioPath(moduleFile) ?: return@modules - val moduleProject = tree.findProject(moduleVirtualFile) ?: return@modules - tree.reconnect(aggregatorProject, moduleProject) + if (modulePath.isNotBlank()) { + val moduleFile = file.parent.resolve(modulePath).resolve("pom.xml") + val moduleVirtualFile = VirtualFileManager.getInstance().findFileByNioPath(moduleFile) ?: return@modules + val moduleProject = tree.findProject(moduleVirtualFile) ?: return@modules + tree.reconnect(aggregatorProject, moduleProject) + } } } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProjectsTree.kt b/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProjectsTree.kt index 5f0d364eac58..c8bfb7846603 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProjectsTree.kt +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/project/MavenProjectsTree.kt @@ -348,7 +348,7 @@ class MavenProjectsTree(val project: Project) { force: Boolean, generalSettings: MavenGeneralSettings, mavenEmbedderWrappers: MavenEmbedderWrappers, - process: MavenProgressIndicator + process: MavenProgressIndicator, ) { runBlockingMaybeCancellable { updateAll(force, generalSettings, mavenEmbedderWrappers, process.indicator) } } @@ -358,7 +358,7 @@ class MavenProjectsTree(val project: Project) { force: Boolean, generalSettings: MavenGeneralSettings, mavenEmbedderWrappers: MavenEmbedderWrappers, - process: ProgressIndicator + process: ProgressIndicator, ): MavenProjectsTreeUpdateResult { return updateAll(force, generalSettings, mavenEmbedderWrappers, toRawProgressReporter(process)) } @@ -400,11 +400,13 @@ class MavenProjectsTree(val project: Project) { return update(files, false, force, projectReader, progressReporter) } - private suspend fun update(files: Collection, - updateModules: Boolean, - forceRead: Boolean, - projectReader: MavenProjectReader, - progressReporter: RawProgressReporter): MavenProjectsTreeUpdateResult { + private suspend fun update( + files: Collection, + updateModules: Boolean, + forceRead: Boolean, + projectReader: MavenProjectReader, + progressReporter: RawProgressReporter, + ): MavenProjectsTreeUpdateResult { val updateContext = MavenProjectsTreeUpdateContext(this) val updater = MavenProjectsTreeUpdater( @@ -419,7 +421,7 @@ class MavenProjectsTree(val project: Project) { if (null == findProject(file)) { filesToAddModules.add(file) } - tracer.spanBuilder("updateProjectFile").useWithScope { updater.updateProjects(listOf(UpdateSpec(file, forceRead))) } + tracer.spanBuilder("updateProjectFile").useWithScope { updater.updateProjects(listOf(UpdateSpec(file, forceRead))) } } for (aggregator in projects) { @@ -490,9 +492,11 @@ class MavenProjectsTree(val project: Project) { return delete(projectReader, files, progressReporter) } - private suspend fun delete(projectReader: MavenProjectReader, - files: Collection, - progressReporter: RawProgressReporter): MavenProjectsTreeUpdateResult { + private suspend fun delete( + projectReader: MavenProjectReader, + files: Collection, + progressReporter: RawProgressReporter, + ): MavenProjectsTreeUpdateResult { val updateContext = MavenProjectsTreeUpdateContext(this) val inheritorsToUpdate: MutableSet = HashSet() @@ -583,7 +587,7 @@ class MavenProjectsTree(val project: Project) { val prevAggregator = findAggregator(project) if (prevAggregator === newAggregator) return false - + if (newAggregator === project) return false withWriteLock { if (prevAggregator != null) { removeModule(prevAggregator, project) @@ -780,11 +784,16 @@ class MavenProjectsTree(val project: Project) { private fun doFindRootProject(project: MavenProject): MavenProject { var rootProject = project + val traversed = LinkedHashSet().also { it.add(project) } while (true) { val aggregator = myModuleToAggregatorMapping[rootProject] if (aggregator == null) { return rootProject } + if (!traversed.add(aggregator)) { + MavenLog.LOG.warn("Recursive aggregator definition: ${traversed.joinToString(" -> ") { it.mavenId.toString() }}") + return project + } rootProject = aggregator } } @@ -941,9 +950,11 @@ class MavenProjectsTree(val project: Project) { fun profilesChanged() { } - fun projectsIgnoredStateChanged(ignored: List, - unignored: List, - fromImport: Boolean) { + fun projectsIgnoredStateChanged( + ignored: List, + unignored: List, + fromImport: Boolean, + ) { } fun projectsUpdated(updated: List>, deleted: List) { @@ -951,8 +962,10 @@ class MavenProjectsTree(val project: Project) { @Suppress("DEPRECATION") @Deprecated("use projectResolved(Pair)") - fun projectResolved(projectWithChanges: Pair, - nativeMavenProject: NativeMavenProjectHolder?) { + fun projectResolved( + projectWithChanges: Pair, + nativeMavenProject: NativeMavenProjectHolder?, + ) { } @Suppress("DEPRECATION") @@ -1152,8 +1165,10 @@ class MavenProjectsTree(val project: Project) { } @Throws(IOException::class) - private fun readProjectsRecursively(inputStream: DataInputStream, - tree: MavenProjectsTree): MutableList { + private fun readProjectsRecursively( + inputStream: DataInputStream, + tree: MavenProjectsTree, + ): MutableList { var count = inputStream.readInt() val result: MutableList = ArrayList(count) while (count-- > 0) { @@ -1176,9 +1191,11 @@ class MavenProjectsTree(val project: Project) { return result } - private fun updateExplicitProfiles(explicitProfiles: MutableCollection, - temporarilyRemovedExplicitProfiles: MutableCollection, - available: Set) { + private fun updateExplicitProfiles( + explicitProfiles: MutableCollection, + temporarilyRemovedExplicitProfiles: MutableCollection, + available: Set, + ) { val removedProfiles = HashSet(explicitProfiles) removedProfiles.removeAll(available) temporarilyRemovedExplicitProfiles.addAll(removedProfiles) diff --git a/plugins/maven/src/main/resources/META-INF/plugin.xml b/plugins/maven/src/main/resources/META-INF/plugin.xml index 4afb144fc743..1e3b0e8a635e 100644 --- a/plugins/maven/src/main/resources/META-INF/plugin.xml +++ b/plugins/maven/src/main/resources/META-INF/plugin.xml @@ -79,6 +79,9 @@ + + diff --git a/plugins/maven/src/test/java/org/jetbrains/idea/maven/importing/InvalidProjectImportingTest.kt b/plugins/maven/src/test/java/org/jetbrains/idea/maven/importing/InvalidProjectImportingTest.kt index 76f0c45a2eb8..251f7d3033b7 100644 --- a/plugins/maven/src/test/java/org/jetbrains/idea/maven/importing/InvalidProjectImportingTest.kt +++ b/plugins/maven/src/test/java/org/jetbrains/idea/maven/importing/InvalidProjectImportingTest.kt @@ -21,7 +21,6 @@ import com.intellij.build.events.BuildIssueEvent import com.intellij.build.events.MessageEvent import com.intellij.maven.testFramework.MavenMultiVersionImportingTestCase import com.intellij.openapi.application.edtWriteAction -import com.intellij.openapi.util.registry.Registry import com.intellij.testFramework.UsefulTestCase import com.intellij.testFramework.replaceService import kotlinx.coroutines.runBlocking @@ -1009,6 +1008,38 @@ class InvalidProjectImportingTest : MavenMultiVersionImportingTestCase() { assertProblems(root, "'settings.xml' has syntax errors") } + @Test + fun testImportingWithEmptyPath() = runBlocking { + importProjectAsync(""" + test + project + 1 + pom + + + + """.trimIndent()) + assertModules("project") + val rootProject = projectsManager.findProject(projectPom) + assertNotNull("Project should be found", rootProject) + val rootOfRoot = projectsManager.findRootProject(rootProject!!) + assertNotNull("Root of root should be null", rootOfRoot) + } + + @Test + fun testImportingWithSelfInclusionInclusion() = runBlocking { + importProjectAsync(""" + test + project + 1 + pom + + ./pom.xml + + """.trimIndent()) + assertModules("project") + } + private val rootProjects: List get() = projectsTree.rootProjects diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/BackendTerminalInput.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/BackendTerminalInput.kt index ae54ceb0e298..400dd3d9a511 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/BackendTerminalInput.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/BackendTerminalInput.kt @@ -11,7 +11,7 @@ import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.launch import org.jetbrains.plugins.terminal.block.reworked.TerminalUsageLocalStorage import org.jetbrains.plugins.terminal.block.ui.withLock -import org.jetbrains.plugins.terminal.session.* +import org.jetbrains.plugins.terminal.session.impl.* import org.jetbrains.plugins.terminal.util.STOP_EMULATOR_TIMEOUT import org.jetbrains.plugins.terminal.util.waitFor import java.util.concurrent.CancellationException diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/BackendTerminalSession.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/BackendTerminalSession.kt index 9fae85b1e1b7..fd3c88ee0571 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/BackendTerminalSession.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/BackendTerminalSession.kt @@ -4,7 +4,7 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.logger import kotlinx.coroutines.CoroutineScope import org.jetbrains.annotations.ApiStatus -import org.jetbrains.plugins.terminal.session.TerminalSession +import org.jetbrains.plugins.terminal.session.impl.TerminalSession @ApiStatus.Internal interface BackendTerminalSession : TerminalSession { diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/BackendTerminalSessionImpl.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/BackendTerminalSessionImpl.kt index a3fa4b0d43ee..eef855ae8f03 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/BackendTerminalSessionImpl.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/BackendTerminalSessionImpl.kt @@ -10,9 +10,9 @@ import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext import org.jetbrains.plugins.terminal.TerminalUtil -import org.jetbrains.plugins.terminal.session.TerminalInputEvent -import org.jetbrains.plugins.terminal.session.TerminalOutputEvent -import org.jetbrains.plugins.terminal.session.TerminalSessionTerminatedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalInputEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalOutputEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalSessionTerminatedEvent internal class BackendTerminalSessionImpl( private val inputChannel: SendChannel, diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/StateAwareTerminalSession.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/StateAwareTerminalSession.kt index eda9057fddc2..97e867d3b672 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/StateAwareTerminalSession.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/StateAwareTerminalSession.kt @@ -17,16 +17,17 @@ import kotlinx.coroutines.flow.merge import org.jetbrains.plugins.terminal.block.reworked.* import org.jetbrains.plugins.terminal.block.ui.TerminalUiUtils import org.jetbrains.plugins.terminal.fus.* -import org.jetbrains.plugins.terminal.session.* -import org.jetbrains.plugins.terminal.session.dto.toDto -import org.jetbrains.plugins.terminal.session.dto.toTerminalState +import org.jetbrains.plugins.terminal.session.TerminalStartupOptions +import org.jetbrains.plugins.terminal.session.impl.* +import org.jetbrains.plugins.terminal.session.impl.dto.toDto +import org.jetbrains.plugins.terminal.session.impl.dto.toTerminalState import org.jetbrains.plugins.terminal.view.shellIntegration.impl.TerminalBlocksModelImpl import kotlin.coroutines.cancellation.CancellationException import kotlin.time.TimeSource /** * TerminalSession implementation that stores the state of the [delegate] session output. - * This state is then passed as the [org.jetbrains.plugins.terminal.session.TerminalInitialStateEvent] to the output flow as the first event + * This state is then passed as the [org.jetbrains.plugins.terminal.session.impl.TerminalInitialStateEvent] to the output flow as the first event * every time when [getOutputFlow] is requested. * * So, actually it allows restoring the state of UI that requests the [getOutputFlow]. @@ -38,6 +39,7 @@ import kotlin.time.TimeSource internal class StateAwareTerminalSession( project: Project, private val delegate: BackendTerminalSession, + private val startupOptions: TerminalStartupOptions, override val coroutineScope: CoroutineScope, ) : BackendTerminalSession { private val outputFlowProducer = IncrementalUpdateFlowProducer(State()) @@ -80,7 +82,7 @@ internal class StateAwareTerminalSession( alternateBufferModel = MutableTerminalOutputModelImpl(alternateBufferDocument, maxOutputLength = 0) alternateBufferHyperlinkFacade = BackendTerminalHyperlinkFacade(project, hyperlinkScope, alternateBufferModel, isInAlternateBuffer = true) - blocksModel = TerminalBlocksModelImpl(outputModel, coroutineScope.asDisposable()) + blocksModel = TerminalBlocksModelImpl(outputModel, sessionModel, coroutineScope.asDisposable()) coroutineScope.launch(CoroutineName("StateAwareTerminalSession: models updating")) { merge( @@ -192,7 +194,7 @@ internal class StateAwareTerminalSession( } is TerminalCommandStartedEvent -> { blocksModel.updateActiveCommandBlock { block -> - block.copy(outputStartOffset = outputModel.cursorOffset) + block.copy(outputStartOffset = outputModel.cursorOffset, executedCommand = event.command) } } is TerminalCommandFinishedEvent -> { @@ -218,6 +220,7 @@ internal class StateAwareTerminalSession( override suspend fun takeSnapshot(): List> { val event = TerminalInitialStateEvent( + startupOptions = startupOptions.toDto(), sessionState = sessionModel.terminalState.value.toDto(), outputModelState = outputModel.dumpState().toDto(), alternateBufferState = alternateBufferModel.dumpState().toDto(), diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalContentChangesTracker.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalContentChangesTracker.kt index 6688d8adc9ab..a8a094d150a4 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalContentChangesTracker.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalContentChangesTracker.kt @@ -12,9 +12,9 @@ import org.jetbrains.plugins.terminal.block.session.collectLines import org.jetbrains.plugins.terminal.block.session.scraper.SimpleStringCollector import org.jetbrains.plugins.terminal.block.session.scraper.StylesCollectingTerminalLinesCollector import org.jetbrains.plugins.terminal.fus.* -import org.jetbrains.plugins.terminal.session.StyleRange -import org.jetbrains.plugins.terminal.session.dto.StyleRangeDto -import org.jetbrains.plugins.terminal.session.dto.toDto +import org.jetbrains.plugins.terminal.session.impl.StyleRange +import org.jetbrains.plugins.terminal.session.impl.dto.StyleRangeDto +import org.jetbrains.plugins.terminal.session.impl.dto.toDto import java.util.concurrent.CopyOnWriteArrayList import kotlin.math.min import kotlin.time.TimeSource diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalCursorPositionTracker.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalCursorPositionTracker.kt index 2a12e51a80a4..1a483257126b 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalCursorPositionTracker.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalCursorPositionTracker.kt @@ -8,7 +8,7 @@ import com.jediterm.terminal.util.CharUtils import org.jetbrains.annotations.ApiStatus import org.jetbrains.plugins.terminal.block.ui.getLengthWithoutDwc import org.jetbrains.plugins.terminal.block.ui.withLock -import org.jetbrains.plugins.terminal.session.TerminalCursorPositionChangedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalCursorPositionChangedEvent @ApiStatus.Internal class TerminalCursorPositionTracker( diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutput.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutput.kt index c4f8bc4eefe2..215f5e81792b 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutput.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalOutput.kt @@ -11,8 +11,8 @@ import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.MutableSharedFlow import org.jetbrains.plugins.terminal.block.reworked.TerminalShellIntegrationEventsListener import org.jetbrains.plugins.terminal.block.ui.withLock -import org.jetbrains.plugins.terminal.session.* -import org.jetbrains.plugins.terminal.session.dto.toDto +import org.jetbrains.plugins.terminal.session.impl.* +import org.jetbrains.plugins.terminal.session.impl.dto.toDto @OptIn(ExperimentalCoroutinesApi::class) internal fun createTerminalOutputFlow( diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionStart.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionStart.kt index 1a9d5e9d80d1..e77a26a03cd3 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionStart.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionStart.kt @@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import org.jetbrains.annotations.ApiStatus import org.jetbrains.plugins.terminal.ShellStartupOptions -import org.jetbrains.plugins.terminal.session.TerminalSessionTerminatedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalSessionTerminatedEvent import org.jetbrains.plugins.terminal.util.STOP_EMULATOR_TIMEOUT import org.jetbrains.plugins.terminal.util.waitFor diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionsManager.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionsManager.kt index 779d9dda2df0..8701b4af3385 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionsManager.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalSessionsManager.kt @@ -13,6 +13,7 @@ import org.jetbrains.annotations.ApiStatus import org.jetbrains.plugins.terminal.JBTerminalSystemSettingsProvider import org.jetbrains.plugins.terminal.ShellStartupOptions import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalSessionId +import org.jetbrains.plugins.terminal.session.impl.TerminalStartupOptionsImpl import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicInteger @@ -49,7 +50,14 @@ class TerminalSessionsManager { // and cancel the main scope. val jediTermScope = scope.childScope("JediTerm session") val jediTermSession = createTerminalSession(project, observableTtyConnector, configuredOptions, JBTerminalSystemSettingsProvider(), jediTermScope) - val stateAwareSession = StateAwareTerminalSession(project, jediTermSession, scope) + + // It should be guaranteed that the shell command and working directory are not null. + val options = TerminalStartupOptionsImpl( + shellCommand = configuredOptions.shellCommand!!, + workingDirectory = configuredOptions.workingDirectory!!, + envVariables = configuredOptions.envVariables, + ) + val stateAwareSession = StateAwareTerminalSession(project, jediTermSession, options, scope) val sessionId = storeSession(stateAwareSession, scope) diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalShellIntegrationController.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalShellIntegrationController.kt index e318a079b4c5..f0c2f503b089 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalShellIntegrationController.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalShellIntegrationController.kt @@ -6,7 +6,7 @@ import com.intellij.util.EventDispatcher import com.jediterm.terminal.Terminal import org.jetbrains.plugins.terminal.block.reworked.TerminalShellIntegrationEventsListener import org.jetbrains.plugins.terminal.exp.completion.TerminalShellSupport -import org.jetbrains.plugins.terminal.session.TerminalAliasesInfo +import org.jetbrains.plugins.terminal.session.impl.TerminalAliasesInfo import org.jetbrains.plugins.terminal.util.ShellType import java.util.* diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalTabsManager.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalTabsManager.kt index 3ea1c91af5e9..f512a15b15e6 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalTabsManager.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/TerminalTabsManager.kt @@ -17,8 +17,8 @@ import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import org.jetbrains.plugins.terminal.ShellStartupOptions import org.jetbrains.plugins.terminal.block.reworked.session.TerminalSessionTab -import org.jetbrains.plugins.terminal.session.TerminalCloseEvent -import org.jetbrains.plugins.terminal.session.TerminalStateChangedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalCloseEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalStateChangedEvent import java.util.concurrent.atomic.AtomicInteger @OptIn(AwaitCancellationAndInvoke::class) diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/hyperlinks/BackendHyperlinkInfoServiceImpl.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/hyperlinks/BackendHyperlinkInfoServiceImpl.kt index 4a37a9bb4d50..28a8d7c9d482 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/hyperlinks/BackendHyperlinkInfoServiceImpl.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/hyperlinks/BackendHyperlinkInfoServiceImpl.kt @@ -5,7 +5,7 @@ import com.intellij.terminal.backend.TerminalSessionsManager import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalSessionId import org.jetbrains.plugins.terminal.hyperlinks.BackendHyperlinkInfo import org.jetbrains.plugins.terminal.hyperlinks.BackendHyperlinkInfoService -import org.jetbrains.plugins.terminal.session.TerminalHyperlinkId +import org.jetbrains.plugins.terminal.session.impl.TerminalHyperlinkId internal class BackendHyperlinkInfoServiceImpl : BackendHyperlinkInfoService { override fun getHyperlinkInfo(sessionId: TerminalSessionId, isAlternateBuffer: Boolean, hyperlinkId: TerminalHyperlinkId): BackendHyperlinkInfo? { diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/hyperlinks/BackendTerminalHyperlinkFacade.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/hyperlinks/BackendTerminalHyperlinkFacade.kt index 12669c53dda6..08f1656abba1 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/hyperlinks/BackendTerminalHyperlinkFacade.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/hyperlinks/BackendTerminalHyperlinkFacade.kt @@ -18,11 +18,11 @@ import org.jetbrains.plugins.terminal.block.reworked.TerminalOutputModel import org.jetbrains.plugins.terminal.block.reworked.hyperlinks.TerminalHyperlinksModel import org.jetbrains.plugins.terminal.fus.ReworkedTerminalUsageCollector import org.jetbrains.plugins.terminal.hyperlinks.BackendHyperlinkInfo -import org.jetbrains.plugins.terminal.session.TerminalHyperlinkId -import org.jetbrains.plugins.terminal.session.TerminalHyperlinksChangedEvent -import org.jetbrains.plugins.terminal.session.TerminalHyperlinksHeartbeatEvent -import org.jetbrains.plugins.terminal.session.TerminalHyperlinksModelState -import org.jetbrains.plugins.terminal.session.dto.toFilterResultInfo +import org.jetbrains.plugins.terminal.session.impl.TerminalHyperlinkId +import org.jetbrains.plugins.terminal.session.impl.TerminalHyperlinksChangedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalHyperlinksHeartbeatEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalHyperlinksModelState +import org.jetbrains.plugins.terminal.session.impl.dto.toFilterResultInfo @ApiStatus.Internal class BackendTerminalHyperlinkFacade( diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/hyperlinks/BackendTerminalHyperlinkHighlighter.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/hyperlinks/BackendTerminalHyperlinkHighlighter.kt index e92c847b5810..bb8d762c69f1 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/hyperlinks/BackendTerminalHyperlinkHighlighter.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/hyperlinks/BackendTerminalHyperlinkHighlighter.kt @@ -22,12 +22,10 @@ import kotlinx.coroutines.flow.* import org.jetbrains.annotations.TestOnly import org.jetbrains.plugins.terminal.block.hyperlinks.CompositeFilterWrapper import org.jetbrains.plugins.terminal.block.reworked.* -import org.jetbrains.plugins.terminal.block.reworked.TerminalLineIndex -import org.jetbrains.plugins.terminal.block.reworked.TerminalOffset -import org.jetbrains.plugins.terminal.session.TerminalHyperlinkId -import org.jetbrains.plugins.terminal.session.TerminalHyperlinksChangedEvent -import org.jetbrains.plugins.terminal.session.TerminalHyperlinksHeartbeatEvent -import org.jetbrains.plugins.terminal.session.dto.* +import org.jetbrains.plugins.terminal.session.impl.TerminalHyperlinkId +import org.jetbrains.plugins.terminal.session.impl.TerminalHyperlinksChangedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalHyperlinksHeartbeatEvent +import org.jetbrains.plugins.terminal.session.impl.dto.* import java.awt.event.MouseEvent import java.util.* import java.util.concurrent.LinkedBlockingDeque diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/rpc/TerminalInputChannelsManager.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/rpc/TerminalInputChannelsManager.kt index 4fd5307ac009..e3d2299905e9 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/rpc/TerminalInputChannelsManager.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/rpc/TerminalInputChannelsManager.kt @@ -15,7 +15,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalSessionId -import org.jetbrains.plugins.terminal.session.TerminalInputEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalInputEvent /** * Service that has separate instances for each [com.intellij.openapi.client.ClientAppSession] diff --git a/plugins/terminal/backend/src/com/intellij/terminal/backend/rpc/TerminalSessionApiImpl.kt b/plugins/terminal/backend/src/com/intellij/terminal/backend/rpc/TerminalSessionApiImpl.kt index e948cc934cfd..da5cdc93dfd3 100644 --- a/plugins/terminal/backend/src/com/intellij/terminal/backend/rpc/TerminalSessionApiImpl.kt +++ b/plugins/terminal/backend/src/com/intellij/terminal/backend/rpc/TerminalSessionApiImpl.kt @@ -5,9 +5,9 @@ import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.flow.Flow import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalSessionApi import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalSessionId -import org.jetbrains.plugins.terminal.session.TerminalInputEvent -import org.jetbrains.plugins.terminal.session.TerminalOutputEvent -import org.jetbrains.plugins.terminal.session.TerminalSession +import org.jetbrains.plugins.terminal.session.impl.TerminalInputEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalOutputEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalSession internal class TerminalSessionApiImpl : TerminalSessionApi { override suspend fun getInputChannel(sessionId: TerminalSessionId): SendChannel { diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/fus/FrontendLatencyServiceImpl.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/fus/FrontendLatencyServiceImpl.kt index 61ac914979d6..c36d24250e03 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/fus/FrontendLatencyServiceImpl.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/fus/FrontendLatencyServiceImpl.kt @@ -3,7 +3,7 @@ package com.intellij.terminal.frontend.fus import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.editor.impl.EditorImpl import org.jetbrains.plugins.terminal.fus.* -import org.jetbrains.plugins.terminal.session.TerminalContentUpdatedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalContentUpdatedEvent import java.util.concurrent.ArrayBlockingQueue import kotlin.time.TimeMark import kotlin.time.TimeSource diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/TerminalView.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/TerminalView.kt index 3359ced8582c..f6a64e5c6b82 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/TerminalView.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/TerminalView.kt @@ -3,10 +3,12 @@ package com.intellij.terminal.frontend.view import com.intellij.openapi.actionSystem.DataKey import com.intellij.terminal.TerminalTitle import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred import kotlinx.coroutines.flow.StateFlow import org.jetbrains.annotations.ApiStatus import org.jetbrains.plugins.terminal.block.reworked.TerminalOutputModel import org.jetbrains.plugins.terminal.session.TerminalGridSize +import org.jetbrains.plugins.terminal.session.TerminalStartupOptions import org.jetbrains.plugins.terminal.view.TerminalOutputModelsSet import org.jetbrains.plugins.terminal.view.TerminalSendTextBuilder import org.jetbrains.plugins.terminal.view.shellIntegration.TerminalShellIntegration @@ -29,6 +31,10 @@ interface TerminalView { val sessionState: StateFlow + val shellIntegrationDeferred: Deferred + + val startupOptionsDeferred: Deferred + suspend fun hasChildProcesses(): Boolean fun getCurrentDirectory(): String? @@ -37,10 +43,6 @@ interface TerminalView { fun createSendTextBuilder(): TerminalSendTextBuilder - fun getShellIntegration(): TerminalShellIntegration? - - suspend fun awaitShellIntegrationInitialized(): TerminalShellIntegration - // todo companion object { diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/hyperlinks/FrontendTerminalHyperlinkFacade.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/hyperlinks/FrontendTerminalHyperlinkFacade.kt index 781fc81558f7..d09965732dae 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/hyperlinks/FrontendTerminalHyperlinkFacade.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/hyperlinks/FrontendTerminalHyperlinkFacade.kt @@ -9,9 +9,9 @@ import kotlinx.coroutines.CoroutineScope import org.jetbrains.plugins.terminal.block.reworked.TerminalOffset import org.jetbrains.plugins.terminal.block.reworked.TerminalOutputModel import org.jetbrains.plugins.terminal.block.reworked.hyperlinks.TerminalHyperlinksModel -import org.jetbrains.plugins.terminal.session.* -import org.jetbrains.plugins.terminal.session.dto.TerminalHyperlinksModelStateDto -import org.jetbrains.plugins.terminal.session.dto.toFilterResultInfo +import org.jetbrains.plugins.terminal.session.impl.* +import org.jetbrains.plugins.terminal.session.impl.dto.TerminalHyperlinksModelStateDto +import org.jetbrains.plugins.terminal.session.impl.dto.toFilterResultInfo internal class FrontendTerminalHyperlinkFacade( private val isInAlternateBuffer: Boolean, diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalEventsHandlerImpl.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalEventsHandlerImpl.kt index ec238a3123ac..1ee052c7263f 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalEventsHandlerImpl.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalEventsHandlerImpl.kt @@ -14,10 +14,12 @@ import com.jediterm.terminal.emulator.mouse.MouseButtonCodes import com.jediterm.terminal.emulator.mouse.MouseButtonModifierFlags import com.jediterm.terminal.emulator.mouse.MouseFormat import com.jediterm.terminal.emulator.mouse.MouseMode +import kotlinx.coroutines.Deferred import org.jetbrains.plugins.terminal.TerminalOptionsProvider import org.jetbrains.plugins.terminal.block.reworked.* import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isOutputModelEditor -import org.jetbrains.plugins.terminal.session.TerminalState +import org.jetbrains.plugins.terminal.session.impl.TerminalState +import org.jetbrains.plugins.terminal.util.getNow import org.jetbrains.plugins.terminal.view.shellIntegration.TerminalOutputStatus import org.jetbrains.plugins.terminal.view.shellIntegration.TerminalShellIntegration import java.awt.Point @@ -26,7 +28,6 @@ import java.awt.event.KeyEvent import java.awt.event.MouseEvent import java.awt.event.MouseWheelEvent import java.nio.charset.Charset -import java.util.concurrent.CompletableFuture import javax.swing.SwingUtilities import kotlin.math.abs @@ -43,7 +44,7 @@ internal open class TerminalEventsHandlerImpl( private val settings: JBTerminalSystemSettingsProviderBase, private val scrollingModel: TerminalOutputScrollingModel?, private val outputModel: TerminalOutputModel, - private val shellIntegrationFuture: CompletableFuture?, + private val shellIntegrationDeferred: Deferred?, private val typeAhead: TerminalTypeAhead?, ) : TerminalEventsHandler { private var ignoreNextKeyTypedEvent: Boolean = false @@ -404,7 +405,7 @@ internal open class TerminalEventsHandlerImpl( private fun scheduleCompletionPopupIfNeeded(charTyped: Char) { val project = editor.project ?: return - val shellIntegration = shellIntegrationFuture?.getNow(null) ?: return + val shellIntegration = shellIntegrationDeferred?.getNow() ?: return if (editor.isOutputModelEditor && TerminalCommandCompletion.isEnabled() && TerminalOptionsProvider.instance.showCompletionPopupAutomatically diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalInput.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalInput.kt index 472bfbaddfa0..fb06f19a49dd 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalInput.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalInput.kt @@ -14,7 +14,8 @@ import kotlinx.coroutines.future.await import org.jetbrains.plugins.terminal.block.reworked.TerminalSessionModel import org.jetbrains.plugins.terminal.block.ui.sanitizeLineSeparators import org.jetbrains.plugins.terminal.fus.* -import org.jetbrains.plugins.terminal.session.* +import org.jetbrains.plugins.terminal.session.TerminalGridSize +import org.jetbrains.plugins.terminal.session.impl.* import org.jetbrains.plugins.terminal.view.impl.TerminalSendTextOptions import java.awt.event.KeyEvent import java.nio.charset.StandardCharsets diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalKeyEncodingManager.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalKeyEncodingManager.kt index 04624679df7f..041128ba0cfe 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalKeyEncodingManager.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalKeyEncodingManager.kt @@ -11,7 +11,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.jetbrains.annotations.ApiStatus import org.jetbrains.plugins.terminal.block.reworked.TerminalSessionModel -import org.jetbrains.plugins.terminal.session.TerminalState +import org.jetbrains.plugins.terminal.session.impl.TerminalState /** * Actually a wrapper around [TerminalKeyEncoder]. diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalOutputEventsHandler.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalOutputEventsHandler.kt index b0c811f7f4ad..dc910b082ce4 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalOutputEventsHandler.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalOutputEventsHandler.kt @@ -1,6 +1,6 @@ package com.intellij.terminal.frontend.view.impl -import org.jetbrains.plugins.terminal.session.TerminalOutputEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalOutputEvent internal interface TerminalOutputEventsHandler { suspend fun handleEvent(event: TerminalOutputEvent) diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalOutputModelController.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalOutputModelController.kt index a01d04699909..7279a8dcafda 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalOutputModelController.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalOutputModelController.kt @@ -2,8 +2,8 @@ package com.intellij.terminal.frontend.view.impl import com.intellij.util.concurrency.annotations.RequiresEdt import org.jetbrains.plugins.terminal.block.reworked.MutableTerminalOutputModel -import org.jetbrains.plugins.terminal.session.TerminalContentUpdatedEvent -import org.jetbrains.plugins.terminal.session.TerminalCursorPositionChangedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalContentUpdatedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalCursorPositionChangedEvent /** * A controller for the output model updates. diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalOutputModelControllerImpl.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalOutputModelControllerImpl.kt index c30644c5fc86..b9b4c1c1207e 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalOutputModelControllerImpl.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalOutputModelControllerImpl.kt @@ -2,8 +2,8 @@ package com.intellij.terminal.frontend.view.impl import org.jetbrains.plugins.terminal.block.reworked.MutableTerminalOutputModel import org.jetbrains.plugins.terminal.block.reworked.updateContent -import org.jetbrains.plugins.terminal.session.TerminalContentUpdatedEvent -import org.jetbrains.plugins.terminal.session.TerminalCursorPositionChangedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalContentUpdatedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalCursorPositionChangedEvent /** * Simple implementation of the [TerminalOutputModelController] that just updates the output model immediately. diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalSessionController.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalSessionController.kt index 5dc80cbe5a32..adf2c11193dc 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalSessionController.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalSessionController.kt @@ -12,9 +12,11 @@ import com.intellij.terminal.frontend.view.hyperlinks.FrontendTerminalHyperlinkF import com.intellij.util.containers.DisposableWrapperList import kotlinx.coroutines.* import org.jetbrains.plugins.terminal.block.reworked.TerminalSessionModel -import org.jetbrains.plugins.terminal.session.* -import org.jetbrains.plugins.terminal.session.dto.toState -import org.jetbrains.plugins.terminal.session.dto.toTerminalState +import org.jetbrains.plugins.terminal.session.TerminalStartupOptions +import org.jetbrains.plugins.terminal.session.impl.* +import org.jetbrains.plugins.terminal.session.impl.dto.toOptions +import org.jetbrains.plugins.terminal.session.impl.dto.toState +import org.jetbrains.plugins.terminal.session.impl.dto.toTerminalState import java.awt.Toolkit import kotlin.coroutines.cancellation.CancellationException @@ -24,6 +26,7 @@ internal class TerminalSessionController( private val outputHyperlinkFacade: FrontendTerminalHyperlinkFacade?, private val alternateBufferModelController: TerminalOutputModelController, private val alternateBufferHyperlinkFacade: FrontendTerminalHyperlinkFacade?, + private val startupOptionsDeferred: CompletableDeferred, private val settings: JBTerminalSystemSettingsProviderBase, private val coroutineScope: CoroutineScope, ) { @@ -67,6 +70,7 @@ internal class TerminalSessionController( when (event) { is TerminalInitialStateEvent -> { sessionModel.updateTerminalState(event.sessionState.toTerminalState()) + startupOptionsDeferred.complete(event.startupOptions.toOptions()) withContext(edtContext) { outputModelController.applyPendingUpdates() alternateBufferModelController.applyPendingUpdates() diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalShellIntegrationEventsHandler.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalShellIntegrationEventsHandler.kt index c8d171b849e8..cf132b02426e 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalShellIntegrationEventsHandler.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalShellIntegrationEventsHandler.kt @@ -4,26 +4,29 @@ import com.intellij.openapi.application.EDT import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.asContextElement import com.intellij.platform.util.coroutines.childScope +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.jetbrains.plugins.terminal.block.reworked.TerminalAliasesStorage -import org.jetbrains.plugins.terminal.session.* -import org.jetbrains.plugins.terminal.session.dto.toState +import org.jetbrains.plugins.terminal.block.reworked.TerminalSessionModel +import org.jetbrains.plugins.terminal.session.impl.* +import org.jetbrains.plugins.terminal.session.impl.dto.toState +import org.jetbrains.plugins.terminal.util.getNow import org.jetbrains.plugins.terminal.view.shellIntegration.TerminalShellIntegration import org.jetbrains.plugins.terminal.view.shellIntegration.impl.TerminalShellIntegrationImpl -import java.util.concurrent.CompletableFuture internal class TerminalShellIntegrationEventsHandler( private val outputModelController: TerminalOutputModelController, - private val shellIntegrationFuture: CompletableFuture, + private val sessionModel: TerminalSessionModel, + private val shellIntegrationDeferred: CompletableDeferred, private val aliasesStorage: TerminalAliasesStorage, private val coroutineScope: CoroutineScope, ) : TerminalOutputEventsHandler { private val edtContext = Dispatchers.EDT + ModalityState.any().asContextElement() private val shellIntegration: TerminalShellIntegrationImpl? - get() = shellIntegrationFuture.getNow(null) as? TerminalShellIntegrationImpl + get() = shellIntegrationDeferred.getNow() as? TerminalShellIntegrationImpl private fun getIntegrationOrThrow(): TerminalShellIntegrationImpl { return shellIntegration ?: error("Shell integration is not initialized yet") @@ -61,7 +64,7 @@ internal class TerminalShellIntegrationEventsHandler( is TerminalCommandStartedEvent -> { withContext(edtContext) { outputModelController.applyPendingUpdates() - getIntegrationOrThrow().onCommandStarted(outputModelController.model.cursorOffset) + getIntegrationOrThrow().onCommandStarted(outputModelController.model.cursorOffset, event.command) } } is TerminalCommandFinishedEvent -> { @@ -82,8 +85,9 @@ internal class TerminalShellIntegrationEventsHandler( private fun initShellIntegration() { val integration = TerminalShellIntegrationImpl( outputModelController.model, + sessionModel, coroutineScope.childScope("TerminalShellIntegration") ) - shellIntegrationFuture.complete(integration) + shellIntegrationDeferred.complete(integration) } } \ No newline at end of file diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalTypeAheadOutputModelController.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalTypeAheadOutputModelController.kt index 319c1e594d57..a7b08d6c0479 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalTypeAheadOutputModelController.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalTypeAheadOutputModelController.kt @@ -16,14 +16,14 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.debounce import org.jetbrains.plugins.terminal.block.reworked.* import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isReworkedTerminalEditor -import org.jetbrains.plugins.terminal.session.TerminalContentUpdatedEvent -import org.jetbrains.plugins.terminal.session.TerminalCursorPositionChangedEvent -import org.jetbrains.plugins.terminal.session.TerminalOutputEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalContentUpdatedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalCursorPositionChangedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalOutputEvent +import org.jetbrains.plugins.terminal.util.getNow import org.jetbrains.plugins.terminal.view.shellIntegration.TerminalCommandBlock import org.jetbrains.plugins.terminal.view.shellIntegration.TerminalOutputStatus import org.jetbrains.plugins.terminal.view.shellIntegration.TerminalShellIntegration import java.lang.Runnable -import java.util.concurrent.CompletableFuture /** * Implementation of the [TerminalOutputModelController] that supports type-ahead. @@ -33,11 +33,11 @@ import java.util.concurrent.CompletableFuture * This way, there will be no flickering when partial backend updates are applied on top of the predictions. * And if type-ahead predictions were incorrect, the user will see the actual state with a small delay. */ -@OptIn(FlowPreview::class) +@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class) internal class TerminalTypeAheadOutputModelController( private val project: Project, private val outputModel: MutableTerminalOutputModel, - private val shellIntegrationFuture: CompletableFuture, + private val shellIntegrationDeferred: Deferred, coroutineScope: CoroutineScope, ) : TerminalOutputModelController, TerminalTypeAhead { override val model: MutableTerminalOutputModel = outputModel @@ -60,7 +60,7 @@ internal class TerminalTypeAheadOutputModelController( private fun isTypeAheadEnabled(): Boolean { val enabledInRegistry = Registry.`is`("terminal.type.ahead", false) - val outputStatus = shellIntegrationFuture.getNow(null)?.outputStatus?.value + val outputStatus = shellIntegrationDeferred.getNow()?.outputStatus?.value return enabledInRegistry && outputStatus == TerminalOutputStatus.TypingCommand } @@ -78,7 +78,7 @@ internal class TerminalTypeAheadOutputModelController( override fun backspace() { if (!isTypeAheadEnabled()) return - val shellIntegration = shellIntegrationFuture.getNow(null)!! // isTypeAheadEnabled should guarantee that it is available + val shellIntegration = shellIntegrationDeferred.getCompleted() // isTypeAheadEnabled should guarantee that it is available val commandBlock = shellIntegration.blocksModel.activeBlock as? TerminalCommandBlock val cursorOffset = outputModel.cursorOffset val commandStartOffset = commandBlock?.commandStartOffset diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalVfsSynchronizer.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalVfsSynchronizer.kt index cf7623780a62..a68d6dcfd240 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalVfsSynchronizer.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalVfsSynchronizer.kt @@ -11,9 +11,7 @@ import com.intellij.openapi.util.Key import com.intellij.platform.util.coroutines.childScope import com.intellij.util.asDisposable import com.intellij.util.concurrency.annotations.RequiresEdt -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.cancel -import kotlinx.coroutines.isActive +import kotlinx.coroutines.* import org.jetbrains.plugins.terminal.block.reworked.TerminalOutputModel import org.jetbrains.plugins.terminal.view.shellIntegration.TerminalCommandExecutionListener import org.jetbrains.plugins.terminal.view.shellIntegration.TerminalCommandFinishedEvent @@ -21,11 +19,10 @@ import org.jetbrains.plugins.terminal.view.shellIntegration.TerminalShellIntegra import java.awt.event.FocusEvent import java.awt.event.FocusListener import java.awt.event.KeyEvent -import java.util.concurrent.CompletableFuture import javax.swing.JComponent internal class TerminalVfsSynchronizer( - shellIntegrationFuture: CompletableFuture, + shellIntegrationDeferred: Deferred, outputModel: TerminalOutputModel, terminalComponent: JComponent, coroutineScope: CoroutineScope, @@ -45,7 +42,9 @@ internal class TerminalVfsSynchronizer( init { val disposable = coroutineScope.asDisposable() - shellIntegrationFuture.thenAccept { shellIntegration -> + coroutineScope.launch(CoroutineName("Shell integration awaiting")) { + val shellIntegration = shellIntegrationDeferred.await() + // If we have events from the shell integration, we no more need heuristic-based refresher. heuristicBasedRefresherScope.cancel() LOG.debug { "Shell integration initialized, cancel heuristic-based VFS refresher." } diff --git a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalViewImpl.kt b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalViewImpl.kt index 626d842675d1..0f806b0f97be 100644 --- a/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalViewImpl.kt +++ b/plugins/terminal/frontend/src/com/intellij/terminal/frontend/view/impl/TerminalViewImpl.kt @@ -40,7 +40,6 @@ import kotlinx.coroutines.* import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.* -import kotlinx.coroutines.future.await import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.VisibleForTesting import org.jetbrains.plugins.terminal.TerminalPanelMarker @@ -59,8 +58,9 @@ import org.jetbrains.plugins.terminal.block.ui.isTerminalOutputScrollChangingAct import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils import org.jetbrains.plugins.terminal.fus.TerminalStartupFusInfo import org.jetbrains.plugins.terminal.session.TerminalGridSize -import org.jetbrains.plugins.terminal.session.TerminalHyperlinkId -import org.jetbrains.plugins.terminal.session.TerminalSession +import org.jetbrains.plugins.terminal.session.TerminalStartupOptions +import org.jetbrains.plugins.terminal.session.impl.TerminalHyperlinkId +import org.jetbrains.plugins.terminal.session.impl.TerminalSession import org.jetbrains.plugins.terminal.view.TerminalOutputModelsSet import org.jetbrains.plugins.terminal.view.TerminalSendTextBuilder import org.jetbrains.plugins.terminal.view.impl.TerminalOutputModelsSetImpl @@ -90,9 +90,7 @@ class TerminalViewImpl( private val sessionFuture: CompletableFuture = CompletableFuture() @VisibleForTesting - val shellIntegrationFuture: CompletableFuture = CompletableFuture() - - private val sessionModel: TerminalSessionModel + val sessionModel: TerminalSessionModel private val encodingManager: TerminalKeyEncodingManager private val controller: TerminalSessionController @@ -130,11 +128,13 @@ class TerminalViewImpl( private val mutableSessionState: MutableStateFlow = MutableStateFlow(TerminalViewSessionState.NotStarted) override val sessionState: StateFlow = mutableSessionState.asStateFlow() + override val shellIntegrationDeferred: CompletableDeferred = CompletableDeferred(coroutineScope.coroutineContext.job) + override val startupOptionsDeferred: CompletableDeferred = CompletableDeferred(coroutineScope.coroutineContext.job) + init { // Cancell the hanging callbacks that wait for future completion if the coroutine scope is cancelled. coroutineScope.coroutineContext.job.invokeOnCompletion { sessionFuture.cancel(true) - shellIntegrationFuture.cancel(true) } val hyperlinkScope = coroutineScope.childScope("TerminalViewImpl hyperlink facades") @@ -166,7 +166,7 @@ class TerminalViewImpl( settings, scrollingModel = null, alternateBufferModel, - shellIntegrationFuture = null, + shellIntegrationDeferred = null, typeAhead = null, ) configureOutputEditor( @@ -200,7 +200,7 @@ class TerminalViewImpl( val outputModelController = TerminalTypeAheadOutputModelController( project, outputModel, - shellIntegrationFuture, + shellIntegrationDeferred, coroutineScope.childScope("TerminalTypeAheadOutputModelController") ) outputEditor.putUserData(TerminalTypeAhead.KEY, outputModelController) @@ -213,7 +213,7 @@ class TerminalViewImpl( settings, scrollingModel, outputModel, - shellIntegrationFuture, + shellIntegrationDeferred, typeAhead = outputModelController ) @@ -256,12 +256,14 @@ class TerminalViewImpl( outputHyperlinkFacade, alternateBufferModelController, alternateBufferHyperlinkFacade, + startupOptionsDeferred, settings, coroutineScope.childScope("TerminalSessionController") ) val shellIntegrationEventsHandler = TerminalShellIntegrationEventsHandler( outputModelController, - shellIntegrationFuture, + sessionModel, + shellIntegrationDeferred, terminalAliasesStorage, coroutineScope.childScope("TerminalShellIntegrationEventsHandler"), ) @@ -278,7 +280,7 @@ class TerminalViewImpl( listenAlternateBufferSwitch() val synchronizer = TerminalVfsSynchronizer( - shellIntegrationFuture, + shellIntegrationDeferred, outputModel, terminalPanel, coroutineScope.childScope("TerminalVfsSynchronizer"), @@ -290,7 +292,7 @@ class TerminalViewImpl( ModalityState.any().asContextElement() + CoroutineName("Shell integration features init") ) { - val shellIntegration = awaitShellIntegrationInitialized() + val shellIntegration = shellIntegrationDeferred.await() outputEditor.putUserData(TerminalBlocksModel.KEY, shellIntegration.blocksModel) TerminalBlocksDecorator( @@ -345,14 +347,6 @@ class TerminalViewImpl( terminalInput.sendText(options) } - override fun getShellIntegration(): TerminalShellIntegration? { - return shellIntegrationFuture.getNow(null) - } - - override suspend fun awaitShellIntegrationInitialized(): TerminalShellIntegration { - return shellIntegrationFuture.await() - } - fun setTopComponent(component: JComponent, disposable: Disposable) { val resizeListener = object : ComponentAdapter() { override fun componentResized(e: ComponentEvent) { diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/output/PartialCommandOutput.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/output/PartialCommandOutput.kt index a4f5011b573f..4e8d30b89f21 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/output/PartialCommandOutput.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/output/PartialCommandOutput.kt @@ -1,7 +1,7 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package org.jetbrains.plugins.terminal.block.output -import org.jetbrains.plugins.terminal.session.StyleRange +import org.jetbrains.plugins.terminal.session.impl.StyleRange /** * @param styles ranges inside the [text] bounds. diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/output/TerminalOutputController.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/output/TerminalOutputController.kt index 294d79fde516..05481c285149 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/output/TerminalOutputController.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/output/TerminalOutputController.kt @@ -23,7 +23,7 @@ import org.jetbrains.plugins.terminal.block.prompt.TerminalPromptRenderingInfo import org.jetbrains.plugins.terminal.block.session.* import org.jetbrains.plugins.terminal.block.ui.* import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.IS_OUTPUT_EDITOR_KEY -import org.jetbrains.plugins.terminal.session.StyleRange +import org.jetbrains.plugins.terminal.session.impl.StyleRange import org.jetbrains.plugins.terminal.util.ShellType import org.jetbrains.plugins.terminal.util.terminalProjectScope import java.util.* diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/prompt/renderer/ShellPromptRenderer.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/prompt/renderer/ShellPromptRenderer.kt index 8be8d1172b82..4c59ba728429 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/prompt/renderer/ShellPromptRenderer.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/prompt/renderer/ShellPromptRenderer.kt @@ -18,7 +18,7 @@ import org.jetbrains.plugins.terminal.block.prompt.TerminalPromptState import org.jetbrains.plugins.terminal.block.session.ShellCommandOutputScraperImpl import org.jetbrains.plugins.terminal.block.session.StyledCommandOutput import org.jetbrains.plugins.terminal.block.ui.normalize -import org.jetbrains.plugins.terminal.session.StyleRange +import org.jetbrains.plugins.terminal.session.impl.StyleRange internal class ShellPromptRenderer( private val colorPalette: TerminalColorPalette, diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/MutableTerminalOutputModel.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/MutableTerminalOutputModel.kt index 8a99874a1548..8b0944d6f335 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/MutableTerminalOutputModel.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/MutableTerminalOutputModel.kt @@ -3,10 +3,10 @@ package org.jetbrains.plugins.terminal.block.reworked import com.intellij.openapi.editor.Document import org.jetbrains.annotations.ApiStatus -import org.jetbrains.plugins.terminal.session.StyleRange -import org.jetbrains.plugins.terminal.session.TerminalContentUpdatedEvent -import org.jetbrains.plugins.terminal.session.TerminalOutputModelState -import org.jetbrains.plugins.terminal.session.dto.toStyleRange +import org.jetbrains.plugins.terminal.session.impl.StyleRange +import org.jetbrains.plugins.terminal.session.impl.TerminalContentUpdatedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalOutputModelState +import org.jetbrains.plugins.terminal.session.impl.dto.toStyleRange /** * Model that should manage the terminal output content: text, highlightings, and cursor position. diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/MutableTerminalOutputModelImpl.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/MutableTerminalOutputModelImpl.kt index 827889883f37..a18c3d733466 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/MutableTerminalOutputModelImpl.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/MutableTerminalOutputModelImpl.kt @@ -15,8 +15,8 @@ import org.jetbrains.plugins.terminal.block.output.HighlightingInfo import org.jetbrains.plugins.terminal.block.output.TerminalOutputHighlightingsSnapshot import org.jetbrains.plugins.terminal.block.output.TextStyleAdapter import org.jetbrains.plugins.terminal.block.ui.BlockTerminalColorPalette -import org.jetbrains.plugins.terminal.session.StyleRange -import org.jetbrains.plugins.terminal.session.TerminalOutputModelState +import org.jetbrains.plugins.terminal.session.impl.StyleRange +import org.jetbrains.plugins.terminal.session.impl.TerminalOutputModelState /** * [maxOutputLength] limits the length of the document. Zero means unlimited length. diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/TerminalAliasesStorage.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/TerminalAliasesStorage.kt index e0231f836dff..2567b6913d8a 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/TerminalAliasesStorage.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/TerminalAliasesStorage.kt @@ -3,7 +3,7 @@ package org.jetbrains.plugins.terminal.block.reworked import com.intellij.openapi.util.Key import org.jetbrains.annotations.ApiStatus -import org.jetbrains.plugins.terminal.session.TerminalAliasesInfo +import org.jetbrains.plugins.terminal.session.impl.TerminalAliasesInfo @ApiStatus.Internal class TerminalAliasesStorage { diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/TerminalSessionModel.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/TerminalSessionModel.kt index 067560acfa05..cd4a80fecd46 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/TerminalSessionModel.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/TerminalSessionModel.kt @@ -4,7 +4,7 @@ package org.jetbrains.plugins.terminal.block.reworked import com.intellij.openapi.util.Key import kotlinx.coroutines.flow.StateFlow import org.jetbrains.annotations.ApiStatus -import org.jetbrains.plugins.terminal.session.TerminalState +import org.jetbrains.plugins.terminal.session.impl.TerminalState @ApiStatus.Internal interface TerminalSessionModel { diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/TerminalSessionModelImpl.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/TerminalSessionModelImpl.kt index d5136710c278..c8f2601d2a86 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/TerminalSessionModelImpl.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/TerminalSessionModelImpl.kt @@ -7,7 +7,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import org.jetbrains.annotations.ApiStatus -import org.jetbrains.plugins.terminal.session.TerminalState +import org.jetbrains.plugins.terminal.session.impl.TerminalState @ApiStatus.Internal class TerminalSessionModelImpl : TerminalSessionModel { diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/TerminalShellIntegrationEventsListener.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/TerminalShellIntegrationEventsListener.kt index f0057944c034..84d1cc1e73d1 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/TerminalShellIntegrationEventsListener.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/TerminalShellIntegrationEventsListener.kt @@ -2,7 +2,7 @@ package org.jetbrains.plugins.terminal.block.reworked import org.jetbrains.annotations.ApiStatus -import org.jetbrains.plugins.terminal.session.TerminalAliasesInfo +import org.jetbrains.plugins.terminal.session.impl.TerminalAliasesInfo import java.util.* @ApiStatus.Internal diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/hyperlinks/HyperlinkIdDataContextSerializer.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/hyperlinks/HyperlinkIdDataContextSerializer.kt index 29aa488b28c9..70e62c3a441a 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/hyperlinks/HyperlinkIdDataContextSerializer.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/hyperlinks/HyperlinkIdDataContextSerializer.kt @@ -4,7 +4,7 @@ package org.jetbrains.plugins.terminal.block.reworked.hyperlinks import com.intellij.ide.CustomDataContextSerializer import com.intellij.openapi.actionSystem.DataKey import kotlinx.serialization.KSerializer -import org.jetbrains.plugins.terminal.session.TerminalHyperlinkId +import org.jetbrains.plugins.terminal.session.impl.TerminalHyperlinkId internal class HyperlinkIdDataContextSerializer : CustomDataContextSerializer { override val key: DataKey diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/hyperlinks/TerminalHyperlinksModel.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/hyperlinks/TerminalHyperlinksModel.kt index 9bd2f105b1f4..94ef2c19063a 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/hyperlinks/TerminalHyperlinksModel.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/hyperlinks/TerminalHyperlinksModel.kt @@ -5,9 +5,9 @@ import com.intellij.openapi.diagnostic.logger import org.jetbrains.annotations.ApiStatus import org.jetbrains.plugins.terminal.block.reworked.TerminalOffset import org.jetbrains.plugins.terminal.block.reworked.TerminalOutputModel -import org.jetbrains.plugins.terminal.session.TerminalFilterResultInfo -import org.jetbrains.plugins.terminal.session.TerminalHyperlinkId -import org.jetbrains.plugins.terminal.session.TerminalHyperlinksModelState +import org.jetbrains.plugins.terminal.session.impl.TerminalFilterResultInfo +import org.jetbrains.plugins.terminal.session.impl.TerminalHyperlinkId +import org.jetbrains.plugins.terminal.session.impl.TerminalHyperlinksModelState @ApiStatus.Internal class TerminalHyperlinksModel(private val debugName: String, private val outputModel: TerminalOutputModel) { diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/session/FrontendTerminalSession.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/session/FrontendTerminalSession.kt index 7abacc89d6d7..093de2dd6a09 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/session/FrontendTerminalSession.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/session/FrontendTerminalSession.kt @@ -10,10 +10,10 @@ import kotlinx.coroutines.flow.onEach import org.jetbrains.annotations.ApiStatus import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalSessionApi import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalSessionId -import org.jetbrains.plugins.terminal.session.TerminalInputEvent -import org.jetbrains.plugins.terminal.session.TerminalOutputEvent -import org.jetbrains.plugins.terminal.session.TerminalSession -import org.jetbrains.plugins.terminal.session.TerminalSessionTerminatedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalInputEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalOutputEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalSession +import org.jetbrains.plugins.terminal.session.impl.TerminalSessionTerminatedEvent /** * [TerminalSession] implementation that is delegating the methods to [TerminalSessionApi] RPC to backend. diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/session/ShellStartupOptionsDto.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/session/ShellStartupOptionsDto.kt index f5968c3139b9..6de2b8a30932 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/session/ShellStartupOptionsDto.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/session/ShellStartupOptionsDto.kt @@ -5,9 +5,9 @@ import kotlinx.serialization.Contextual import kotlinx.serialization.Serializable import org.jetbrains.annotations.ApiStatus import org.jetbrains.plugins.terminal.ShellStartupOptions -import org.jetbrains.plugins.terminal.session.dto.TerminalSizeDto -import org.jetbrains.plugins.terminal.session.dto.toDto -import org.jetbrains.plugins.terminal.session.dto.toTermSize +import org.jetbrains.plugins.terminal.session.impl.dto.TerminalSizeDto +import org.jetbrains.plugins.terminal.session.impl.dto.toDto +import org.jetbrains.plugins.terminal.session.impl.dto.toTermSize /** * DTO for [org.jetbrains.plugins.terminal.ShellStartupOptions]. diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/session/rpc/TerminalSessionApi.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/session/rpc/TerminalSessionApi.kt index f0702d1181b6..fba92ad7d909 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/session/rpc/TerminalSessionApi.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/reworked/session/rpc/TerminalSessionApi.kt @@ -8,8 +8,8 @@ import fleet.rpc.remoteApiDescriptor import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.flow.Flow import org.jetbrains.annotations.ApiStatus -import org.jetbrains.plugins.terminal.session.TerminalInputEvent -import org.jetbrains.plugins.terminal.session.TerminalOutputEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalInputEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalOutputEvent @ApiStatus.Internal @Rpc diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/session/ShellCommandOutputScraperImpl.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/session/ShellCommandOutputScraperImpl.kt index aa70611ee04e..dcd0db6ca71e 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/session/ShellCommandOutputScraperImpl.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/session/ShellCommandOutputScraperImpl.kt @@ -8,7 +8,7 @@ import org.jetbrains.plugins.terminal.TerminalUtil import org.jetbrains.plugins.terminal.block.session.scraper.* import org.jetbrains.plugins.terminal.block.session.util.Debouncer import org.jetbrains.plugins.terminal.block.ui.withLock -import org.jetbrains.plugins.terminal.session.StyleRange +import org.jetbrains.plugins.terminal.session.impl.StyleRange import org.jetbrains.plugins.terminal.util.addModelListener import java.util.concurrent.CopyOnWriteArrayList import kotlin.math.max diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/session/scraper/StylesCollectingTerminalLinesCollector.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/session/scraper/StylesCollectingTerminalLinesCollector.kt index 9d1183d49d07..3496f34d39dc 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/block/session/scraper/StylesCollectingTerminalLinesCollector.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/block/session/scraper/StylesCollectingTerminalLinesCollector.kt @@ -6,7 +6,7 @@ import com.jediterm.terminal.model.TerminalLine import org.jetbrains.annotations.ApiStatus import org.jetbrains.plugins.terminal.block.ui.TerminalUiUtils import org.jetbrains.plugins.terminal.block.ui.normalize -import org.jetbrains.plugins.terminal.session.StyleRange +import org.jetbrains.plugins.terminal.session.impl.StyleRange @ApiStatus.Internal class StylesCollectingTerminalLinesCollector( diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/FrontendLatencyService.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/FrontendLatencyService.kt index 55a6c7bf799b..e995f8cc444a 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/FrontendLatencyService.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/fus/FrontendLatencyService.kt @@ -4,7 +4,7 @@ package org.jetbrains.plugins.terminal.fus import com.intellij.openapi.components.service import com.intellij.openapi.editor.impl.EditorImpl import org.jetbrains.annotations.ApiStatus -import org.jetbrains.plugins.terminal.session.TerminalContentUpdatedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalContentUpdatedEvent @ApiStatus.Internal interface FrontendLatencyService { diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/hyperlinks/BackendHyperlinkInfoService.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/hyperlinks/BackendHyperlinkInfoService.kt index 5a195060845f..91ac445257b7 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/hyperlinks/BackendHyperlinkInfoService.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/hyperlinks/BackendHyperlinkInfoService.kt @@ -5,7 +5,7 @@ import com.intellij.execution.filters.HyperlinkInfo import com.intellij.openapi.components.service import org.jetbrains.annotations.ApiStatus import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalSessionId -import org.jetbrains.plugins.terminal.session.TerminalHyperlinkId +import org.jetbrains.plugins.terminal.session.impl.TerminalHyperlinkId import java.awt.event.MouseEvent /** diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/hyperlinks/HyperlinkContextMenuActionGroup.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/hyperlinks/HyperlinkContextMenuActionGroup.kt index 6fb895d3f4bc..27bcefcdff5c 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/hyperlinks/HyperlinkContextMenuActionGroup.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/hyperlinks/HyperlinkContextMenuActionGroup.kt @@ -11,7 +11,7 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.trace import org.jetbrains.plugins.terminal.block.reworked.session.rpc.TerminalSessionId import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.IS_ALTERNATE_BUFFER_DATA_KEY -import org.jetbrains.plugins.terminal.session.TerminalHyperlinkId +import org.jetbrains.plugins.terminal.session.impl.TerminalHyperlinkId internal class HyperlinkContextMenuActionGroup : ActionGroup(), ActionRemoteBehaviorSpecification.BackendOnly { override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/ShellName.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/ShellName.kt new file mode 100644 index 000000000000..a688669db02b --- /dev/null +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/ShellName.kt @@ -0,0 +1,20 @@ +// 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.plugins.terminal.session + +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Experimental +sealed interface ShellName { + val value: String + + companion object { + fun of(value: String): ShellName = ShellNameImpl(value.lowercase()) + + val BASH: ShellName = of("bash") + val ZSH: ShellName = of("zsh") + val FISH: ShellName = of("fish") + val POWERSHELL: ShellName = of("powershell") + } +} + +private class ShellNameImpl(override val value: String) : ShellName \ No newline at end of file diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalStartupOptions.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalStartupOptions.kt new file mode 100644 index 000000000000..3fb0ea05aa19 --- /dev/null +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalStartupOptions.kt @@ -0,0 +1,20 @@ +// 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.plugins.terminal.session + +import com.intellij.util.PathUtil +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Experimental +@ApiStatus.NonExtendable +interface TerminalStartupOptions { + val shellCommand: List + val workingDirectory: String + val envVariables: Map +} + +@ApiStatus.Experimental +fun TerminalStartupOptions.getShellName(): ShellName { + val executablePath = shellCommand.first() // it should be guaranteed that it is not empty + val executableName = PathUtil.getFileName(executablePath) + return ShellName.of(executableName) +} \ No newline at end of file diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/StyleRange.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/StyleRange.kt similarity index 87% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/StyleRange.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/StyleRange.kt index 4bdb5721baa1..19816468274f 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/StyleRange.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/StyleRange.kt @@ -1,5 +1,5 @@ // 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.plugins.terminal.session +package org.jetbrains.plugins.terminal.session.impl import com.jediterm.terminal.TextStyle import org.jetbrains.annotations.ApiStatus diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalAliasesInfo.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalAliasesInfo.kt similarity index 85% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalAliasesInfo.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalAliasesInfo.kt index 93ed91026c2a..e5996884b68d 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalAliasesInfo.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalAliasesInfo.kt @@ -1,5 +1,5 @@ // 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.plugins.terminal.session +package org.jetbrains.plugins.terminal.session.impl import kotlinx.serialization.Serializable import org.jetbrains.annotations.ApiStatus diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalBlocksModelState.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalBlocksModelState.kt similarity index 87% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalBlocksModelState.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalBlocksModelState.kt index b79e49c7021c..e2d0f75512ea 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalBlocksModelState.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalBlocksModelState.kt @@ -1,5 +1,5 @@ // 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.plugins.terminal.session +package org.jetbrains.plugins.terminal.session.impl import org.jetbrains.annotations.ApiStatus import org.jetbrains.plugins.terminal.view.shellIntegration.TerminalBlockBase diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalHyperlinksModelState.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalHyperlinksModelState.kt similarity index 97% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalHyperlinksModelState.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalHyperlinksModelState.kt index 4f0a32ef94f6..0874698e75ed 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalHyperlinksModelState.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalHyperlinksModelState.kt @@ -1,5 +1,5 @@ // 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.plugins.terminal.session +package org.jetbrains.plugins.terminal.session.impl import com.intellij.execution.filters.HyperlinkInfo import com.intellij.execution.impl.EditorTextDecorationId diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalInputEvent.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalInputEvent.kt similarity index 94% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalInputEvent.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalInputEvent.kt index 174867a18778..27cdc6b9feae 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalInputEvent.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalInputEvent.kt @@ -1,10 +1,11 @@ // 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.plugins.terminal.session +package org.jetbrains.plugins.terminal.session.impl import com.intellij.openapi.editor.event.EditorMouseEvent import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import org.jetbrains.annotations.ApiStatus +import org.jetbrains.plugins.terminal.session.TerminalGridSize import java.util.concurrent.atomic.AtomicInteger @ApiStatus.Internal diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalOutputEvent.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalOutputEvent.kt similarity index 96% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalOutputEvent.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalOutputEvent.kt index 588bf7ddeeea..ad27291de2a8 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalOutputEvent.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalOutputEvent.kt @@ -1,10 +1,10 @@ // 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.plugins.terminal.session +package org.jetbrains.plugins.terminal.session.impl import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import org.jetbrains.annotations.ApiStatus -import org.jetbrains.plugins.terminal.session.dto.* +import org.jetbrains.plugins.terminal.session.impl.dto.* import kotlin.time.TimeMark @ApiStatus.Internal @@ -44,6 +44,7 @@ data object TerminalSessionTerminatedEvent : TerminalOutputEvent @ApiStatus.Internal @Serializable data class TerminalInitialStateEvent( + val startupOptions: TerminalStartupOptionsDto, val sessionState: TerminalStateDto, val outputModelState: TerminalOutputModelStateDto, val alternateBufferState: TerminalOutputModelStateDto, diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalOutputModelState.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalOutputModelState.kt similarity index 88% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalOutputModelState.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalOutputModelState.kt index 57aa7bbde8b4..dd3033c1560b 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalOutputModelState.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalOutputModelState.kt @@ -1,5 +1,5 @@ // 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.plugins.terminal.session +package org.jetbrains.plugins.terminal.session.impl import org.jetbrains.annotations.ApiStatus diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalSession.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalSession.kt similarity index 96% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalSession.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalSession.kt index 12d12ae9d1ff..c65c9056787f 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalSession.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalSession.kt @@ -1,5 +1,5 @@ // 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.plugins.terminal.session +package org.jetbrains.plugins.terminal.session.impl import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.flow.Flow diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalStartupOptionsImpl.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalStartupOptionsImpl.kt new file mode 100644 index 000000000000..136515670815 --- /dev/null +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalStartupOptionsImpl.kt @@ -0,0 +1,12 @@ +// 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.plugins.terminal.session.impl + +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.plugins.terminal.session.TerminalStartupOptions + +@ApiStatus.Internal +data class TerminalStartupOptionsImpl( + override val shellCommand: List, + override val workingDirectory: String, + override val envVariables: Map, +) : TerminalStartupOptions \ No newline at end of file diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalState.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalState.kt similarity index 94% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalState.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalState.kt index 3cbe19c7725b..269b53004c37 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/TerminalState.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/TerminalState.kt @@ -1,5 +1,5 @@ // 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.plugins.terminal.session +package org.jetbrains.plugins.terminal.session.impl import com.jediterm.terminal.CursorShape import com.jediterm.terminal.emulator.mouse.MouseFormat diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/CursorShapeDto.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/CursorShapeDto.kt similarity index 96% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/CursorShapeDto.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/CursorShapeDto.kt index 2c3869beb3c6..0744969676f0 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/CursorShapeDto.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/CursorShapeDto.kt @@ -1,5 +1,5 @@ // 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.plugins.terminal.session.dto +package org.jetbrains.plugins.terminal.session.impl.dto import com.jediterm.terminal.CursorShape import kotlinx.serialization.Serializable diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/MouseFormatDto.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/MouseFormatDto.kt similarity index 95% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/MouseFormatDto.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/MouseFormatDto.kt index c2e9f2657c71..f88fa97c648c 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/MouseFormatDto.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/MouseFormatDto.kt @@ -1,5 +1,5 @@ // 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.plugins.terminal.session.dto +package org.jetbrains.plugins.terminal.session.impl.dto import com.jediterm.terminal.emulator.mouse.MouseFormat import kotlinx.serialization.Serializable diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/MouseModeDto.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/MouseModeDto.kt similarity index 96% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/MouseModeDto.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/MouseModeDto.kt index 0821fc7727ea..d99314491cc0 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/MouseModeDto.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/MouseModeDto.kt @@ -1,5 +1,5 @@ // 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.plugins.terminal.session.dto +package org.jetbrains.plugins.terminal.session.impl.dto import com.jediterm.terminal.emulator.mouse.MouseMode import kotlinx.serialization.Serializable diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/StyleRangeDto.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/StyleRangeDto.kt similarity index 85% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/StyleRangeDto.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/StyleRangeDto.kt index 9221d5d55639..382e157e40f1 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/StyleRangeDto.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/StyleRangeDto.kt @@ -1,9 +1,9 @@ // 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.plugins.terminal.session.dto +package org.jetbrains.plugins.terminal.session.impl.dto import kotlinx.serialization.Serializable import org.jetbrains.annotations.ApiStatus -import org.jetbrains.plugins.terminal.session.StyleRange +import org.jetbrains.plugins.terminal.session.impl.StyleRange @ApiStatus.Internal @Serializable diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalBlocksModelStateDto.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalBlocksModelStateDto.kt similarity index 87% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalBlocksModelStateDto.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalBlocksModelStateDto.kt index b8f03fa1c2d1..644bb0f9e118 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalBlocksModelStateDto.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalBlocksModelStateDto.kt @@ -1,9 +1,9 @@ // 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.plugins.terminal.session.dto +package org.jetbrains.plugins.terminal.session.impl.dto import kotlinx.serialization.Serializable import org.jetbrains.annotations.ApiStatus -import org.jetbrains.plugins.terminal.session.TerminalBlocksModelState +import org.jetbrains.plugins.terminal.session.impl.TerminalBlocksModelState import org.jetbrains.plugins.terminal.view.shellIntegration.TerminalCommandBlock @Serializable diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalColorDto.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalColorDto.kt similarity index 94% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalColorDto.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalColorDto.kt index dcf0d1c5ddb0..5257cdfde6b3 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalColorDto.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalColorDto.kt @@ -1,5 +1,5 @@ // 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.plugins.terminal.session.dto +package org.jetbrains.plugins.terminal.session.impl.dto import com.jediterm.terminal.TerminalColor import kotlinx.serialization.Serializable diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalCommandBlockDto.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalCommandBlockDto.kt similarity index 84% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalCommandBlockDto.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalCommandBlockDto.kt index 1d67f03e7221..a56f8955f86f 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalCommandBlockDto.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalCommandBlockDto.kt @@ -1,5 +1,5 @@ // 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.plugins.terminal.session.dto +package org.jetbrains.plugins.terminal.session.impl.dto import kotlinx.serialization.Serializable import org.jetbrains.annotations.ApiStatus @@ -16,6 +16,8 @@ data class TerminalCommandBlockDto( val endOffset: Long, val commandStartOffset: Long?, val outputStartOffset: Long?, + val workingDirectory: String?, + val executedCommand: String?, val exitCode: Int?, ) @@ -27,6 +29,8 @@ fun TerminalCommandBlock.toDto(): TerminalCommandBlockDto { endOffset = endOffset.toAbsolute(), commandStartOffset = commandStartOffset?.toAbsolute(), outputStartOffset = outputStartOffset?.toAbsolute(), + workingDirectory = workingDirectory, + executedCommand = executedCommand, exitCode = exitCode, ) } @@ -39,6 +43,8 @@ fun TerminalCommandBlockDto.toCommandBlock(): TerminalCommandBlock { endOffset = TerminalOffset.of(endOffset), commandStartOffset = commandStartOffset?.let { TerminalOffset.of(it) }, outputStartOffset = outputStartOffset?.let { TerminalOffset.of(it) }, + workingDirectory = workingDirectory, + executedCommand = executedCommand, exitCode = exitCode, ) } \ No newline at end of file diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalHyperlinksDto.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalHyperlinksDto.kt similarity index 97% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalHyperlinksDto.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalHyperlinksDto.kt index d34dae220ae7..570d4eb55c80 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalHyperlinksDto.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalHyperlinksDto.kt @@ -1,5 +1,5 @@ // 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.plugins.terminal.session.dto +package org.jetbrains.plugins.terminal.session.impl.dto import com.intellij.execution.filters.HyperlinkInfo import com.intellij.execution.impl.InlayProvider @@ -10,7 +10,7 @@ import com.intellij.openapi.editor.markup.TextAttributes import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import org.jetbrains.annotations.ApiStatus -import org.jetbrains.plugins.terminal.session.* +import org.jetbrains.plugins.terminal.session.impl.* @ApiStatus.Internal @Serializable diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalOutputModelStateDto.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalOutputModelStateDto.kt similarity index 90% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalOutputModelStateDto.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalOutputModelStateDto.kt index 040e824f34be..f4a06bcaf000 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalOutputModelStateDto.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalOutputModelStateDto.kt @@ -1,9 +1,9 @@ // 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.plugins.terminal.session.dto +package org.jetbrains.plugins.terminal.session.impl.dto import kotlinx.serialization.Serializable import org.jetbrains.annotations.ApiStatus -import org.jetbrains.plugins.terminal.session.TerminalOutputModelState +import org.jetbrains.plugins.terminal.session.impl.TerminalOutputModelState @Serializable @ApiStatus.Internal diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalSizeDto.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalSizeDto.kt similarity index 90% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalSizeDto.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalSizeDto.kt index 68e2deca7fd6..71bbe42ff9a4 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalSizeDto.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalSizeDto.kt @@ -1,5 +1,5 @@ // 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.plugins.terminal.session.dto +package org.jetbrains.plugins.terminal.session.impl.dto import com.jediterm.core.util.TermSize import kotlinx.serialization.Serializable diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalStartupOptionsDto.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalStartupOptionsDto.kt new file mode 100644 index 000000000000..2b901bfd9458 --- /dev/null +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalStartupOptionsDto.kt @@ -0,0 +1,33 @@ +// 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.plugins.terminal.session.impl.dto + +import kotlinx.serialization.Serializable +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.plugins.terminal.session.TerminalStartupOptions +import org.jetbrains.plugins.terminal.session.impl.TerminalStartupOptionsImpl + +@ApiStatus.Internal +@Serializable +data class TerminalStartupOptionsDto( + val shellCommand: List, + val workingDirectory: String, + val envVariables: Map, +) + +@ApiStatus.Internal +fun TerminalStartupOptions.toDto(): TerminalStartupOptionsDto { + return TerminalStartupOptionsDto( + shellCommand = shellCommand, + workingDirectory = workingDirectory, + envVariables = envVariables + ) +} + +@ApiStatus.Internal +fun TerminalStartupOptionsDto.toOptions(): TerminalStartupOptions { + return TerminalStartupOptionsImpl( + shellCommand = shellCommand, + workingDirectory = workingDirectory, + envVariables = envVariables + ) +} \ No newline at end of file diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalStateDto.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalStateDto.kt similarity index 94% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalStateDto.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalStateDto.kt index a01b9c0719bc..4daf7be66c34 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TerminalStateDto.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TerminalStateDto.kt @@ -1,9 +1,9 @@ // 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.plugins.terminal.session.dto +package org.jetbrains.plugins.terminal.session.impl.dto import kotlinx.serialization.Serializable import org.jetbrains.annotations.ApiStatus -import org.jetbrains.plugins.terminal.session.TerminalState +import org.jetbrains.plugins.terminal.session.impl.TerminalState @ApiStatus.Internal @Serializable diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TextStyleDto.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TextStyleDto.kt similarity index 94% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TextStyleDto.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TextStyleDto.kt index 01d1060f01d4..de83bb531fb0 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TextStyleDto.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TextStyleDto.kt @@ -1,5 +1,5 @@ // 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.plugins.terminal.session.dto +package org.jetbrains.plugins.terminal.session.impl.dto import com.jediterm.terminal.TextStyle import kotlinx.serialization.Serializable diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TextStyleOptionDto.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TextStyleOptionDto.kt similarity index 96% rename from plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TextStyleOptionDto.kt rename to plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TextStyleOptionDto.kt index 83419798d3e9..735350eac814 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/session/dto/TextStyleOptionDto.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/session/impl/dto/TextStyleOptionDto.kt @@ -1,5 +1,5 @@ // 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.plugins.terminal.session.dto +package org.jetbrains.plugins.terminal.session.impl.dto import com.jediterm.terminal.TextStyle import kotlinx.serialization.Serializable diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/util/terminalCoroutine.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/util/terminalCoroutine.kt index 2147e1d1b35b..2ac4926a8c83 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/util/terminalCoroutine.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/util/terminalCoroutine.kt @@ -5,8 +5,16 @@ import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.ExperimentalCoroutinesApi import org.jetbrains.annotations.ApiStatus +@ApiStatus.Experimental +@OptIn(ExperimentalCoroutinesApi::class) +fun Deferred.getNow(): T? { + return if (isCompleted && !isCancelled) getCompleted() else null +} + @ApiStatus.Internal fun terminalProjectScope(project: Project): CoroutineScope { return project.service().coroutineScope diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/view/shellIntegration/TerminalBlocks.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/view/shellIntegration/TerminalBlocks.kt index 28e6ae5b0875..641f3b357f3f 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/view/shellIntegration/TerminalBlocks.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/view/shellIntegration/TerminalBlocks.kt @@ -19,6 +19,13 @@ interface TerminalBlockBase { interface TerminalCommandBlock : TerminalBlockBase { val commandStartOffset: TerminalOffset? val outputStartOffset: TerminalOffset? + + val workingDirectory: String? + /** + * Should be non-null if the command was started to execute. + * It is the command text reported by the shell integration right before it is started. + */ + val executedCommand: String? val exitCode: Int? } @@ -26,13 +33,14 @@ interface TerminalCommandBlock : TerminalBlockBase { * @param model regular terminal output model (not alternative one) * @return the text without trailing whitespaces that was typed at the place of the command. * Can return null if this block doesn't contain the command - * or if the command became partially output of model bounds because of trimming. + * or if the command became partial out of model bounds because of trimming. * - * **Note**: the return text might be not exactly the command, because currently, - * we do not take the right part of the prompt and inline completion grey text (provided by the shell plugins) into account. + * **Note**: the returned text might be different from [TerminalCommandBlock.executedCommand], because currently, + * we do not detect the right part of the prompt and inline completion grey text (provided by the shell plugins). + * But this method can be called before command execution is started to get the currently typed command. */ @ApiStatus.Experimental -fun TerminalCommandBlock.getCommandText(model: TerminalOutputModel): String? { +fun TerminalCommandBlock.getTypedCommandText(model: TerminalOutputModel): String? { val start = commandStartOffset ?: return null val end = outputStartOffset ?: endOffset if (start < model.startOffset || start > model.endOffset diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/view/shellIntegration/impl/TerminalBlocksModelImpl.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/view/shellIntegration/impl/TerminalBlocksModelImpl.kt index bfe9400f1bef..d8d251ba6b9f 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/view/shellIntegration/impl/TerminalBlocksModelImpl.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/view/shellIntegration/impl/TerminalBlocksModelImpl.kt @@ -3,14 +3,19 @@ package org.jetbrains.plugins.terminal.view.shellIntegration.impl import com.intellij.openapi.Disposable import com.intellij.util.EventDispatcher +import com.intellij.util.text.nullize import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.VisibleForTesting import org.jetbrains.plugins.terminal.block.reworked.* -import org.jetbrains.plugins.terminal.session.TerminalBlocksModelState +import org.jetbrains.plugins.terminal.session.impl.TerminalBlocksModelState import org.jetbrains.plugins.terminal.view.shellIntegration.* @ApiStatus.Internal -class TerminalBlocksModelImpl(private val outputModel: TerminalOutputModel, parentDisposable: Disposable) : TerminalBlocksModel { +class TerminalBlocksModelImpl( + private val outputModel: TerminalOutputModel, + private val sessionModel: TerminalSessionModel, + parentDisposable: Disposable, +) : TerminalBlocksModel { @VisibleForTesting var blockIdCounter: Int = 0 @@ -133,6 +138,8 @@ class TerminalBlocksModelImpl(private val outputModel: TerminalOutputModel, pare endOffset = outputModel.endOffset, commandStartOffset = null, outputStartOffset = null, + workingDirectory = sessionModel.terminalState.value.currentDirectory.nullize(), // it can be empty string, so use nullize() + executedCommand = null, exitCode = null, ) } diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/view/shellIntegration/impl/TerminalCommandBlockImpl.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/view/shellIntegration/impl/TerminalCommandBlockImpl.kt index 176343bd0290..6da60a350c5a 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/view/shellIntegration/impl/TerminalCommandBlockImpl.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/view/shellIntegration/impl/TerminalCommandBlockImpl.kt @@ -13,5 +13,7 @@ data class TerminalCommandBlockImpl( override val endOffset: TerminalOffset, override val commandStartOffset: TerminalOffset?, override val outputStartOffset: TerminalOffset?, + override val workingDirectory: String?, + override val executedCommand: String?, override val exitCode: Int?, ) : TerminalCommandBlock \ No newline at end of file diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/view/shellIntegration/impl/TerminalShellIntegrationImpl.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/view/shellIntegration/impl/TerminalShellIntegrationImpl.kt index 4ff314933965..a36a1386bbda 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/view/shellIntegration/impl/TerminalShellIntegrationImpl.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/view/shellIntegration/impl/TerminalShellIntegrationImpl.kt @@ -11,15 +11,17 @@ import kotlinx.coroutines.flow.asStateFlow import org.jetbrains.annotations.ApiStatus import org.jetbrains.plugins.terminal.block.reworked.TerminalOffset import org.jetbrains.plugins.terminal.block.reworked.TerminalOutputModel +import org.jetbrains.plugins.terminal.block.reworked.TerminalSessionModel import org.jetbrains.plugins.terminal.view.shellIntegration.* import org.jetbrains.plugins.terminal.view.shellIntegration.TerminalOutputStatus.* @ApiStatus.Internal class TerminalShellIntegrationImpl( private val outputModel: TerminalOutputModel, + sessionModel: TerminalSessionModel, coroutineScope: CoroutineScope, ) : TerminalShellIntegration { - override val blocksModel: TerminalBlocksModelImpl = TerminalBlocksModelImpl(outputModel, coroutineScope.asDisposable()) + override val blocksModel: TerminalBlocksModelImpl = TerminalBlocksModelImpl(outputModel, sessionModel, coroutineScope.asDisposable()) private val dispatcher = EventDispatcher.create(TerminalCommandExecutionListener::class.java) @@ -42,9 +44,9 @@ class TerminalShellIntegrationImpl( mutableOutputStatus.value = TypingCommand } - fun onCommandStarted(offset: TerminalOffset) { + fun onCommandStarted(offset: TerminalOffset, command: String) { blocksModel.updateActiveCommandBlock { block -> - block.copy(outputStartOffset = offset) + block.copy(outputStartOffset = offset, executedCommand = command) } mutableOutputStatus.value = ExecutingCommand diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/TerminalBlocksModelTest.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/TerminalBlocksModelTest.kt index 4bd6081d2e29..fb69aa98bf22 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/TerminalBlocksModelTest.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/TerminalBlocksModelTest.kt @@ -9,7 +9,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking import org.jetbrains.plugins.terminal.block.reworked.MutableTerminalOutputModel import org.jetbrains.plugins.terminal.block.reworked.TerminalOffset -import org.jetbrains.plugins.terminal.session.TerminalBlocksModelState +import org.jetbrains.plugins.terminal.block.reworked.TerminalOutputModel +import org.jetbrains.plugins.terminal.block.reworked.TerminalSessionModelImpl +import org.jetbrains.plugins.terminal.session.impl.TerminalBlocksModelState import org.jetbrains.plugins.terminal.view.shellIntegration.* import org.jetbrains.plugins.terminal.view.shellIntegration.impl.TerminalBlocksModelImpl import org.jetbrains.plugins.terminal.view.shellIntegration.impl.TerminalCommandBlockImpl @@ -25,7 +27,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `initial block is created from empty output model`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel() - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) assertEquals(1, blocksModel.blocks.size) val block = blocksModel.activeBlock as TerminalCommandBlock @@ -36,7 +38,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { assertEquals(null, block.commandStartOffset) assertEquals(null, block.outputStartOffset) assertEquals(null, block.exitCode) - assertEquals(null, block.getCommandText(outputModel)) + assertEquals(null, block.getTypedCommandText(outputModel)) assertEquals(null, block.getOutputText(outputModel)) assertEquals(false, block.wasExecuted) } @@ -46,7 +48,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { val outputModel = TerminalTestUtil.createOutputModel() outputModel.update(0, "some welcome text\n") - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) assertEquals(1, blocksModel.blocks.size) val block = blocksModel.activeBlock as TerminalCommandBlock @@ -57,7 +59,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { assertEquals(null, block.commandStartOffset) assertEquals(null, block.outputStartOffset) assertEquals(null, block.exitCode) - assertEquals(null, block.getCommandText(outputModel)) + assertEquals(null, block.getTypedCommandText(outputModel)) assertEquals(null, block.getOutputText(outputModel)) assertEquals(false, block.wasExecuted) } @@ -65,7 +67,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `initial block is replaced with a new one`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel() - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) outputModel.update(0, "\n\n\n") @@ -81,7 +83,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `command start offset is set correctly after prompt finish`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel() - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) outputModel.update(0, "\n\n\n") blocksModel.startNewBlock(outputModel.startOffset + 0L) @@ -100,7 +102,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `block end offset is updated on command typing`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel() - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) outputModel.update(0, "\n\n\n") blocksModel.startNewBlock(outputModel.startOffset + 0L) @@ -116,7 +118,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `output start offset is set correctly after command start`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel() - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) outputModel.update(0, "\n\n\n") blocksModel.startNewBlock(outputModel.startOffset + 0L) @@ -129,7 +131,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { assertEquals(1, blocksModel.blocks.size) val block = blocksModel.activeBlock as TerminalCommandBlock assertEquals("myCommand\n", outputModel.getTextAsString(block.commandStartOffset!!, block.outputStartOffset!!)) - assertEquals("myCommand", block.getCommandText(outputModel)) + assertEquals("myCommand", block.getTypedCommandText(outputModel)) assertEquals("someOutput\n\n", outputModel.getTextAsString(block.outputStartOffset!!, block.endOffset)) assertEquals("someOutput", block.getOutputText(outputModel)) assertEquals(true, block.wasExecuted) @@ -139,7 +141,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `aborted command block contains valid command and no output`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel() - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) outputModel.update(0, "\n\n\n") blocksModel.startNewBlock(TerminalOffset.ZERO) @@ -152,7 +154,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { assertEquals(2, blocksModel.blocks.size) val firstBlock = blocksModel.blocks[0] as TerminalCommandBlock - assertEquals("abortedCommand", firstBlock.getCommandText(outputModel)) + assertEquals("abortedCommand", firstBlock.getTypedCommandText(outputModel)) assertEquals(null, firstBlock.getOutputText(outputModel)) assertEquals(false, firstBlock.wasExecuted) } @@ -160,7 +162,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `getOutputText of command with no output is empty`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel() - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) outputModel.update(0, "\n\n\n") blocksModel.startNewBlock(TerminalOffset.ZERO) @@ -174,7 +176,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { assertEquals(2, blocksModel.blocks.size) val firstBlock = blocksModel.blocks[0] as TerminalCommandBlock - assertEquals("myCommand", firstBlock.getCommandText(outputModel)) + assertEquals("myCommand", firstBlock.getTypedCommandText(outputModel)) assertEquals("", firstBlock.getOutputText(outputModel)) assertEquals(true, firstBlock.wasExecuted) } @@ -182,7 +184,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `getOutputText of running command returns current output`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel() - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) outputModel.update(0, "\n\n\n") blocksModel.startNewBlock(TerminalOffset.ZERO) @@ -194,15 +196,15 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { assertEquals(1, blocksModel.blocks.size) val firstBlock = blocksModel.blocks[0] as TerminalCommandBlock - assertEquals("myCommand", firstBlock.getCommandText(outputModel)) + assertEquals("myCommand", firstBlock.getTypedCommandText(outputModel)) assertEquals("someOutput...", firstBlock.getOutputText(outputModel)) assertEquals(true, firstBlock.wasExecuted) } @Test - fun `getCommandText returns null if command start is trimmed`() = runBlocking(Dispatchers.EDT) { + fun `getTypedCommandText returns null if command start is trimmed`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel(30) - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) outputModel.update(0, "\n\n\n") blocksModel.startNewBlock(TerminalOffset.ZERO) @@ -217,7 +219,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { assertEquals(2, blocksModel.blocks.size) val firstBlock = blocksModel.blocks[0] as TerminalCommandBlock - assertEquals(null, firstBlock.getCommandText(outputModel)) + assertEquals(null, firstBlock.getTypedCommandText(outputModel)) assertEquals("someOutput", firstBlock.getOutputText(outputModel)) assertEquals(true, firstBlock.wasExecuted) } @@ -225,7 +227,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `getOutputText returns partial output if output start is trimmed`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel(25) - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) outputModel.update(0, "\n\n") blocksModel.startNewBlock(TerminalOffset.ZERO) @@ -240,7 +242,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { assertEquals(2, blocksModel.blocks.size) val firstBlock = blocksModel.blocks[0] as TerminalCommandBlock - assertEquals(null, firstBlock.getCommandText(outputModel)) + assertEquals(null, firstBlock.getTypedCommandText(outputModel)) assertEquals("6789-123456789", firstBlock.getOutputText(outputModel)) assertEquals(true, firstBlock.wasExecuted) } @@ -248,7 +250,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `new block is created after next prompt start`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel() - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) outputModel.update(0, "\n\n\n") blocksModel.startNewBlock(outputModel.startOffset + 0L) @@ -272,7 +274,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `initial block is left if there was some text`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel() - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) outputModel.update(0, "\n\n\n") outputModel.update(0, "welcomeText\n\n\n") @@ -294,7 +296,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `blocks are preserved after full text replace`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel() - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) // Prepare outputModel.update(0, "\n\n\n") @@ -330,7 +332,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `single block is left after full replace`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel() - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) // Prepare outputModel.update(0, "\n\n\n") @@ -362,7 +364,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `single block is left after clear`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel() - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) // Prepare outputModel.update(0, "\n\n\n") @@ -390,7 +392,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `single block is left after clear (with trimming)`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel(100) - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) // Prepare outputModel.update(0, "\n\n\n") @@ -419,7 +421,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `block positions stay the same after output start trimmed`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel(maxLength = 30) - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) outputModel.update(0, "\n\n\n") blocksModel.startNewBlock(TerminalOffset.ZERO) @@ -449,7 +451,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `block was removed after output start trimmed`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel(maxLength = 30) - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) outputModel.update(0, "\n\n\n") outputModel.update(0, "welcome12\n\n\n") @@ -480,7 +482,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `check state is dumped correctly`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel() - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) // Prepare outputModel.update(0, "\n\n\n") @@ -506,6 +508,8 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { commandStartOffset = outputModel.startOffset + 10, outputStartOffset = outputModel.startOffset + 20, endOffset = outputModel.startOffset + 31, + workingDirectory = null, + executedCommand = null, exitCode = null ) assertEquals(expectedFirstBlock, state.blocks[0]) @@ -516,6 +520,8 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { commandStartOffset = outputModel.startOffset + 46, outputStartOffset = null, endOffset = outputModel.startOffset + 47, + workingDirectory = null, + executedCommand = null, exitCode = null ) assertEquals(expectedSecondBlock, state.blocks[1]) @@ -524,7 +530,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `check state is restored correctly`() = runBlocking(Dispatchers.EDT) { val outputModel = TerminalTestUtil.createOutputModel() - val blocksModel = TerminalBlocksModelImpl(outputModel, testRootDisposable) + val blocksModel = createBlocksModel(outputModel) val firstBlock = TerminalCommandBlockImpl( id = TerminalBlockIdImpl(1), @@ -532,6 +538,8 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { commandStartOffset = outputModel.startOffset + 10, outputStartOffset = outputModel.startOffset + 20, endOffset = outputModel.startOffset + 31, + workingDirectory = null, + executedCommand = null, exitCode = null ) val secondBlock = TerminalCommandBlockImpl( @@ -540,6 +548,8 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { commandStartOffset = outputModel.startOffset + 46, outputStartOffset = null, endOffset = outputModel.startOffset + 47, + workingDirectory = null, + executedCommand = null, exitCode = null ) val state = TerminalBlocksModelState( @@ -559,7 +569,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { @Test fun `check state is restored correctly after applying dumped state`() = runBlocking(Dispatchers.EDT) { val sourceOutputModel = TerminalTestUtil.createOutputModel() - val sourceBlocksModel = TerminalBlocksModelImpl(sourceOutputModel, testRootDisposable) + val sourceBlocksModel = createBlocksModel(sourceOutputModel) // Prepare sourceOutputModel.update(0, "\n\n\n") @@ -576,7 +586,7 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { // Test val state = sourceBlocksModel.dumpState() val newOutputModel = TerminalTestUtil.createOutputModel() - val newBlocksModel = TerminalBlocksModelImpl(newOutputModel, testRootDisposable) + val newBlocksModel = createBlocksModel(newOutputModel) newBlocksModel.restoreFromState(state) assertEquals(3, state.blockIdCounter) @@ -588,6 +598,8 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { commandStartOffset = newOutputModel.startOffset + 10, outputStartOffset = newOutputModel.startOffset + 20, endOffset = newOutputModel.startOffset + 31, + workingDirectory = null, + executedCommand = null, exitCode = null ) assertEquals(expectedFirstBlock, state.blocks[0]) @@ -598,11 +610,18 @@ internal class TerminalBlocksModelTest : BasePlatformTestCase() { commandStartOffset = newOutputModel.startOffset + 46, outputStartOffset = null, endOffset = newOutputModel.startOffset + 47, + workingDirectory = null, + executedCommand = null, exitCode = null ) assertEquals(expectedSecondBlock, state.blocks[1]) } + private fun createBlocksModel(outputModel: TerminalOutputModel): TerminalBlocksModelImpl { + val sessionModel = TerminalSessionModelImpl() + return TerminalBlocksModelImpl(outputModel, sessionModel, testRootDisposable) + } + private fun MutableTerminalOutputModel.getTextAsString(startOffset: TerminalOffset, endOffset: TerminalOffset): String { return getText(startOffset, endOffset).toString() } diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/TerminalOutputModelTest.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/TerminalOutputModelTest.kt index 9e8598c03a4a..f6e5f0d1637c 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/TerminalOutputModelTest.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/TerminalOutputModelTest.kt @@ -16,8 +16,8 @@ import org.jetbrains.plugins.terminal.block.output.TerminalOutputHighlightingsSn import org.jetbrains.plugins.terminal.block.output.TextStyleAdapter import org.jetbrains.plugins.terminal.block.reworked.* import org.jetbrains.plugins.terminal.block.ui.BlockTerminalColorPalette -import org.jetbrains.plugins.terminal.session.StyleRange -import org.jetbrains.plugins.terminal.session.TerminalOutputModelState +import org.jetbrains.plugins.terminal.session.impl.StyleRange +import org.jetbrains.plugins.terminal.session.impl.TerminalOutputModelState import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/backend/BackendTerminalHyperlinkHighlighterTest.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/backend/BackendTerminalHyperlinkHighlighterTest.kt index 05d95e503322..97fe243362f2 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/backend/BackendTerminalHyperlinkHighlighterTest.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/backend/BackendTerminalHyperlinkHighlighterTest.kt @@ -24,7 +24,7 @@ import org.assertj.core.api.Assertions.assertThat import org.jetbrains.plugins.terminal.block.reworked.TerminalOffset import org.jetbrains.plugins.terminal.block.reworked.TerminalOutputModel import org.jetbrains.plugins.terminal.block.reworked.updateContent -import org.jetbrains.plugins.terminal.session.* +import org.jetbrains.plugins.terminal.session.impl.* import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/backend/ShellIntegrationTest.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/backend/ShellIntegrationTest.kt index ac3114e9cf05..9bc15bf37bd0 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/backend/ShellIntegrationTest.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/backend/ShellIntegrationTest.kt @@ -16,9 +16,9 @@ import kotlinx.coroutines.channels.SendChannel import org.assertj.core.api.Assertions.assertThat import org.jetbrains.plugins.terminal.LocalTerminalCustomizer import org.jetbrains.plugins.terminal.ShellStartupOptions -import org.jetbrains.plugins.terminal.session.* -import org.jetbrains.plugins.terminal.session.dto.toState -import org.jetbrains.plugins.terminal.session.dto.toStyleRange +import org.jetbrains.plugins.terminal.session.impl.* +import org.jetbrains.plugins.terminal.session.impl.dto.toState +import org.jetbrains.plugins.terminal.session.impl.dto.toStyleRange import org.junit.Assume import org.junit.Rule import org.junit.Test diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/backend/TerminalCursorPositionTrackerTest.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/backend/TerminalCursorPositionTrackerTest.kt index 77bb9f90713c..a4dae2be7e0d 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/backend/TerminalCursorPositionTrackerTest.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/backend/TerminalCursorPositionTrackerTest.kt @@ -7,7 +7,7 @@ import com.jediterm.terminal.model.StyleState import com.jediterm.terminal.model.TerminalTextBuffer import com.jediterm.terminal.ui.settings.DefaultSettingsProvider import org.assertj.core.api.Assertions.assertThat -import org.jetbrains.plugins.terminal.session.TerminalCursorPositionChangedEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalCursorPositionChangedEvent import org.junit.Test internal class TerminalCursorPositionTrackerTest { diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/backend/TerminalSessionTest.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/backend/TerminalSessionTest.kt index 93e7171f15a4..d9d22a1c76fa 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/backend/TerminalSessionTest.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/backend/TerminalSessionTest.kt @@ -11,7 +11,7 @@ import com.intellij.testFramework.common.timeoutRunBlocking import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.launch -import org.jetbrains.plugins.terminal.session.* +import org.jetbrains.plugins.terminal.session.impl.* import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/frontend/completion/TerminalCompletionFixture.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/frontend/completion/TerminalCompletionFixture.kt index 34072ae0412a..448b09aba8fb 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/frontend/completion/TerminalCompletionFixture.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/frontend/completion/TerminalCompletionFixture.kt @@ -52,8 +52,8 @@ class TerminalCompletionFixture(val project: Project, val testRootDisposable: Di terminalScope.cancel() } view = TerminalViewImpl(project, JBTerminalSystemSettingsProvider(), null, terminalScope) - val shellIntegration = TerminalShellIntegrationImpl(outputModel, terminalScope.childScope("TerminalShellIntegration")) - view.shellIntegrationFuture.complete(shellIntegration) + val shellIntegration = TerminalShellIntegrationImpl(outputModel, view.sessionModel, terminalScope.childScope("TerminalShellIntegration")) + view.shellIntegrationDeferred.complete(shellIntegration) shellIntegration.onPromptFinished(TerminalOffset.ZERO) // To make TerminalOutputStatus = TypingCommand assertEquals(TerminalOutputStatus.TypingCommand, shellIntegration.outputStatus.value) diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TerminalSessionTestUtil.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TerminalSessionTestUtil.kt index f369c371efb8..27efbbdb7964 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TerminalSessionTestUtil.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TerminalSessionTestUtil.kt @@ -23,8 +23,8 @@ import org.jetbrains.plugins.terminal.TerminalEngine import org.jetbrains.plugins.terminal.TerminalOptionsProvider import org.jetbrains.plugins.terminal.runner.LocalShellIntegrationInjector import org.jetbrains.plugins.terminal.runner.LocalTerminalStartCommandBuilder -import org.jetbrains.plugins.terminal.session.TerminalOutputEvent -import org.jetbrains.plugins.terminal.session.TerminalSession +import org.jetbrains.plugins.terminal.session.impl.TerminalOutputEvent +import org.jetbrains.plugins.terminal.session.impl.TerminalSession import org.junit.Assume import java.nio.file.Files import java.nio.file.Path @@ -47,7 +47,7 @@ internal object TerminalSessionTestUtil { * * @param isLowLevelSession whether the same session should be used as in the production or its low-level JediTerm implementation. * Low-level session outputs the events in their natural order, - * while production one replaces some initial events with [org.jetbrains.plugins.terminal.session.TerminalInitialStateEvent]. + * while production one replaces some initial events with [org.jetbrains.plugins.terminal.session.impl.TerminalInitialStateEvent]. * Prefer low-level session in exceptional cases only when you need to test the exact sequences of events. * Use production session (specify `false`) in all other cases. */ diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TerminalTestUtil.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TerminalTestUtil.kt index 0bb060f50114..b16477da042e 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TerminalTestUtil.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TerminalTestUtil.kt @@ -12,8 +12,8 @@ import org.jetbrains.plugins.terminal.TerminalEngine import org.jetbrains.plugins.terminal.TerminalOptionsProvider import org.jetbrains.plugins.terminal.block.reworked.MutableTerminalOutputModel import org.jetbrains.plugins.terminal.block.reworked.MutableTerminalOutputModelImpl -import org.jetbrains.plugins.terminal.session.StyleRange -import org.jetbrains.plugins.terminal.session.TerminalOutputModelState +import org.jetbrains.plugins.terminal.session.impl.StyleRange +import org.jetbrains.plugins.terminal.session.impl.TerminalOutputModelState @ApiStatus.Internal object TerminalTestUtil { diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TextWithReplacement.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TextWithReplacement.kt index 9ff1400a0699..add4878eec81 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TextWithReplacement.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TextWithReplacement.kt @@ -8,7 +8,7 @@ import com.intellij.terminal.tests.reworked.util.TextWithReplacement.Companion.S import com.jediterm.terminal.TerminalColor import com.jediterm.terminal.TextStyle import org.assertj.core.api.Assertions.assertThat -import org.jetbrains.plugins.terminal.session.StyleRange +import org.jetbrains.plugins.terminal.session.impl.StyleRange internal fun parseTextWithReplacement(textWithReplacement: String): TextWithReplacement { diff --git a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TextWithReplacementTest.kt b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TextWithReplacementTest.kt index a3d082fd1597..20ab67fcf559 100644 --- a/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TextWithReplacementTest.kt +++ b/plugins/terminal/tests/src/com/intellij/terminal/tests/reworked/util/TextWithReplacementTest.kt @@ -7,7 +7,7 @@ import com.intellij.terminal.tests.reworked.util.TextWithReplacement.Companion.S import com.intellij.terminal.tests.reworked.util.TextWithReplacement.Companion.STYLE4 import com.jediterm.terminal.TextStyle import org.assertj.core.api.Assertions.assertThat -import org.jetbrains.plugins.terminal.session.StyleRange +import org.jetbrains.plugins.terminal.session.impl.StyleRange import org.junit.Test class TextWithReplacementTest { diff --git a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/resources/promo/backgrounds/promotion_bg.svg b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/resources/promo/backgrounds/promotion_bg.svg deleted file mode 100644 index ba211d578e33..000000000000 --- a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/resources/promo/backgrounds/promotion_bg.svg +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyCommunityToUnifiedPromoService.kt b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyCommunityToUnifiedPromoService.kt index 82c0bc0846d9..194d3b9bafa9 100644 --- a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyCommunityToUnifiedPromoService.kt +++ b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyCommunityToUnifiedPromoService.kt @@ -9,6 +9,7 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.updateSettings.UpdateStrategyCustomization import com.intellij.openapi.updateSettings.impl.* import com.intellij.openapi.util.registry.Registry +import com.intellij.pycharm.community.ide.impl.promo.WelcomeToUnifiedWelcomeScreenBanner import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.sync.Mutex @@ -48,6 +49,8 @@ internal class PyCommunityToUnifiedPromoService(val serviceScope: CoroutineScope val propertiesComponent = PropertiesComponent.getInstance() propertiesComponent.unsetValue(PROMO_UPDATE_DECLINED) propertiesComponent.unsetValue(PROMO_REMIND_ME_LATER_LAST_TIME_CLICKED) + propertiesComponent.unsetValue(WelcomeToUnifiedWelcomeScreenBanner.BANNER_CLOSED_PROPERTY) + Registry.get(REG_PROMO_INTERVAL_SECONDS).resetToDefault() } diff --git a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyCommunityToUnifiedWelcomeScreenBanner.kt b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyCommunityToUnifiedWelcomeScreenBanner.kt index 781bc1a6f0f1..5966df278b54 100644 --- a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyCommunityToUnifiedWelcomeScreenBanner.kt +++ b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyCommunityToUnifiedWelcomeScreenBanner.kt @@ -4,18 +4,16 @@ package com.intellij.pycharm.community.ide.impl.promotion.communityToUnified import com.intellij.ide.ui.laf.darcula.ui.DarculaButtonUI import com.intellij.openapi.application.EDT import com.intellij.openapi.components.service -import com.intellij.openapi.wm.BannerStartPagePromoter import com.intellij.openapi.wm.StartPagePromoter +import com.intellij.pycharm.community.ide.impl.icons.PycharmCommunityIdeImplIcons +import com.intellij.pycharm.community.ide.impl.promo.PyCommunityToUnifiedWelcomeScreenBannerBase import com.intellij.pycharm.community.ide.impl.promotion.communityToUnified.statistics.PyCommunityUnifiedPromoFusCollector -import com.intellij.pycharm.community.ide.impl.promotion.icons.PycharmCommunityIdeImplPromotionIcons -import com.intellij.ui.components.panels.BackgroundRoundedPanel import com.intellij.ui.components.panels.Wrapper import com.intellij.ui.dsl.builder.EmptySpacingConfiguration import com.intellij.ui.dsl.builder.TopGap import com.intellij.ui.dsl.builder.panel import com.intellij.ui.dsl.gridLayout.UnscaledGaps import com.intellij.ui.dsl.gridLayout.UnscaledGapsY -import com.intellij.util.IconUtil import com.intellij.util.PlatformUtils import com.intellij.util.ui.JBFont import com.intellij.util.ui.JBUI @@ -24,21 +22,16 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.Nls -import java.awt.Graphics -import java.awt.Graphics2D -import java.awt.geom.RoundRectangle2D -import javax.swing.BoxLayout import javax.swing.Icon import javax.swing.JComponent import javax.swing.JPanel -import kotlin.math.min @ApiStatus.Internal -internal class PyCommunityToUnifiedWelcomeScreenBanner : BannerStartPagePromoter() { +internal class PyCommunityToUnifiedWelcomeScreenBanner : PyCommunityToUnifiedWelcomeScreenBannerBase() { override val headerLabel: @Nls String = "" override val actionLabel: @Nls String = "" override val description: @Nls String = "" - override val promoImage: Icon = PycharmCommunityIdeImplPromotionIcons.Backgrounds.Promotion_bg + override val promoImage: Icon = PycharmCommunityIdeImplIcons.Backgrounds.Promotion_bg private val mainBannerPanel = createMainPanel() @@ -58,19 +51,19 @@ internal class PyCommunityToUnifiedWelcomeScreenBanner : BannerStartPagePromoter } } } - return container + return wrapIntoSvgBackgroundPanel(container) } - override fun runAction() {} - override fun canCreatePromo(isEmptyState: Boolean): Boolean { return PlatformUtils.isPyCharmCommunity() } + override fun getMainPanel(): JPanel = container + override fun getPriorityLevel(): Int = StartPagePromoter.PRIORITY_LEVEL_HIGH - private fun createMainPanel(): JPanel { - val panel = panel { + private fun createMainPanel(): JPanel = + panel { customizeSpacingConfiguration(EmptySpacingConfiguration()) { row { label(PyPromoSharedComponents.headerTitle) @@ -101,46 +94,4 @@ internal class PyCommunityToUnifiedWelcomeScreenBanner : BannerStartPagePromoter isOpaque = false border = JBUI.Borders.empty(0, 12, 16, 0) } - return wrapIntoSvgBackgroundPanel(panel, JBUI.scale(16), promoImage) - } - - private fun wrapIntoSvgBackgroundPanel(panel: JPanel, arc: Int, bgImage: Icon): JPanel { - val svgPanel = SvgBackgroundRightAnchoredPanel(arc, bgImage).apply { - layout = BoxLayout(this, BoxLayout.X_AXIS) - } - svgPanel.add(panel) - return svgPanel - } - - private class SvgBackgroundRightAnchoredPanel( - private val arc: Int, - private val bgImage: Icon, - ) : BackgroundRoundedPanel(arc) { - - override fun paintComponent(g: Graphics) { - super.paintComponent(g) - if (width <= 0 || height <= 0) return - - val iw = bgImage.iconWidth - val ih = bgImage.iconHeight - if (iw <= 0 || ih <= 0) return - - val g2 = g.create() as Graphics2D - try { - g2.clip(RoundRectangle2D.Float(0f, 0f, width.toFloat(), height.toFloat(), arc.toFloat(), arc.toFloat())) - val sx = width.toFloat() / iw - val sy = height.toFloat() / ih - val scale = min(sx, sy) - - val scaled = IconUtil.scale(bgImage, this, scale) - // Right-anchored (no padding) - val x = width - scaled.iconWidth - val y = 0 - scaled.paintIcon(this, g2, x, y) - } - finally { - g2.dispose() - } - } - } } \ No newline at end of file diff --git a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyPromoSharedComponents.kt b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyPromoSharedComponents.kt index 156c2b63f08a..33406bf2a405 100644 --- a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyPromoSharedComponents.kt +++ b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/communityToUnified/PyPromoSharedComponents.kt @@ -9,7 +9,7 @@ import java.awt.Color internal object PyPromoSharedComponents { - val popUpImg = PycharmCommunityIdeImplPromotionIcons.Images.Popup_img + val popUpImg = PycharmCommunityIdeImplPromotionIcons.Popup_img val infoFontColor = JBColor(Color.decode("#6C707E"), Color.decode("#A8ADBD")) // URLs used by promo-related messages diff --git a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/icons/PycharmCommunityIdeImplPromotionIcons.java b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/icons/PycharmCommunityIdeImplPromotionIcons.java index a4ad5d26b7fd..370c819993a6 100644 --- a/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/icons/PycharmCommunityIdeImplPromotionIcons.java +++ b/python/ide/impl/intellij.pycharm.community.ide.impl.promotion/src/com/intellij/pycharm/community/ide/impl/promotion/icons/PycharmCommunityIdeImplPromotionIcons.java @@ -15,12 +15,5 @@ public final class PycharmCommunityIdeImplPromotionIcons { private static @NotNull Icon load(@NotNull String path, int cacheKey, int flags) { return IconManager.getInstance().loadRasterizedIcon(path, PycharmCommunityIdeImplPromotionIcons.class.getClassLoader(), cacheKey, flags); } - - public static final class Backgrounds { - /** 360x128 */ public static final @NotNull Icon Promotion_bg = load("promo/backgrounds/promotion_bg.svg", -882473096, 0); - } - - public static final class Images { - /** 384x242 */ public static final @NotNull Icon Popup_img = load("promo/images/popup_img.svg", 986597622, 0); - } + /** 384x242 */ public static final @NotNull Icon Popup_img = load("promo/images/popup_img.svg", 986597622, 0); } diff --git a/python/ide/impl/resources/backgrounds/promotion_bg.svg b/python/ide/impl/resources/backgrounds/promotion_bg.svg new file mode 100644 index 000000000000..6795402cbc55 --- /dev/null +++ b/python/ide/impl/resources/backgrounds/promotion_bg.svg @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/ide/impl/resources/icons/close.svg b/python/ide/impl/resources/icons/close.svg new file mode 100644 index 000000000000..4000ed8344b0 --- /dev/null +++ b/python/ide/impl/resources/icons/close.svg @@ -0,0 +1,4 @@ + + + + diff --git a/python/ide/impl/resources/icons/close_dark.svg b/python/ide/impl/resources/icons/close_dark.svg new file mode 100644 index 000000000000..1ded8acf432f --- /dev/null +++ b/python/ide/impl/resources/icons/close_dark.svg @@ -0,0 +1,4 @@ + + + + diff --git a/python/ide/impl/resources/intellij.pycharm.community.ide.impl.xml b/python/ide/impl/resources/intellij.pycharm.community.ide.impl.xml index a21af3c53a65..92b2a4495d21 100644 --- a/python/ide/impl/resources/intellij.pycharm.community.ide.impl.xml +++ b/python/ide/impl/resources/intellij.pycharm.community.ide.impl.xml @@ -93,6 +93,9 @@ instance="com.intellij.pycharm.community.ide.impl.settings.MemoryUsageConfigurable" groupId="root"/> + + @@ -141,6 +144,8 @@ + @@ -165,6 +170,8 @@ + diff --git a/python/ide/impl/resources/messages/PyCharmCommunityCustomizationBundle.properties b/python/ide/impl/resources/messages/PyCharmCommunityCustomizationBundle.properties index 6cc92ba28ac3..378a29e9f4a2 100644 --- a/python/ide/impl/resources/messages/PyCharmCommunityCustomizationBundle.properties +++ b/python/ide/impl/resources/messages/PyCharmCommunityCustomizationBundle.properties @@ -77,11 +77,14 @@ promotion.update.to.unified.banner.header.title=Update to the unified PyCharm promotion.update.to.unified.info.text=Starting with version 2025.3, all updates will be released exclusively for the unified PyCharm.
    Update the IDE to keep receiving new features \u2013 all core functionalities will remain free. promotion.update.to.unified.update.now.button.label=Update Now promotion.update.to.unified.learn.more.hyper.link=Learn More -promotion.update.to.unified.closed.banner.go.to.settings.info=You can find and install the new update in the Settings section promotion.update.to.unified.skip.button.label=Skip action.ShowPyCommunityToUnifiedTooltipAction.text=Community To Unified Tooltip action.ShowPyCommunityToUnifiedTooltipAction.description=For testing purposes action.ShowPyCommunityToUnifiedPromoDialogAction.text=Community To Unified Modal Promo action.ShowPyCommunityToUnifiedPromoDialogAction.description=For testing purposes action.ResetCommunityToUnifiedPromoStateAction.text=Reset Community To Unified Promo State -action.ResetCommunityToUnifiedPromoStateAction.description=For testing purposes \ No newline at end of file +action.ResetCommunityToUnifiedPromoStateAction.description=For testing purposes +promotion.welcome.to.unified.main.text=Everything is in one place! Switch between core and advanced
    features within the same installation. +promotion.welcome.to.unified.title=Welcome to unified PyCharm! +promotion.welcome.to.unified.close=Close +promotion.welcome.to.unified.close.banner=Close banner \ No newline at end of file diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/icons/PycharmCommunityIdeImplIcons.java b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/icons/PycharmCommunityIdeImplIcons.java new file mode 100644 index 000000000000..08864ecbaf8e --- /dev/null +++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/icons/PycharmCommunityIdeImplIcons.java @@ -0,0 +1,26 @@ +// 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.pycharm.community.ide.impl.icons; + +import com.intellij.ui.IconManager; +import org.jetbrains.annotations.NotNull; + +import javax.swing.*; + +/** + * NOTE THIS FILE IS AUTO-GENERATED + * DO NOT EDIT IT BY HAND, run "Generate icon classes" configuration instead + */ +@org.jetbrains.annotations.ApiStatus.Internal +public final class PycharmCommunityIdeImplIcons { + private static @NotNull Icon load(@NotNull String path, int cacheKey, int flags) { + return IconManager.getInstance().loadRasterizedIcon(path, PycharmCommunityIdeImplIcons.class.getClassLoader(), cacheKey, flags); + } + + public static final class Backgrounds { + /** 770x128 */ public static final @NotNull Icon Promotion_bg = load("backgrounds/promotion_bg.svg", -1173415960, 0); + } + + public static final class Icons { + /** 16x16 */ public static final @NotNull Icon Close = load("icons/close.svg", -540869824, 2); + } +} diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/promo/PyCommunityToUnifiedWelcomeScreenBannerBase.kt b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/promo/PyCommunityToUnifiedWelcomeScreenBannerBase.kt new file mode 100644 index 000000000000..47ee28c8e8cf --- /dev/null +++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/promo/PyCommunityToUnifiedWelcomeScreenBannerBase.kt @@ -0,0 +1,93 @@ +// 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.pycharm.community.ide.impl.promo + +import com.intellij.openapi.Disposable +import com.intellij.openapi.observable.util.whenDisposed +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.wm.BannerStartPagePromoter +import com.intellij.openapi.wm.impl.IdeBackgroundUtil +import com.intellij.util.SVGLoader +import com.intellij.util.ui.GraphicsUtil +import com.intellij.util.ui.JBInsets +import com.intellij.util.ui.JBSwingUtilities +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.Nls +import java.awt.* +import javax.swing.JComponent +import javax.swing.JPanel + +@ApiStatus.Internal +abstract class PyCommunityToUnifiedWelcomeScreenBannerBase : BannerStartPagePromoter() { + override val headerLabel: @Nls String = "" + override val actionLabel: @Nls String = "" + override val description: @Nls String = "" + + abstract fun getMainPanel(): JPanel + + override fun getPromotion(isEmptyState: Boolean): JComponent { + val mainPanel = getMainPanel() + return wrapIntoSvgBackgroundPanel(mainPanel) + } + + override fun runAction() {} + + fun wrapIntoSvgBackgroundPanel(panel: JPanel): JPanel { + val disposable = Disposer.newDisposable("BackgroundPanel") + val url = WelcomeToUnifiedWelcomeScreenBanner::class.java.getResource("/backgrounds/promotion_bg.svg") ?: return panel + val backgroundImage = SVGLoader.load(url, 1f) + val backgroundPanel = BackgroundPanel(BorderLayout(), backgroundImage, disposable) + backgroundPanel.add(panel, BorderLayout.CENTER) + backgroundPanel.isOpaque = false + return backgroundPanel + } + + private class BackgroundPanel(layout: LayoutManager, private val image: Image, parentDisposable: Disposable) : JPanel(layout) { + + private var disposable: Disposable? = null + + init { + parentDisposable.whenDisposed { clearResources() } + installBackground() + } + + override fun paintComponent(g: Graphics) { + super.paintComponent(g) + val g2 = g.create() as Graphics2D + try { + GraphicsUtil.setupAAPainting(g2) + g2.color = background + g2.fillRoundRect(0, 0, width, height, 16, 16) + } + finally { + g2.dispose() + } + } + + override fun getComponentGraphics(g: Graphics?): Graphics? { + return JBSwingUtilities.runGlobalCGTransform(this, super.getComponentGraphics(g)) + } + + override fun updateUI() { + super.updateUI() + @Suppress("SENSELESS_COMPARISON") + if (image != null) { + installBackground() + } + } + + private fun installBackground() { + clearResources() + disposable = Disposer.newDisposable("BackgroundPanel") + IdeBackgroundUtil.createTemporaryBackgroundTransform(this, image, + IdeBackgroundUtil.Fill.SCALE, + IdeBackgroundUtil.Anchor.MIDDLE_RIGHT, + 1f, JBInsets.emptyInsets(), + disposable) + } + + private fun clearResources() { + disposable?.let { Disposer.dispose(it) } + disposable = null + } + } +} \ No newline at end of file diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/promo/WelcomeToUnifiedWelcomeScreenBanner.kt b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/promo/WelcomeToUnifiedWelcomeScreenBanner.kt new file mode 100644 index 000000000000..457546a62987 --- /dev/null +++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/promo/WelcomeToUnifiedWelcomeScreenBanner.kt @@ -0,0 +1,112 @@ +// 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.pycharm.community.ide.impl.promo + + +import com.intellij.ide.AppLifecycleListener +import com.intellij.ide.util.PropertiesComponent +import com.intellij.openapi.actionSystem.ActionUpdateThread +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.application.ApplicationInfo +import com.intellij.openapi.project.DumbAwareAction +import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle +import com.intellij.pycharm.community.ide.impl.icons.PycharmCommunityIdeImplIcons +import com.intellij.ui.JBColor +import com.intellij.ui.components.panels.Wrapper +import com.intellij.ui.dsl.builder.* +import com.intellij.ui.dsl.gridLayout.UnscaledGaps +import com.intellij.ui.dsl.gridLayout.UnscaledGapsY +import com.intellij.util.IconUtil +import com.intellij.util.PlatformUtils +import com.intellij.util.ui.JBFont +import com.intellij.util.ui.JBUI +import java.awt.Color +import java.awt.Dimension +import javax.swing.Icon +import javax.swing.JPanel + +private const val PREV_BUILD_WAS_PC_PROP = "pycharm.previous.version.was.community" + +class WelcomeToUnifiedWelcomeScreenBanner : PyCommunityToUnifiedWelcomeScreenBannerBase() { + + companion object { + const val BANNER_CLOSED_PROPERTY: String = "WELCOME_TO_UNIFIED_BANNER_CLOSED" + } + + private fun isBannerClosed(): Boolean = PropertiesComponent.getInstance().getBoolean(BANNER_CLOSED_PROPERTY) + private fun onBannerClosed() = PropertiesComponent.getInstance().setValue(BANNER_CLOSED_PROPERTY, true) + + override val promoImage: Icon = PycharmCommunityIdeImplIcons.Backgrounds.Promotion_bg + + private val infoFontColor = JBColor(Color.decode("#6C707E"), Color.decode("#A8ADBD")) + private val closeIcon = IconUtil.scale(PycharmCommunityIdeImplIcons.Icons.Close, null, 1.1f) + + private val myCloseAction = + object : DumbAwareAction(PyCharmCommunityCustomizationBundle.message("promotion.welcome.to.unified.close"), + PyCharmCommunityCustomizationBundle.message("promotion.welcome.to.unified.close.banner"), + closeIcon) { + override fun getActionUpdateThread() = ActionUpdateThread.EDT + override fun actionPerformed(e: AnActionEvent) { + onBannerClosed() + container.isVisible = false + container.repaint() + } + } + + val container: Wrapper = Wrapper(createMainPanel()) + + override fun canCreatePromo(isEmptyState: Boolean): Boolean { + val isUnified = ApplicationInfo.getInstance().build.productCode.equals("PY", ignoreCase = true) + return isUnified && !isBannerClosed() && isUpgradeFromCommunityToUnified() + } + + private fun createMainPanel(): JPanel { + val panel = panel { + customizeSpacingConfiguration(EmptySpacingConfiguration()) { + row { + label(PyCharmCommunityCustomizationBundle.message("promotion.welcome.to.unified.title")) + .applyToComponent { + font = JBFont.h3().asBold() + } + actionButton(myCloseAction) + .applyToComponent { + setMinimumButtonSize { Dimension(closeIcon.iconWidth, closeIcon.iconHeight) } + } + .align(AlignX.RIGHT) + .align(AlignY.TOP) + .customize(UnscaledGaps(right = 8)) + }.customize(customRowGaps = UnscaledGapsY(top = 12)) + row { + text(PyCharmCommunityCustomizationBundle.message("promotion.welcome.to.unified.main.text")) + .applyToComponent { + foreground = infoFontColor + } + }.customize(customRowGaps = UnscaledGapsY(top = 4, bottom = 8)) + } + row { + browserLink(PyCharmCommunityCustomizationBundle.message("promotion.update.to.unified.learn.more.hyper.link"), + "https://blog.jetbrains.com/pycharm/2025/04/unified-pycharm/") + }.topGap(TopGap.NONE) + }.apply { + isOpaque = false + border = JBUI.Borders.empty(0, 12, 16, 0) + } + return panel + } + + override fun getMainPanel(): JPanel = container + + private fun isUpgradeFromCommunityToUnified(): Boolean { + val currentCode = ApplicationInfo.getInstance().build.productCode + + val prevWasCommunity = PropertiesComponent.getInstance().getBoolean(PREV_BUILD_WAS_PC_PROP, false) + return prevWasCommunity && currentCode.equals("PY", ignoreCase = true) + } +} + +class CommunityToUnifiedListener : AppLifecycleListener { + override fun appStarted() { + if (PlatformUtils.isPyCharmCommunity()) { + PropertiesComponent.getInstance().setValue(PREV_BUILD_WAS_PC_PROP, true) + } + } +} \ No newline at end of file diff --git a/python/ide/implTraining/intellij.pycharm.community.ide.implTraining/resources/img/pycharm-onboarding-gradient-background-dark.svg b/python/ide/implTraining/intellij.pycharm.community.ide.implTraining/resources/img/pycharm-onboarding-gradient-background-dark.svg deleted file mode 100644 index e6a0758c24a3..000000000000 --- a/python/ide/implTraining/intellij.pycharm.community.ide.implTraining/resources/img/pycharm-onboarding-gradient-background-dark.svg +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/ide/implTraining/intellij.pycharm.community.ide.implTraining/resources/img/pycharm-onboarding-gradient-background-light.svg b/python/ide/implTraining/intellij.pycharm.community.ide.implTraining/resources/img/pycharm-onboarding-gradient-background-light.svg deleted file mode 100644 index 894275f4b10f..000000000000 --- a/python/ide/implTraining/intellij.pycharm.community.ide.implTraining/resources/img/pycharm-onboarding-gradient-background-light.svg +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/ide/implTraining/intellij.pycharm.community.ide.implTraining/resources/img/pycharm-onboarding-gradient-background.svg b/python/ide/implTraining/intellij.pycharm.community.ide.implTraining/resources/img/pycharm-onboarding-gradient-background.svg new file mode 100644 index 000000000000..b44f39ae9be3 --- /dev/null +++ b/python/ide/implTraining/intellij.pycharm.community.ide.implTraining/resources/img/pycharm-onboarding-gradient-background.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/ide/implTraining/intellij.pycharm.community.ide.implTraining/src/com/intellij/pycharm/community/ide/implTraining/PyCharmDialogBackgroundImageProvider.kt b/python/ide/implTraining/intellij.pycharm.community.ide.implTraining/src/com/intellij/pycharm/community/ide/implTraining/PyCharmDialogBackgroundImageProvider.kt index b8c6ae258245..88723d49d071 100644 --- a/python/ide/implTraining/intellij.pycharm.community.ide.implTraining/src/com/intellij/pycharm/community/ide/implTraining/PyCharmDialogBackgroundImageProvider.kt +++ b/python/ide/implTraining/intellij.pycharm.community.ide.implTraining/src/com/intellij/pycharm/community/ide/implTraining/PyCharmDialogBackgroundImageProvider.kt @@ -5,6 +5,5 @@ import com.intellij.openapi.wm.impl.DialogBackgroundImageProviderBase import java.net.URL internal class PyCharmDialogBackgroundImageProvider : DialogBackgroundImageProviderBase() { - override fun getImageUrl(isDark: Boolean): URL? = javaClass.getResource(if (isDark) "/img/pycharm-onboarding-gradient-background-dark.svg" - else "/img/pycharm-onboarding-gradient-background-light.svg") + override fun getImageUrl(isDark: Boolean): URL? = javaClass.getResource("/img/pycharm-onboarding-gradient-background.svg") } \ No newline at end of file diff --git a/python/pluginResources/intellij.python.community.impl.xml b/python/pluginResources/intellij.python.community.impl.xml index b5ad92b8701f..b843ff1c972d 100644 --- a/python/pluginResources/intellij.python.community.impl.xml +++ b/python/pluginResources/intellij.python.community.impl.xml @@ -83,6 +83,8 @@ + +
    @@ -192,6 +194,7 @@ overrides="true"/> + diff --git a/python/src/com/jetbrains/python/packaging/toolwindow/details/PyPackagingJcefHtmlPanel.kt b/python/src/com/jetbrains/python/packaging/toolwindow/details/PyPackagingJcefHtmlPanel.kt index 8bf2be1ff926..1839e0bfe745 100644 --- a/python/src/com/jetbrains/python/packaging/toolwindow/details/PyPackagingJcefHtmlPanel.kt +++ b/python/src/com/jetbrains/python/packaging/toolwindow/details/PyPackagingJcefHtmlPanel.kt @@ -19,11 +19,10 @@ class PyPackagingJcefHtmlPanel(project: Project) : JCEFHtmlPanel(uniqueUrl) { private val cssStyleCodeToInject: String get() { val styleKey = when (val laf = LafManager.getInstance().getCurrentUIThemeLookAndFeel()) { - is UIThemeLookAndFeelInfo -> when (laf.id) { - "ExperimentalDark" -> "python_packaging_toolwindow_dark.css" - "ExperimentalLight" -> "python_packaging_toolwindow_light.css" - "JetBrainsHighContrastTheme" -> "python_packaging_toolwindow_high_contrast.css" - else -> "python_packaging_toolwindow_default.css" + is UIThemeLookAndFeelInfo -> when { + laf.id == "JetBrainsHighContrastTheme" -> "python_packaging_toolwindow_high_contrast.css" + laf.isDark -> "python_packaging_toolwindow_dark.css" + else -> "python_packaging_toolwindow_light.css" } else -> "python_packaging_toolwindow_default.css" } diff --git a/python/src/com/jetbrains/python/run/PythonCommandLineState.java b/python/src/com/jetbrains/python/run/PythonCommandLineState.java index c9cf0f2246a4..f448dcef3b9a 100644 --- a/python/src/com/jetbrains/python/run/PythonCommandLineState.java +++ b/python/src/com/jetbrains/python/run/PythonCommandLineState.java @@ -67,8 +67,10 @@ import com.jetbrains.python.library.PythonLibraryType; import com.jetbrains.python.packaging.PyExecutionException; import com.jetbrains.python.remote.PyRemotePathMapper; import com.jetbrains.python.remote.PyRemoteSdkAdditionalData; +import com.jetbrains.python.run.features.PyRunToolIds; import com.jetbrains.python.run.features.PyRunToolParameters; import com.jetbrains.python.run.features.PyRunToolProvider; +import com.jetbrains.python.run.features.PyRunToolUsageCollector; import com.jetbrains.python.run.target.HelpersAwareTargetEnvironmentRequest; import com.jetbrains.python.run.target.PySdkTargetPaths; import com.jetbrains.python.run.target.PythonCommandLineTargetEnvironmentProvider; @@ -93,6 +95,7 @@ import java.util.function.Function; import static com.intellij.execution.util.EnvFilesUtilKt.configureEnvsFromFiles; import static com.jetbrains.python.run.PythonScriptCommandLineState.getExpandedWorkingDir; import static com.jetbrains.python.run.features.PyRunToolExtKt.useRunTool; +import static com.jetbrains.python.run.features.PyRunToolProviderKt.getEnableRunTool; /** * Since this state is async, any method could be called on any thread @@ -350,10 +353,11 @@ public abstract class PythonCommandLineState extends CommandLineState { .toList(); PyRunToolParameters runToolParameters = null; - if (sdk != null) { + if (sdk != null && getEnableRunTool()) { PyRunToolProvider runToolProvider = PyRunToolProvider.forSdk(sdk); if (runToolProvider != null && useRunTool(myConfig, sdk)) { runToolParameters = runToolProvider.getRunToolParameters(); + PyRunToolUsageCollector.logRun(myConfig.getProject(), PyRunToolIds.idOf(runToolProvider)); } } diff --git a/python/src/com/jetbrains/python/run/configuration/AbstractPythonConfigurationFragmentedEditor.kt b/python/src/com/jetbrains/python/run/configuration/AbstractPythonConfigurationFragmentedEditor.kt index 75a266087659..57744e494a0f 100644 --- a/python/src/com/jetbrains/python/run/configuration/AbstractPythonConfigurationFragmentedEditor.kt +++ b/python/src/com/jetbrains/python/run/configuration/AbstractPythonConfigurationFragmentedEditor.kt @@ -12,6 +12,7 @@ import com.jetbrains.python.run.AbstractPythonRunConfiguration import com.jetbrains.python.run.PyCommonFragmentsBuilder import com.jetbrains.python.run.PythonRunConfigurationExtensionsManager import com.jetbrains.python.run.features.PyRunToolProvider +import com.jetbrains.python.run.features.enableRunTool import com.jetbrains.python.run.features.useRunTool import java.awt.event.ComponentAdapter import java.awt.event.ComponentEvent @@ -31,9 +32,9 @@ abstract class AbstractPythonConfigurationFragmentedEditor - createRunToolTag(sdk)?.let { fragments.add(it) } - } + runConfiguration.sdk?.takeIf { enableRunTool } + ?.let { createRunToolTag(it) } + ?.let { fragments.add(it) } customizeFragments(fragments) fragments.add(PyEditorExtensionFragment()) diff --git a/python/src/com/jetbrains/python/run/features/PyRunToolProvider.kt b/python/src/com/jetbrains/python/run/features/PyRunToolProvider.kt index b990d0c81b37..06aeb31b6980 100644 --- a/python/src/com/jetbrains/python/run/features/PyRunToolProvider.kt +++ b/python/src/com/jetbrains/python/run/features/PyRunToolProvider.kt @@ -3,26 +3,39 @@ package com.jetbrains.python.run.features import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.util.registry.Registry import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.Nls import org.jetbrains.annotations.NonNls +/** + * Feature flag for Python "Run with …" tool integration. + */ +internal val enableRunTool: Boolean get() = Registry.`is`("run.with.py.tool") + @JvmInline @ApiStatus.Internal value class PyRunToolId(@param:NonNls val value: String) +internal object PyRunToolIds { + @JvmStatic + fun idOf(provider: PyRunToolProvider): String = provider.runToolData.id.value +} + /** * Metadata describing a concrete Python "Run with …" tool option. * * - id: a unique identifier of the tool. * - name: user‑visible action/configuration name (localized). * - group: user‑visible group name under which the action is shown in UI (localized). + * - idForStatistics: identifier for FUS statistics (should not change to avoid breaking statistics). */ @ApiStatus.Internal data class PyRunToolData( @param:NonNls val id: PyRunToolId, @param:Nls val name: String, - @param:Nls val group: String + @param:Nls val group: String, + @param:NonNls val idForStatistics: String = id.value ) /** diff --git a/python/src/com/jetbrains/python/run/features/PyRunToolUsageCollector.kt b/python/src/com/jetbrains/python/run/features/PyRunToolUsageCollector.kt new file mode 100644 index 000000000000..db384ad3810d --- /dev/null +++ b/python/src/com/jetbrains/python/run/features/PyRunToolUsageCollector.kt @@ -0,0 +1,49 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.jetbrains.python.run.features + +import com.intellij.internal.statistic.eventLog.EventLogGroup +import com.intellij.internal.statistic.eventLog.events.EventFields +import com.intellij.internal.statistic.eventLog.events.EventId1 +import com.intellij.internal.statistic.eventLog.validator.ValidationResultType +import com.intellij.internal.statistic.eventLog.validator.rules.EventContext +import com.intellij.internal.statistic.eventLog.validator.rules.impl.CustomValidationRule +import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector +import com.intellij.internal.statistic.utils.getPluginInfo +import com.intellij.openapi.project.Project + +/** + * FUS collector for "Run with …" Python tools exposed via [PyRunToolProvider]. + * Logs how many runs are started via a run tool and the tool id. + */ +internal object PyRunToolUsageCollector : CounterUsagesCollector() { + private val GROUP = EventLogGroup("python.run.tool", 1, "FUS", "Tracks Python run configurations executed with specific run tools (e.g., uv run)") + + private val TOOL_ID = EventFields.StringValidatedByCustomRule("run_tool_id", PyRunToolIdValidator::class.java) + + private val RUN_USED: EventId1 = GROUP.registerEvent("run.used", TOOL_ID, "Execution of Python run configurations with specific run tool") + + @JvmStatic + fun logRun(project: Project, runToolId: String) { + RUN_USED.log(project, runToolId) + } + + override fun getGroup(): EventLogGroup = GROUP +} + +/** + * Custom validator for PyRunTool IDs in FUS statistics. + * Validates that the ID corresponds to a known PyRunToolProvider. + */ +internal class PyRunToolIdValidator : CustomValidationRule() { + override fun getRuleId(): String = "python_run_tool" + + override fun doValidate(data: String, context: EventContext): ValidationResultType { + val validIds = PyRunToolProvider.EP.extensionList + .asSequence() + .filter { getPluginInfo(it.javaClass).isDevelopedByJetBrains() } + .map { it.runToolData.idForStatistics } + .toSet() + + return if (data in validIds) ValidationResultType.ACCEPTED else ValidationResultType.REJECTED + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/uv/run/UvRunToolProvider.kt b/python/src/com/jetbrains/python/sdk/uv/run/UvRunToolProvider.kt index 4d6f8bd8b1db..10b0614206a3 100644 --- a/python/src/com/jetbrains/python/sdk/uv/run/UvRunToolProvider.kt +++ b/python/src/com/jetbrains/python/sdk/uv/run/UvRunToolProvider.kt @@ -15,7 +15,7 @@ import com.jetbrains.python.sdk.uv.isUv private class UvRunToolProvider : PyRunToolProvider { override val runToolData: PyRunToolData = PyRunToolData( - PyRunToolId("py.run.with.uv"), + PyRunToolId("uv.run"), PyBundle.message("uv.run.configuration.type.display.name"), PyBundle.message("python.run.configuration.fragments.python.group"), )