ASPR-3010 Recommended plugin installation on IDEA startup (fix required plugins downloads)

This commit is contained in:
Nikita Iarychenko
2026-03-06 17:52:04 +04:00
parent 21d961b26e
commit 30bb824de6
5 changed files with 99 additions and 58 deletions

View File

@@ -1,19 +1,68 @@
<!-- 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. -->
<!-- -->
<!-- Modified by Nikita Iarychenko at 2025 as part of the OpenIDE project (https://openide.ru). -->
<!-- Any modifications are available on the same license terms as the original source code. -->
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3917_59728)">
<path d="M44.9455 0H18.7204C17.9105 0 17.1334 0.3216 16.5604 0.894545L0.894545 16.56C0.3216 17.1329 0 17.9097 0 18.72V44.9455C0 46.6324 1.36756 48 3.05455 48H29.2791C30.0899 48 30.8675 47.6775 31.4404 47.1041L47.1055 31.4212C47.6775 30.8487 47.9987 30.072 47.9987 29.2625L48 3.05455C48 1.36756 46.6324 0 44.9455 0Z" fill="url(#paint0_radial_3917_59728)"/>
<path d="M39 9H9V39H39V9Z" fill="black"/>
<path d="M25 33H13V35.0001H25V33Z" fill="white"/>
<path d="M13 22.0828H15.1845V14.9172H13V13H19.4819V14.9172H17.2975V22.0828H19.4819V24H13V22.0828Z" fill="white"/>
<path d="M20.582 22.0202H22.1614C22.4808 22.0202 22.7652 21.9521 23.014 21.816C23.2627 21.6798 23.4551 21.4874 23.5913 21.2387C23.7274 20.9899 23.7955 20.7059 23.7955 20.3861V13H25.9404V20.5349C25.9404 21.195 25.7883 21.7882 25.4846 22.3147C25.1808 22.8412 24.7619 23.2538 24.2274 23.5521C23.693 23.8508 23.0934 24 22.4282 24H20.582V22.0202Z" fill="white"/>
</g>
<defs>
<radialGradient id="paint0_radial_3917_59728" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(47.9996) rotate(135) scale(65.4071)">
<stop offset="0.0819502" stop-color="#FE2857"/>
<stop offset="0.72777" stop-color="#007EFF"/>
</radialGradient>
<clipPath id="clip0_3917_59728">
<rect width="48" height="48" fill="white"/>
</clipPath>
</defs>
<g clip-path="url(#clip0_3181_719)">
<mask id="mask0_3181_719" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="48" height="48">
<rect width="48" height="48" rx="3.5625" fill="white"/>
</mask>
<g mask="url(#mask0_3181_719)">
<g filter="url(#filter0_f_3181_719)">
<circle cx="23.8041" cy="23.8041" r="23.8041" fill="#9B84E7"/>
</g>
<g filter="url(#filter1_f_3181_719)">
<ellipse cx="43.5" cy="22.125" rx="24" ry="41.625" fill="#9B84E7"/>
</g>
<g filter="url(#filter2_f_3181_719)">
<ellipse cx="17.5349" cy="54.5633" rx="21.0612" ry="21.8449" fill="#F24E4E"/>
</g>
<g filter="url(#filter3_f_3181_719)">
<ellipse cx="-2.05727" cy="46.8245" rx="14.0082" ry="20.3755" fill="#9B84E7"/>
</g>
<g filter="url(#filter4_f_3181_719)">
<ellipse cx="49.5" cy="51" rx="12.75" ry="22.5" fill="white"/>
</g>
<g filter="url(#filter5_f_3181_719)">
<ellipse cx="8.62019" cy="8.13063" rx="23.7061" ry="27.5265" fill="#4D409B"/>
</g>
</g>
<path d="M30.2595 16.9275H38.3003C40.1579 16.9275 41.6633 15.514 41.6633 13.7722V10.2923C41.6633 8.55051 40.1579 7.13702 38.3003 7.13702H30.2595C28.4019 7.13702 26.8965 8.55051 26.8965 10.2923V13.7722C26.8965 15.514 28.4019 16.9275 30.2595 16.9275Z" fill="white"/>
<path d="M38.686 19.5119H29.8753C28.0449 19.5119 26.5613 21.002 26.5613 22.8404V34.0881C26.5613 34.7775 26.0438 35.3812 25.3592 35.4232C24.6165 35.4688 23.9991 34.8778 23.9991 34.1374V8.27327C23.9991 6.8999 22.7933 5.8293 21.4405 6.02263C12.7097 7.27015 6 14.81 6 23.9219C6.00182 33.8966 14.0698 42 24.0009 42C33.932 42 42 33.9057 42 23.9183V22.8386C42 21.0001 40.5164 19.51 38.686 19.51V19.5119Z" fill="white"/>
</g>
<defs>
<filter id="filter0_f_3181_719" x="-5.49551" y="-5.49551" width="58.5994" height="58.5992" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2.74776" result="effect1_foregroundBlur_3181_719"/>
</filter>
<filter id="filter1_f_3181_719" x="14.0045" y="-24.9955" width="58.991" height="94.241" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2.74776" result="effect1_foregroundBlur_3181_719"/>
</filter>
<filter id="filter2_f_3181_719" x="-9.25698" y="26.9878" width="53.5838" height="55.151" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2.86531" result="effect1_foregroundBlur_3181_719"/>
</filter>
<filter id="filter3_f_3181_719" x="-21.5609" y="20.9535" width="39.0071" height="51.7421" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="2.74776" result="effect1_foregroundBlur_3181_719"/>
</filter>
<filter id="filter4_f_3181_719" x="28.6488" y="20.3988" width="41.7024" height="61.2024" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="4.05061" result="effect1_foregroundBlur_3181_719"/>
</filter>
<filter id="filter5_f_3181_719" x="-22.6876" y="-26.9975" width="62.6154" height="70.2563" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="3.80082" result="effect1_foregroundBlur_3181_719"/>
</filter>
<clipPath id="clip0_3181_719">
<rect width="48" height="48" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -94,7 +94,7 @@ plugins.page.ok.button.continue.without = Продолжить без Плаги
plugins.page.ok.button.install = Установить Выбранные
plugins.page.choose.counter.no = Нет плагинов для установки
plugins.page.choose.counter.one = 1 плагин выбран для установки
plugins.page.choose.counter.multiple = {0} плагина выбрано для установки
plugins.page.choose.counter.multiple = Выбрано плагинов для установки: {0}
plugins.page.list.item.bundled = Предустановлен
# Plugin descriptions

View File

@@ -143,12 +143,12 @@ class OpenIdeStartupWizardService(private val coroutineScope: CoroutineScope) :
name = "CSV Editor",
description = ImportSettingsBundle.message("plugin.description.csvEditor"),
),
WizardPluginImpl(
id = "com.koxudaxi.pydantic",
icon = AllIcons.Plugins.PluginLogo,
name = "Pydantic",
description = ImportSettingsBundle.message("plugin.description.pydantic"),
),
//WizardPluginImpl(
// id = "com.koxudaxi.pydantic",
// icon = AllIcons.Plugins.PluginLogo,
// name = "Pydantic",
// description = ImportSettingsBundle.message("plugin.description.pydantic"),
//),
)
),
WizardPluginGroupImpl(

View File

@@ -7,11 +7,11 @@ package com.intellij.ide.startup.importSettings.jb
import com.intellij.configurationStore.getPerOsSettingsStorageFolderName
import com.intellij.ide.GeneralSettings
import com.intellij.ide.plugins.*
import com.intellij.ide.plugins.marketplace.MarketplaceRequests
import com.intellij.ide.startup.importSettings.ImportSettingsBundle
import com.intellij.ide.startup.importSettings.StartupImportIcons
import com.intellij.ide.startup.importSettings.chooser.ui.SettingsImportOrigin
import com.intellij.ide.startup.importSettings.data.*
import com.intellij.ide.startup.importSettings.openide.utils.PluginInstallationUtil
import com.intellij.ide.startup.importSettings.statistics.ImportSettingsEventsCollector
import com.intellij.ide.startup.importSettings.transfer.TransferSettingsProgress
import com.intellij.l10n.LocalizationStateService
@@ -402,6 +402,8 @@ class JbImportServiceImpl(private val coroutineScope: CoroutineScope) : JbServic
filteredCategories.add(SettingsCategory.PLUGINS)
plugins2import = productInfo.getPluginsDescriptors().filter {
setting.selectedChildIds?.contains(it.key.idString) ?: false
}.filter { (_, descriptor) ->
PluginManagerCore.isCompatible(descriptor) && descriptor.namespace?.lowercase() != "jetbrains"
}
unselectedPlugins = setting.unselectedChildIds
logger.info("Will import ${setting.selectedChildIds?.size} custom plugins: ${setting.selectedChildIds?.joinToString()}\n" +
@@ -650,11 +652,9 @@ private suspend fun calculatePluginsToInstall(alreadyInstalled: Set<PluginId>, t
if (pluginsToAttemptInstallation.isEmpty()) return emptyList()
reporter.text(ImportSettingsBundle.message("plugin-installation.progress.determining-plugins-to-download"))
val loadedPlugins = withContext(Dispatchers.IO) {
MarketplaceRequests.loadLastCompatiblePluginDescriptors(pluginsToAttemptInstallation.toSet(), null, true)
}
val loadedPlugins = PluginInstallationUtil.getPluginsToInstall(pluginsToAttemptInstallation)
return loadedPlugins
return loadedPlugins.filter { it.pluginId !in alreadyInstalled }
}
}

View File

@@ -15,6 +15,7 @@ package com.intellij.ide.startup.importSettings.openide.utils
import com.intellij.ide.plugins.IdeaPluginDescriptor
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.ide.plugins.PluginNode
import com.intellij.ide.plugins.marketplace.MarketplaceRequests
import com.intellij.ide.startup.importSettings.ImportSettingsBundle
import com.intellij.ide.startup.importSettings.openide.service.PluginIconService
@@ -57,9 +58,7 @@ object PluginInstallationUtil {
progressIndicator.text = ImportSettingsBundle.message("plugin-installation.progress.determining-plugins-to-download")
progressIndicator.fraction = 0.05
val pluginsToInstall: List<IdeaPluginDescriptor> = withContext(Dispatchers.IO) {
MarketplaceRequests.Companion.loadLastCompatiblePluginDescriptors(idsToInstall.toSet(), null, true)
}
val pluginsToInstall: List<IdeaPluginDescriptor> = getPluginsToInstall(idsToInstall)
if (pluginsToInstall.isEmpty()) {
progressIndicator.fraction = 1.0
@@ -67,20 +66,12 @@ object PluginInstallationUtil {
}
var shouldRestart = false
val installedDuringSession = mutableSetOf<PluginId>()
val queue = ArrayDeque(pluginsToInstall)
val progressPerPlugin = 0.9 / pluginsToInstall.size.coerceAtLeast(1)
var currentProgress = 0.1
while (queue.isNotEmpty()) {
for (plugin in pluginsToInstall) {
if (progressIndicator.isCanceled) break
val plugin = queue.removeFirst()
if (plugin.pluginId in installedDuringSession || PluginManagerCore.isPluginInstalled(plugin.pluginId)) {
continue
}
val progressPerPlugin = 0.9 / (queue.size + 1).coerceAtLeast(1)
progressIndicator.text = ImportSettingsBundle.message("plugin-installation.progress.downloading", plugin.name)
// Load and set plugin icon
@@ -102,28 +93,13 @@ object PluginInstallationUtil {
}
withContext(Dispatchers.IO) {
val prepareToInstall = downloader.prepareToInstall(null)
if (prepareToInstall) {
val depIds = downloader.descriptor
.dependencies
.filter { !it.isOptional }
.map { it.pluginId }
.filter { it !in installedDuringSession && !PluginManagerCore.isPluginInstalled(it) }
if (depIds.isNotEmpty()) {
val depDescriptors = MarketplaceRequests.loadLastCompatiblePluginDescriptors(depIds.toSet(), null, true)
queue.addAll(depDescriptors)
}
}
downloader.prepareToInstall(null)
}
val appliedWithoutRestart = withContext(Dispatchers.EDT + ModalityState.any().asContextElement()) {
downloader.installDynamically(null)
}
installedDuringSession.add(plugin.pluginId)
if (!appliedWithoutRestart) {
shouldRestart = true
}
@@ -139,4 +115,20 @@ object PluginInstallationUtil {
progressIndicator.text = null
return shouldRestart
}
suspend fun getPluginsToInstall(idsToInstall: List<PluginId>): List<PluginNode> = withContext(Dispatchers.IO) {
val directPlugins = MarketplaceRequests.loadLastCompatiblePluginDescriptors(idsToInstall.toSet(), null, true)
val dependencyIds = directPlugins
.asSequence()
.flatMap { it.dependencies }
.filter { !it.isOptional }
.map { it.pluginId }
.filter { it !in idsToInstall && !PluginManagerCore.isPluginInstalled(it) }
.toSet()
val dependencyPlugins = MarketplaceRequests.loadLastCompatiblePluginDescriptors(dependencyIds, null, true)
(directPlugins + dependencyPlugins).distinctBy { it.pluginId }
}
}