diff --git a/plugins/ide-startup/importSettings/resources/icons/docker.svg b/plugins/ide-startup/importSettings/resources/icons/docker.svg
new file mode 100644
index 000000000000..995eeab68df7
--- /dev/null
+++ b/plugins/ide-startup/importSettings/resources/icons/docker.svg
@@ -0,0 +1,3 @@
+
diff --git a/plugins/ide-startup/importSettings/resources/icons/spring.svg b/plugins/ide-startup/importSettings/resources/icons/spring.svg
new file mode 100644
index 000000000000..c90355a4766e
--- /dev/null
+++ b/plugins/ide-startup/importSettings/resources/icons/spring.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/plugins/ide-startup/importSettings/resources/ideIcons/IC_20.svg b/plugins/ide-startup/importSettings/resources/ideIcons/IC_20.svg
index 96681fb110f6..8f96380b58fa 100644
--- a/plugins/ide-startup/importSettings/resources/ideIcons/IC_20.svg
+++ b/plugins/ide-startup/importSettings/resources/ideIcons/IC_20.svg
@@ -1,68 +1,19 @@
-
-
-
-
+
diff --git a/plugins/ide-startup/importSettings/resources/ideIcons/IC_24.svg b/plugins/ide-startup/importSettings/resources/ideIcons/IC_24.svg
index bd6425e874d0..70d7da33e8e5 100644
--- a/plugins/ide-startup/importSettings/resources/ideIcons/IC_24.svg
+++ b/plugins/ide-startup/importSettings/resources/ideIcons/IC_24.svg
@@ -1,68 +1,19 @@
-
-
-
-
+
diff --git a/plugins/ide-startup/importSettings/resources/intellij.ide.startup.importSettings.xml b/plugins/ide-startup/importSettings/resources/intellij.ide.startup.importSettings.xml
index d7ee92d63781..d43a6f4bfae5 100644
--- a/plugins/ide-startup/importSettings/resources/intellij.ide.startup.importSettings.xml
+++ b/plugins/ide-startup/importSettings/resources/intellij.ide.startup.importSettings.xml
@@ -29,7 +29,7 @@
+ serviceImplementation="com.intellij.ide.startup.importSettings.chooser.ui.OpenIdeStartupWizardService"/>
diff --git a/plugins/ide-startup/importSettings/resources/messages/ImportSettingsBundle.properties b/plugins/ide-startup/importSettings/resources/messages/ImportSettingsBundle.properties
index 8549e15401f1..e0e09ee29e11 100644
--- a/plugins/ide-startup/importSettings/resources/messages/ImportSettingsBundle.properties
+++ b/plugins/ide-startup/importSettings/resources/messages/ImportSettingsBundle.properties
@@ -41,11 +41,12 @@ exit.confirm.title = Settings Import Is in Progress
exit.confirm.prompt = Do you want to stop the import?
If you stop it now, only part of the settings will be imported.
stop.import = Stop Import
-onboarding.wizard.finish-button=Finish
+onboarding.wizard.finish-button=Готово
onboarding.wizard.getting-ready=Getting Ready\u2026
plugin-installation.progress.text-with-details={0} / {1}
plugin-installation.progress.determining-plugins-to-download=Determining plugins to download
+plugin-installation.progress.downloading=Установка {0}...
settings.category.ui.name=UI settings
settings.category.ui.description=Includes theme, editor font, toolbar, and notifications
@@ -66,6 +67,7 @@ transfer.settings.message=Importing from {0}\u2026
import.settings.title = Importing settings\u2026
import.settings.ok = Import Settings
import.settings.back = Back
+import.settings.back.ru = Назад
import.settings.sync.ok = Sync Settings
import.settings.sync.import.once = Import Once
@@ -87,13 +89,37 @@ choose.product.setting.sync.turned.off=Setting Sync Is Turned Off
choose.keymap.title = Choose Keymap
wizard.button.continue = Continue
-plugins.page.title = Featured Plugins
-plugins.page.ok.button.continue.without = Continue without Plugins
-plugins.page.ok.button.install = Install Selected
-plugins.page.choose.counter.no = No plugins selected for installation
-plugins.page.choose.counter.one = 1 plugin selected for installation
-plugins.page.choose.counter.multiple = {0} plugins selected for installation
-install.plugins.page.title = Installing Plugins\u2026
+plugins.page.title = Рекомендуемые Плагины
+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.list.item.bundled = Предустановлен
+
+# Plugin descriptions
+plugin.description.mapstruct = Плагин для редактирования MapStruct мапперов
+plugin.description.init.spring = Интеграция Init Spring для новых проектов
+plugin.description.checkStyle = Статический анализ кода на соответствие стандартам кодирования
+plugin.description.mavenHelper = Анализ зависимостей Maven и запуск из редактора
+plugin.description.java.kotlin.support = Поддерживается OpenIDE
+plugin.description.spring.support = Поддерживается Amplicode
+plugin.description.python = Поддержка языка Python
+plugin.description.django = Поддержка Django фреймворка для веб-приложений на Python
+plugin.description.pylint = Статический анализатор кода Python для выявления ошибок
+plugin.description.csvEditor = Редактор CSV и TSV файлов с подсветкой и форматированием
+plugin.description.pydantic = Поддержка Pydantic для валидации данных в Python
+plugin.description.webtools = Набор инструментов для веб-разработки
+plugin.description.go = Поддержка языка Go
+plugin.description.fileWatchers = Автоматический запуск внешних инструментов при изменении файлов
+plugin.description.makefile = Подсветка синтаксиса и навигация для Makefile
+plugin.description.protocolBuffers = Поддержка Protocol Buffers для сериализации данных
+plugin.description.easyp = Линтинг и форматирование proto файлов с помощью easyp
+plugin.description.kilocode = Kilo Code AI Агент
+plugin.description.continue = Open-source AI ассистент для кода
+plugin.description.docker = Поддержка Docker для управления контейнерами
+
+install.plugins.page.title = Установка Плагинов\u2026
theme.page.title = Choose Theme
theme.page.dark = Dark
diff --git a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/OpenIdePluginIcons.java b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/OpenIdePluginIcons.java
new file mode 100644
index 000000000000..57186a4e62a2
--- /dev/null
+++ b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/OpenIdePluginIcons.java
@@ -0,0 +1,30 @@
+// OpenIDE Project
+// Copyright (C) 2026 “Open Development Platform” Ltd. (https://openide.ru)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License version 3 or later as published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+package com.intellij.ide.startup.importSettings;
+
+import com.intellij.ui.IconManager;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+public final class OpenIdePluginIcons {
+ private static @NotNull Icon load(@NotNull String path, int cacheKey, int flags) {
+ return IconManager.getInstance().loadRasterizedIcon(path, OpenIdePluginIcons.class.getClassLoader(), cacheKey, flags);
+ }
+
+ public static final class Icons {
+ /** 60x48 */ public static final @NotNull Icon Docker = load("icons/docker.svg", -271658419, 0);
+ /** 16x16 */ public static final @NotNull Icon Spring = load("icons/spring.svg", 1614158219, 0);
+ }
+}
diff --git a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/StartupImportIcons.java b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/StartupImportIcons.java
index 1dba337d4a4c..4248c5a6718d 100644
--- a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/StartupImportIcons.java
+++ b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/StartupImportIcons.java
@@ -1,4 +1,7 @@
-// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+//
+// Modified by Artyom Orlov at 2026 as part of the OpenIDE project(https://openide.ru).
+// Any modifications are available on the same license terms as the original source code.
package com.intellij.ide.startup.importSettings;
import com.intellij.ui.IconManager;
@@ -45,9 +48,9 @@ public final class StartupImportIcons {
/** 20x20 */ public static final @NotNull Icon GO_20 = load("ideIcons/GO_20.svg", -1868538832, 0);
/** 24x24 */ public static final @NotNull Icon GO_24 = load("ideIcons/GO_24.svg", 1440377958, 0);
/** 48x48 */ public static final @NotNull Icon GO_48 = load("ideIcons/GO_48.svg", 1561543628, 0);
- /** 20x20 */ public static final @NotNull Icon IC_20 = load("ideIcons/IC_20.svg", -1970123839, 0);
- /** 24x24 */ public static final @NotNull Icon IC_24 = load("ideIcons/IC_24.svg", 1159411325, 0);
- /** 48x48 */ public static final @NotNull Icon IC_48 = load("ideIcons/IC_48.svg", 337830589, 0);
+ /** 20x20 */ public static final @NotNull Icon IC_20 = load("ideIcons/IC_20.svg", -1970123830, 0);
+ /** 24x24 */ public static final @NotNull Icon IC_24 = load("ideIcons/IC_24.svg", 1159411326, 0);
+ /** 48x48 */ public static final @NotNull Icon IC_48 = load("ideIcons/IC_48.svg", 337830580, 0);
/** 20x20 */ public static final @NotNull Icon IU_20 = load("ideIcons/IU_20.svg", 1667621169, 0);
/** 24x24 */ public static final @NotNull Icon IU_24 = load("ideIcons/IU_24.svg", 458021303, 0);
/** 48x48 */ public static final @NotNull Icon IU_48 = load("ideIcons/IU_48.svg", 897538403, 0);
diff --git a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/chooser/ui/OnboardingController.kt b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/chooser/ui/OnboardingController.kt
index 00a579c4039a..7404610b8db1 100644
--- a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/chooser/ui/OnboardingController.kt
+++ b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/chooser/ui/OnboardingController.kt
@@ -1,4 +1,7 @@
-// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+//
+// Modified by Artyom Orlov at 2026 as part of the OpenIDE project(https://openide.ru).
+// Any modifications are available on the same license terms as the original source code.
package com.intellij.ide.startup.importSettings.chooser.ui
import com.intellij.ide.startup.importSettings.data.StartupWizardService
@@ -107,7 +110,7 @@ class OnboardingController private constructor(){
}
service.onEnter()
- wizardController.goToThemePage(true)
+ wizardController.goToPluginPage()
if(!dl.isShowing) {
dl.initialize()
diff --git a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/chooser/ui/OpenIdeStartupWizardService.kt b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/chooser/ui/OpenIdeStartupWizardService.kt
new file mode 100644
index 000000000000..1b1a70fe2db6
--- /dev/null
+++ b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/chooser/ui/OpenIdeStartupWizardService.kt
@@ -0,0 +1,272 @@
+// OpenIDE Project
+// Copyright (C) 2026 “Open Development Platform” Ltd. (https://openide.ru)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License version 3 or later as published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+package com.intellij.ide.startup.importSettings.chooser.ui
+
+import com.intellij.icons.AllIcons
+import com.intellij.ide.startup.importSettings.ImportSettingsBundle
+import com.intellij.ide.startup.importSettings.OpenIdePluginIcons
+import com.intellij.ide.startup.importSettings.data.*
+import com.intellij.ide.startup.importSettings.openide.utils.PluginInstallationUtil
+import com.intellij.ide.startup.importSettings.transfer.ProgressIndicatorAdapter
+import com.intellij.ide.startup.importSettings.transfer.TransferSettingsProgressIndicator
+import com.intellij.openapi.application.EDT
+import com.intellij.openapi.application.ModalityState
+import com.intellij.openapi.application.asContextElement
+import com.intellij.openapi.application.ex.ApplicationManagerEx
+import com.intellij.util.IconUtil
+import com.jetbrains.rd.util.lifetime.Lifetime
+import com.jetbrains.rd.util.reactive.Property
+import com.jetbrains.rd.util.reactive.Signal
+import kotlinx.coroutines.*
+
+class OpenIdeStartupWizardService(private val coroutineScope: CoroutineScope) : StartupWizardService {
+ override val isActive = true
+ private val _shouldClose = Signal()
+ override val shouldClose: Signal
+ get() = _shouldClose
+
+ override fun getKeymapService(): KeymapService {
+ TODO("Not yet implemented")
+ }
+
+ override fun getThemeService(): ThemeService {
+ TODO("Not yet implemented")
+ }
+
+
+ override fun getPluginService(): PluginService {
+ return object : PluginService {
+ override val pluginGroups: List
+ get() = listOf(
+ WizardPluginGroupImpl(
+ id = "ai",
+ name = "AI",
+ icon = AllIcons.Actions.Lightning,
+ plugins = listOf(
+ WizardPluginImpl(
+ id = "ai.kilocode.jetbrains",
+ icon = AllIcons.Plugins.PluginLogo,
+ name = "Kilo Code",
+ description = ImportSettingsBundle.message("plugin.description.kilocode"),
+ ),
+ WizardPluginImpl(
+ id = "com.github.continuedev.continueintellijextension",
+ icon = AllIcons.Plugins.PluginLogo,
+ name = "Continue",
+ description = ImportSettingsBundle.message("plugin.description.continue"),
+ ),
+ )
+ ),
+ WizardPluginGroupImpl(
+ id = "java-kotlin",
+ name = "Java / Kotlin",
+ icon = AllIcons.FileTypes.Java,
+ plugins = listOf(
+ WizardPluginImpl(
+ id = "org.mapstruct.intellij",
+ icon = AllIcons.Plugins.PluginLogo,
+ name = "MapStruct Support",
+ description = ImportSettingsBundle.message("plugin.description.mapstruct"),
+ ),
+ WizardPluginImpl(
+ id = "pro.nikolaev.init-spring",
+ icon = AllIcons.Plugins.PluginLogo,
+ name = "Init Spring",
+ description = ImportSettingsBundle.message("plugin.description.init.spring"),
+ ),
+ WizardPluginImpl(
+ id = "CheckStyle-IDEA",
+ icon = AllIcons.Plugins.PluginLogo,
+ name = "CheckStyle-IDEA",
+ description = ImportSettingsBundle.message("plugin.description.checkStyle"),
+ ),
+ WizardPluginImpl(
+ id = "MavenRunHelper",
+ icon = AllIcons.Plugins.PluginLogo,
+ name = "Maven Helper",
+ description = ImportSettingsBundle.message("plugin.description.mavenHelper"),
+ ),
+ WizardPluginImpl(
+ id = "bundled.java.kotlin",
+ icon = AllIcons.FileTypes.Java,
+ name = "Java / Kotlin support",
+ description = ImportSettingsBundle.message("plugin.description.java.kotlin.support"),
+ bundled = true,
+ ),
+ WizardPluginImpl(
+ id = "bundled.spring",
+ icon = OpenIdePluginIcons.Icons.Spring,
+ name = "Spring support",
+ description = ImportSettingsBundle.message("plugin.description.spring.support"),
+ bundled = true,
+ ),
+
+ )
+ ),
+ WizardPluginGroupImpl(
+ id = "python",
+ name = "Python",
+ icon = AllIcons.Language.Python,
+ plugins = listOf(
+ WizardPluginImpl(
+ id = "PythonCore",
+ icon = AllIcons.Plugins.PluginLogo,
+ name = "Python Community Edition",
+ description = ImportSettingsBundle.message("plugin.description.python"),
+ ),
+ WizardPluginImpl(
+ id = "com.haulmont.django",
+ icon = AllIcons.Plugins.PluginLogo,
+ name = "Django[beta]",
+ description = ImportSettingsBundle.message("plugin.description.django"),
+ ),
+ WizardPluginImpl(
+ id = "com.leinardi.pycharm.pylint",
+ icon = AllIcons.Plugins.PluginLogo,
+ name = "Pylint",
+ description = ImportSettingsBundle.message("plugin.description.pylint"),
+ ),
+ WizardPluginImpl(
+ id = "net.seesharpsoft.intellij.plugins.csv",
+ icon = AllIcons.Plugins.PluginLogo,
+ 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"),
+ //),
+ )
+ ),
+ WizardPluginGroupImpl(
+ id = "web",
+ name = "Web",
+ icon = AllIcons.FileTypes.Html,
+ plugins = listOf(
+ WizardPluginImpl(
+ id = "com.haulmont.webtools",
+ icon = AllIcons.Plugins.PluginLogo,
+ name = "Frontend/Web [beta]",
+ description = ImportSettingsBundle.message("plugin.description.webtools"),
+ ),
+ )
+ ),
+ WizardPluginGroupImpl(
+ id = "go",
+ name = "Go",
+ icon = AllIcons.Language.GO,
+ plugins = listOf(
+ WizardPluginImpl(
+ id = "com.haulmont.go",
+ icon = AllIcons.Plugins.PluginLogo,
+ name = "Go [beta]",
+ description = ImportSettingsBundle.message("plugin.description.go"),
+ ),
+ WizardPluginImpl(
+ id = "com.intellij.plugins.watcher",
+ icon = AllIcons.Plugins.PluginLogo,
+ name = "File Watchers",
+ description = ImportSettingsBundle.message("plugin.description.fileWatchers"),
+ ),
+ WizardPluginImpl(
+ id = "name.kropp.intellij.makefile",
+ icon = AllIcons.Plugins.PluginLogo,
+ name = "Makefile Language",
+ description = ImportSettingsBundle.message("plugin.description.makefile"),
+ ),
+ WizardPluginImpl(
+ id = "idea.plugin.protoeditor",
+ icon = AllIcons.Plugins.PluginLogo,
+ name = "Protocol Buffers",
+ description = ImportSettingsBundle.message("plugin.description.protocolBuffers"),
+ ),
+ WizardPluginImpl(
+ id = "com.github.easyptech.easyp",
+ icon = AllIcons.Plugins.PluginLogo,
+ name = "easyp",
+ description = ImportSettingsBundle.message("plugin.description.easyp"),
+ ),
+ )
+ ),
+ WizardPluginGroupImpl(
+ id = "deployment",
+ name = "Deployment",
+ icon = OpenIdePluginIcons.Icons.Docker,
+ plugins = listOf(
+ WizardPluginImpl(
+ id = "ru.openide.docker",
+ icon = AllIcons.Plugins.PluginLogo,
+ name = "Docker",
+ description = ImportSettingsBundle.message("plugin.description.docker"),
+ ),
+ )
+ ),
+ )
+
+ override fun onStepEnter() {
+ }
+
+ override fun install(lifetime: Lifetime, ids: List): PluginImportProgress {
+ val progressIndicator = TransferSettingsProgressIndicator()
+ val progressAdapter = ProgressIndicatorAdapter(progressIndicator)
+ val iconProperty = Property(AllIcons.Plugins.PluginLogo)
+
+ coroutineScope.launch {
+ var needRestart = false
+ try {
+ needRestart = PluginInstallationUtil.installPlugins(ids, progressAdapter) { icon ->
+ iconProperty.set(IconUtil.resizeSquared(icon, 64))
+ }
+ } catch (e: CancellationException) {
+ throw e
+ } catch (_: Exception) {
+ } finally {
+ withContext(Dispatchers.EDT + ModalityState.any().asContextElement()) {
+ if (needRestart) {
+ ApplicationManagerEx.getApplicationEx().restart(true)
+ } else {
+ _shouldClose.fire(Unit)
+ }
+ }
+ }
+ }
+
+ return object : PluginImportProgress {
+ override val icon = iconProperty
+ override val progressMessage = progressIndicator.progressMessage
+ override val progress = progressIndicator.progress
+ }
+ }
+
+ override fun skipPlugins() {
+ }
+
+ override fun shouldShowPage(pluginIdsMarkedForInstallation: List): Boolean {
+ return true
+ }
+
+ }
+ }
+
+ override fun onEnter() {
+ }
+
+ override fun onCancel() {
+ }
+
+ override fun onExit() {
+ }
+}
\ No newline at end of file
diff --git a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/chooser/ui/WizardController.kt b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/chooser/ui/WizardController.kt
index 99344894940a..381b51b3a6d6 100644
--- a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/chooser/ui/WizardController.kt
+++ b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/chooser/ui/WizardController.kt
@@ -1,4 +1,7 @@
-// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+//
+// Modified by Artyom Orlov at 2026 as part of the OpenIDE project(https://openide.ru).
+// Any modifications are available on the same license terms as the original source code.
package com.intellij.ide.startup.importSettings.chooser.ui
import com.intellij.ide.startup.importSettings.data.StartupWizardService
@@ -64,9 +67,8 @@ internal class WizardControllerImpl(dialog: OnboardingDialog,
}
override fun goToPluginPage() {
- val page = WizardPluginsPage(this, service.getPluginService(), goBackAction = {
- goToKeymapPage(isForwardDirection = false)
- }, goForwardAction = { ids ->
+ val page = WizardPluginsPage(this, service.getPluginService(), goBackAction = null,
+ goForwardAction = { ids ->
goToInstallPluginPage(ids)
}, continueButtonTextOverride = null)
dialog.changePage(page)
diff --git a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/data/StartupWizardService.kt b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/data/StartupWizardService.kt
index 46bf59f08352..e190f08894ed 100644
--- a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/data/StartupWizardService.kt
+++ b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/data/StartupWizardService.kt
@@ -1,4 +1,7 @@
-// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+//
+// Modified by Artyom Orlov at 2026 as part of the OpenIDE project(https://openide.ru).
+// Any modifications are available on the same license terms as the original source code.
package com.intellij.ide.startup.importSettings.data
import com.intellij.ide.startup.importSettings.ImportSettingsBundle
@@ -88,7 +91,7 @@ data class WizardScheme(
)
interface PluginService {
- val plugins: List
+ val pluginGroups: List
fun onStepEnter()
fun install(lifetime: Lifetime, ids: List): PluginImportProgress
fun skipPlugins()
@@ -104,6 +107,15 @@ interface WizardPlugin {
val icon: Icon
val name: String
val description: String?
+ val bundled: Boolean
+ get() = false
+}
+
+interface WizardPluginGroup {
+ val id: String
+ val name: String
+ val icon: Icon
+ val plugins: List
}
interface KeymapService {
diff --git a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/data/WizardMockData.kt b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/data/WizardMockData.kt
index db7a7963a739..3dd288d25bde 100644
--- a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/data/WizardMockData.kt
+++ b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/data/WizardMockData.kt
@@ -1,4 +1,7 @@
-// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+//
+// Modified by Artyom Orlov at 2026 as part of the OpenIDE project(https://openide.ru).
+// Any modifications are available on the same license terms as the original source code.
package com.intellij.ide.startup.importSettings.data
import com.intellij.icons.AllIcons
@@ -91,7 +94,14 @@ class PluginServiceImpl : PluginService {
WizardPluginImpl(AllIcons.TransferSettings.Vscode, "Ideolog", "An interactive viewer for log files with customizible highlighting, filtration and navigation to source code."),
)
- override val plugins: List = listOf
+ override val pluginGroups: List = listOf(
+ WizardPluginGroupImpl(
+ id = "mock.group",
+ name = "Mock Group",
+ icon = AllIcons.Plugins.PluginLogo,
+ plugins = listOf
+ )
+ )
override fun onStepEnter() {}
@@ -136,6 +146,13 @@ class WizardPluginImpl(
override val id: String = UUID.randomUUID().toString(),
) : WizardPlugin
+class WizardPluginGroupImpl(
+ override val id: String,
+ override val name: String,
+ override val icon: Icon,
+ override val plugins: List
+) : WizardPluginGroup
+
@Suppress("HardCodedStringLiteral")
class TestKeymapService : KeymapService {
override val keymaps: List = listOf(
diff --git a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/jb/JbImportServiceImpl.kt b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/jb/JbImportServiceImpl.kt
index 4e55be33bcfd..0e73d8cc6742 100644
--- a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/jb/JbImportServiceImpl.kt
+++ b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/jb/JbImportServiceImpl.kt
@@ -24,6 +24,7 @@ import com.intellij.ide.startup.importSettings.data.IconProductSize
import com.intellij.ide.startup.importSettings.data.JbService
import com.intellij.ide.startup.importSettings.data.Product
import com.intellij.ide.startup.importSettings.data.SettingsService
+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
@@ -418,6 +419,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(
@@ -660,11 +663,9 @@ private suspend fun calculatePluginsToInstall(alreadyInstalled: Set, t
if (pluginsToAttemptInstallation.isEmpty()) return emptyList()
reporter.text(ImportSettingsBundle.message("plugin-installation.progress.determining-plugins-to-download"))
- val loadedPlugins = withContext(Dispatchers.IO) {
- MarketplaceRequests.loadLastCompatiblePluginModels(pluginsToAttemptInstallation.toSet(), buildNumber = null, throwExceptions = true)
- .mapNotNull { it.getDescriptor() as? PluginNode }
- }
- return loadedPlugins
+ val loadedPlugins = PluginInstallationUtil.getPluginsToInstall(pluginsToAttemptInstallation)
+
+ return loadedPlugins.filter { it.pluginId !in alreadyInstalled }
}
}
diff --git a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/openide/service/PluginIconService.kt b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/openide/service/PluginIconService.kt
new file mode 100644
index 000000000000..c3f4b09aa9eb
--- /dev/null
+++ b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/openide/service/PluginIconService.kt
@@ -0,0 +1,53 @@
+// OpenIDE Project
+// Copyright (C) 2026 “Open Development Platform” Ltd. (https://openide.ru)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License version 3 or later as published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+package com.intellij.ide.startup.importSettings.openide.service
+
+import com.intellij.ide.plugins.marketplace.MarketplaceRequests
+import com.intellij.ide.plugins.newui.PluginLogo
+import com.intellij.openapi.components.Service
+import com.intellij.openapi.components.service
+import com.intellij.openapi.extensions.PluginId
+import com.intellij.openapi.util.IntellijInternalApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import javax.swing.Icon
+
+@Service(Service.Level.APP)
+class PluginIconService {
+
+ suspend fun loadIcon(pluginId: String): Icon? {
+ return withContext(Dispatchers.IO) {
+ loadFromMarketplace(pluginId)
+ }
+ }
+
+ @OptIn(IntellijInternalApi::class)
+ private fun loadFromMarketplace(pluginId: String): Icon? {
+ return try {
+ val marketplaceRequests = MarketplaceRequests.getInstance()
+ val pluginNode = marketplaceRequests.getLastCompatiblePluginUpdateModel(
+ PluginId.getId(pluginId),
+ buildNumber = null
+ ) ?: return null
+
+ PluginLogo.getIcon(pluginNode.getDescriptor(), false, false, false)
+ } catch (_: Exception) {
+ null
+ }
+ }
+
+ companion object {
+ fun getInstance(): PluginIconService = service()
+ }
+}
\ No newline at end of file
diff --git a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/openide/utils/PluginInstallationUtil.kt b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/openide/utils/PluginInstallationUtil.kt
new file mode 100644
index 000000000000..a41168d9940e
--- /dev/null
+++ b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/openide/utils/PluginInstallationUtil.kt
@@ -0,0 +1,134 @@
+// OpenIDE Project
+// Copyright (C) 2026 “Open Development Platform” Ltd. (https://openide.ru)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License version 3 or later as published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+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
+import com.intellij.openapi.application.EDT
+import com.intellij.openapi.application.ModalityState
+import com.intellij.openapi.application.asContextElement
+import com.intellij.openapi.extensions.PluginId
+import com.intellij.openapi.progress.ProgressIndicator
+import com.intellij.openapi.updateSettings.impl.PluginDownloader
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import javax.swing.Icon
+
+object PluginInstallationUtil {
+
+ /**
+ * Installs plugins by their IDs.
+ * @param onIconLoaded callback to update icon when plugin icon is loaded
+ * @return true if IDE restart is required after installation
+ */
+ @Suppress("DEPRECATION")
+ suspend fun installPlugins(
+ pluginIds: List,
+ progressIndicator: ProgressIndicator,
+ onIconLoaded: ((Icon) -> Unit)? = null,
+ ): Boolean {
+ if (progressIndicator.isCanceled || pluginIds.isEmpty()) {
+ return false
+ }
+
+ val idsToInstall = pluginIds
+ .map { PluginId.Companion.getId(it) }
+ .filter { !PluginManagerCore.isPluginInstalled(it) }
+
+ if (idsToInstall.isEmpty()) {
+ progressIndicator.fraction = 1.0
+ return false
+ }
+
+ progressIndicator.text = ImportSettingsBundle.message("plugin-installation.progress.determining-plugins-to-download")
+ progressIndicator.fraction = 0.05
+
+ val pluginsToInstall: List = getPluginsToInstall(idsToInstall)
+
+ if (pluginsToInstall.isEmpty()) {
+ progressIndicator.fraction = 1.0
+ return false
+ }
+
+ var shouldRestart = false
+ val progressPerPlugin = 0.9 / pluginsToInstall.size.coerceAtLeast(1)
+ var currentProgress = 0.1
+
+ for (plugin in pluginsToInstall) {
+ if (progressIndicator.isCanceled) break
+
+ progressIndicator.text = ImportSettingsBundle.message("plugin-installation.progress.downloading", plugin.name)
+
+ // Load and set plugin icon
+ if (onIconLoaded != null) {
+ try {
+ val icon = PluginIconService.Companion.getInstance().loadIcon(plugin.pluginId.idString)
+ if (icon != null) {
+ withContext(Dispatchers.EDT + ModalityState.any().asContextElement()) {
+ onIconLoaded(icon)
+ }
+ }
+ }
+ catch (_: Exception) {
+ }
+ }
+
+ try {
+ val downloader = PluginDownloader.createDownloader(plugin).withErrorsConsumer { problem ->
+ }
+
+ withContext(Dispatchers.IO) {
+ downloader.prepareToInstall(null)
+ }
+
+ val appliedWithoutRestart = withContext(Dispatchers.EDT + ModalityState.any().asContextElement()) {
+ downloader.installDynamically(null)
+ }
+
+ if (!appliedWithoutRestart) {
+ shouldRestart = true
+ }
+ }
+ catch (_: Exception) {
+ }
+
+ currentProgress += progressPerPlugin
+ progressIndicator.fraction = currentProgress.coerceAtMost(0.99)
+ }
+
+ progressIndicator.fraction = 1.0
+ progressIndicator.text = null
+ return shouldRestart
+ }
+
+ suspend fun getPluginsToInstall(idsToInstall: List): List = 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 }
+ }
+}
\ No newline at end of file
diff --git a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/wizard/pluginChooser/OpenIdeWizardPluginGroupPane.kt b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/wizard/pluginChooser/OpenIdeWizardPluginGroupPane.kt
new file mode 100644
index 000000000000..765fb5dd229c
--- /dev/null
+++ b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/wizard/pluginChooser/OpenIdeWizardPluginGroupPane.kt
@@ -0,0 +1,173 @@
+// OpenIDE Project
+// Copyright (C) 2026 “Open Development Platform” Ltd. (https://openide.ru)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License version 3 or later as published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+package com.intellij.ide.startup.importSettings.wizard.pluginChooser
+
+import com.intellij.icons.AllIcons
+import com.intellij.ide.startup.importSettings.data.WizardPluginGroup
+import com.intellij.ui.components.JBLabel
+import com.intellij.ui.components.panels.VerticalLayout
+import com.intellij.util.IconUtil
+import com.intellij.util.ui.JBFont
+import com.intellij.util.ui.JBUI
+import com.intellij.util.ui.ThreeStateCheckBox
+import com.intellij.util.ui.UIUtil
+import java.awt.BorderLayout
+import java.awt.Cursor
+import java.awt.FlowLayout
+import java.awt.event.MouseAdapter
+import java.awt.event.MouseEvent
+import kotlinx.coroutines.CoroutineScope
+import javax.swing.Icon
+import javax.swing.JLabel
+import javax.swing.JPanel
+
+class OpenIdeWizardPluginGroupPane(
+ private val group: WizardPluginGroup,
+ private val coroutineScope: CoroutineScope,
+ private val onSelectionChanged: () -> Unit
+) {
+ private val pluginPanes = mutableListOf()
+ private val groupCheckbox = ThreeStateCheckBox()
+ private val counterLabel = JLabel()
+ private val contentPanel = JPanel(VerticalLayout(0)).apply {
+ isOpaque = false
+ isVisible = false
+ border = JBUI.Borders.emptyLeft(28)
+
+ group.plugins.forEach { plugin ->
+ val pluginPane = OpenIdeWizardPluginPane(plugin, coroutineScope) {
+ updateGroupCheckboxState()
+ onSelectionChanged()
+ }
+ pluginPanes.add(pluginPane)
+ add(pluginPane.pane)
+ }
+ }
+ private val expandIcon = JLabel()
+ private var isExpanded = false
+
+ val pane: JPanel = createPane()
+
+ private val selectablePanes: List
+ get() = pluginPanes.filter { !it.isBundled }
+
+ val selectedPlugins: List
+ get() = pluginPanes.filter { it.selected && !it.isBundled }
+
+ val selectedCount: Int
+ get() = selectablePanes.count { it.selected }
+
+ val totalCount: Int
+ get() = selectablePanes.size
+
+ private fun createPane(): JPanel {
+ return JPanel(BorderLayout()).apply {
+ isOpaque = false
+ border = JBUI.Borders.empty(0, 16)
+
+ add(createHeader(), BorderLayout.NORTH)
+ add(contentPanel, BorderLayout.CENTER)
+
+ updateGroupCheckboxState()
+ }
+ }
+
+ private fun createHeader(): JPanel {
+ groupCheckbox.apply {
+ isOpaque = false
+ isThirdStateEnabled = false
+ addActionListener {
+ val shouldSelect = state == ThreeStateCheckBox.State.SELECTED
+ selectablePanes.forEach { it.setSelected(shouldSelect) }
+ updateGroupCheckboxState()
+ onSelectionChanged()
+ }
+ }
+
+ counterLabel.apply {
+ foreground = UIUtil.getContextHelpForeground()
+ font = JBFont.label()
+ }
+
+ expandIcon.apply {
+ icon = getExpandIcon()
+ cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
+ addMouseListener(object : MouseAdapter() {
+ override fun mouseClicked(e: MouseEvent) {
+ toggleExpanded()
+ }
+ })
+ }
+
+ val scaledIcon = IconUtil.scale(group.icon, null, 1.5f)
+ val groupIcon = JLabel(scaledIcon).apply {
+ border = JBUI.Borders.emptyRight(8)
+ }
+
+ val groupName = JBLabel(group.name).apply {
+ font = JBFont.h4()
+ }
+
+ val headerClickArea = JPanel(FlowLayout(FlowLayout.LEFT, 0, 0)).apply {
+ isOpaque = false
+ add(expandIcon)
+ add(groupCheckbox)
+ add(groupIcon)
+ add(groupName)
+ add(JLabel(" ").apply { border = JBUI.Borders.emptyLeft(4) })
+ add(counterLabel)
+
+ cursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)
+ addMouseListener(object : MouseAdapter() {
+ override fun mouseClicked(e: MouseEvent) {
+ if (e.source !== expandIcon && e.source !== groupCheckbox) {
+ toggleExpanded()
+ }
+ }
+ })
+ }
+
+ return JPanel(BorderLayout()).apply {
+ isOpaque = false
+ border = JBUI.Borders.empty(4, 0)
+ add(headerClickArea, BorderLayout.CENTER)
+ }
+ }
+
+ private fun updateGroupCheckboxState() {
+ val selectable = selectablePanes
+ val selectedCount = selectable.count { it.selected }
+ groupCheckbox.state = when (selectedCount) {
+ 0 -> ThreeStateCheckBox.State.NOT_SELECTED
+ selectable.size -> ThreeStateCheckBox.State.SELECTED
+ else -> ThreeStateCheckBox.State.DONT_CARE
+ }
+ counterLabel.text = "($selectedCount/${selectable.size})"
+ }
+
+ private fun toggleExpanded() {
+ isExpanded = !isExpanded
+ contentPanel.isVisible = isExpanded
+ expandIcon.icon = getExpandIcon()
+ }
+
+ private fun getExpandIcon(): Icon {
+ return if (isExpanded) AllIcons.General.ArrowDown else AllIcons.General.ArrowRight
+ }
+
+ fun setAllSelected(selected: Boolean) {
+ selectablePanes.forEach { it.setSelected(selected) }
+ updateGroupCheckboxState()
+ }
+}
\ No newline at end of file
diff --git a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/wizard/pluginChooser/OpenIdeWizardPluginPane.kt b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/wizard/pluginChooser/OpenIdeWizardPluginPane.kt
new file mode 100644
index 000000000000..81fba2bce3ac
--- /dev/null
+++ b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/wizard/pluginChooser/OpenIdeWizardPluginPane.kt
@@ -0,0 +1,173 @@
+// OpenIDE Project
+// Copyright (C) 2026 “Open Development Platform” Ltd. (https://openide.ru)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License version 3 or later as published by the Free Software Foundation.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see http://www.gnu.org/licenses/.
+package com.intellij.ide.startup.importSettings.wizard.pluginChooser
+
+import com.intellij.ide.startup.importSettings.ImportSettingsBundle
+import com.intellij.ide.startup.importSettings.data.WizardPlugin
+import com.intellij.ide.startup.importSettings.openide.service.PluginIconService
+import com.intellij.ui.components.JBCheckBox
+import com.intellij.ui.dsl.builder.HyperlinkEventAction
+import com.intellij.ui.dsl.builder.MAX_LINE_LENGTH_WORD_WRAP
+import com.intellij.ui.dsl.builder.components.DslLabel
+import com.intellij.ui.dsl.builder.components.DslLabelType
+import com.intellij.util.IconUtil
+import com.intellij.util.ui.JBUI
+import com.intellij.util.ui.UIUtil
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import java.awt.GridBagConstraints
+import java.awt.GridBagLayout
+import javax.swing.JEditorPane
+import javax.swing.JLabel
+import javax.swing.JPanel
+import javax.swing.SwingUtilities
+
+class OpenIdeWizardPluginPane(val plugin: WizardPlugin, private val coroutineScope: CoroutineScope, changeHandler: () -> Unit) {
+
+ private var checkBox = JBCheckBox().apply {
+ isOpaque = false
+ }
+
+ val selected: Boolean
+ get() = checkBox.isSelected
+
+ fun setSelected(value: Boolean) {
+ checkBox.isSelected = value
+ }
+
+ fun setEnabled(value: Boolean) {
+ checkBox.isEnabled = value
+ pane.isEnabled = value
+ nameLabel.isEnabled = value
+ }
+
+ private val iconLabel = JLabel(IconUtil.resizeSquared(plugin.icon, 24))
+ private val nameLabel = createLabel()
+ private val bundledLabel = JLabel(ImportSettingsBundle.message("plugins.page.list.item.bundled")).apply {
+ foreground = UIUtil.getContextHelpForeground()
+ border = JBUI.Borders.emptyRight(10)
+ }
+
+ val pane = JPanel(GridBagLayout()).apply {
+ val c = GridBagConstraints()
+
+ plugin.description?.let {
+ c.gridx = 0
+ c.gridy = 0
+ c.weightx = 0.0
+ c.anchor = GridBagConstraints.CENTER
+ add(checkBox, c)
+
+ c.gridx = 1
+ c.anchor = GridBagConstraints.CENTER
+ add(iconLabel.apply {
+ border = JBUI.Borders.empty(0, 10)
+ }, c)
+
+ c.gridx = 2
+ c.weightx = 1.0
+ c.fill = GridBagConstraints.HORIZONTAL
+ add(JPanel(GridBagLayout()).apply {
+ isOpaque = false
+ val inner = GridBagConstraints()
+ inner.gridx = 0
+ inner.gridy = 0
+ inner.weightx = 1.0
+ inner.fill = GridBagConstraints.HORIZONTAL
+ inner.anchor = GridBagConstraints.WEST
+ add(nameLabel.apply {
+ text = plugin.name
+ }, inner)
+
+ inner.gridy = 1
+ add(createLabel().apply {
+ text = plugin.description
+ foreground = UIUtil.getLabelDisabledForeground()
+ }, inner)
+ }, c)
+
+ if (plugin.bundled) {
+ c.gridx = 3
+ c.weightx = 0.0
+ c.fill = GridBagConstraints.NONE
+ c.anchor = GridBagConstraints.EAST
+ add(bundledLabel, c)
+ }
+
+ } ?: run {
+ c.gridx = 0
+ c.gridy = 0
+ c.weightx = 0.0
+ add(checkBox, c)
+ c.gridx = 1
+ c.gridy = 0
+ add(iconLabel.apply {
+ border = JBUI.Borders.empty(0, 10)
+ }, c)
+ c.gridx = 2
+ c.gridy = 0
+ c.weightx = 1.0
+ c.fill = GridBagConstraints.HORIZONTAL
+ add(nameLabel.apply {
+ text = plugin.name
+ }, c)
+
+ if (plugin.bundled) {
+ c.gridx = 3
+ c.weightx = 0.0
+ c.fill = GridBagConstraints.NONE
+ c.anchor = GridBagConstraints.EAST
+ add(bundledLabel, c)
+ }
+ }
+ }.apply {
+ isOpaque = false
+ border = JBUI.Borders.empty(8, 20)
+ }
+
+ val isBundled: Boolean = plugin.bundled
+
+ init {
+ loadIconAsync()
+
+ if (isBundled) {
+ checkBox.isSelected = true
+ setEnabled(false)
+ }
+
+ checkBox.addItemListener {
+ changeHandler()
+ }
+ }
+
+ private fun createLabel(): JEditorPane {
+ val dslLabel = DslLabel(DslLabelType.LABEL)
+ dslLabel.action = HyperlinkEventAction.HTML_HYPERLINK_INSTANCE
+ dslLabel.maxLineLength = MAX_LINE_LENGTH_WORD_WRAP
+
+ return dslLabel
+ }
+
+ private fun loadIconAsync() {
+ coroutineScope.launch {
+ val icon = PluginIconService.getInstance().loadIcon(plugin.id)
+ if (icon != null) {
+ SwingUtilities.invokeLater {
+ iconLabel.icon = IconUtil.resizeSquared(icon, 24)
+ pane.repaint()
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/wizard/pluginChooser/WizardPluginsPage.kt b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/wizard/pluginChooser/WizardPluginsPage.kt
index c7bac8abd147..f84e0e3cd00f 100644
--- a/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/wizard/pluginChooser/WizardPluginsPage.kt
+++ b/plugins/ide-startup/importSettings/src/com/intellij/ide/startup/importSettings/wizard/pluginChooser/WizardPluginsPage.kt
@@ -1,4 +1,7 @@
-// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+//
+// Modified by Artyom Orlov at 2026 as part of the OpenIDE project(https://openide.ru).
+// Any modifications are available on the same license terms as the original source code.
package com.intellij.ide.startup.importSettings.wizard.pluginChooser
import com.intellij.ide.startup.importSettings.ImportSettingsBundle
@@ -8,6 +11,7 @@ import com.intellij.ide.startup.importSettings.chooser.ui.ScrollSnapToFocused
import com.intellij.ide.startup.importSettings.chooser.ui.UiUtils
import com.intellij.ide.startup.importSettings.chooser.ui.WizardPagePane
import com.intellij.ide.startup.importSettings.data.PluginService
+import com.intellij.ide.startup.importSettings.data.WizardPluginGroup
import com.intellij.openapi.util.NlsActions.ActionText
import com.intellij.openapi.util.SystemInfo
import com.intellij.platform.ide.bootstrap.StartupWizardStage
@@ -29,14 +33,14 @@ import javax.swing.SwingUtilities
internal class WizardPluginsPage(
val controller: BaseController,
private val pluginService: PluginService,
- goBackAction: () -> Unit,
+ goBackAction: (() -> Unit)?,
goForwardAction: (List) -> Unit,
private val continueButtonTextOverride: @ActionText String?
) : OnboardingPage {
override val stage: StartupWizardStage = StartupWizardStage.WizardPluginPage
- private val pluginPanes = mutableListOf()
+ private val groupPanes = mutableListOf()
private val contentPage: JComponent
@@ -44,14 +48,20 @@ internal class WizardPluginsPage(
border = JBUI.Borders.emptyLeft(26)
foreground = UIUtil.getContextHelpForeground()
}
+
override fun confirmExit(parentComponent: Component?): Boolean = true
- private fun getSelected(): List {
- return pluginPanes.filter { it.selected }.toList()
+ private fun getSelected(): List {
+ return groupPanes.flatMap { it.selectedPlugins }
}
+
+ private fun getSelectedCount(): Int {
+ return groupPanes.sumOf { it.selectedCount }
+ }
+
private fun changeHandler() {
- val selected = getSelected()
- when(selected.size) {
+ val selectedSize = getSelected().size
+ when(selectedSize) {
0 -> {
leftLabel.text = ImportSettingsBundle.message("plugins.page.choose.counter.no")
continueAction.text = continueButtonTextOverride ?: ImportSettingsBundle.message("plugins.page.ok.button.continue.without")
@@ -61,7 +71,7 @@ internal class WizardPluginsPage(
continueAction.text = continueButtonTextOverride ?: ImportSettingsBundle.message("plugins.page.ok.button.install")
}
else -> {
- leftLabel.text = ImportSettingsBundle.message("plugins.page.choose.counter.multiple", selected.size)
+ leftLabel.text = ImportSettingsBundle.message("plugins.page.choose.counter.multiple", selectedSize)
continueAction.text = continueButtonTextOverride ?: ImportSettingsBundle.message("plugins.page.ok.button.install")
}
}
@@ -74,14 +84,14 @@ internal class WizardPluginsPage(
border = JBUI.Borders.empty(18, 20)
})
- val plugins = pluginService.plugins
+ val pluginGroups: List = pluginService.pluginGroups
val listPane = JPanel(VerticalLayout(JBUI.scale(4))).apply {
isOpaque = false
- plugins.forEach {
- val pl = WizardPluginPane(it) { changeHandler() }
- pluginPanes.add(pl)
- add(pl.pane)
+ pluginGroups.forEach { group: WizardPluginGroup ->
+ val groupPane = OpenIdeWizardPluginGroupPane(group, controller.lifetime.coroutineScope) { changeHandler() }
+ groupPanes.add(groupPane)
+ add(groupPane.pane)
}
border = JBUI.Borders.empty(10, 0)
}
@@ -102,7 +112,7 @@ internal class WizardPluginsPage(
})
}
- private val backAction = controller.createButton(ImportSettingsBundle.message("import.settings.back"), goBackAction)
+ private val backAction = controller.createButton(ImportSettingsBundle.message("import.settings.back.ru"), goBackAction ?: {})
private val continueAction = controller.createDefaultButton(continueButtonTextOverride ?: ImportSettingsBundle.message("plugins.page.ok.button.continue.without")) {
val ids = getSelected().map { it.plugin.id }.toList()
@@ -110,10 +120,20 @@ internal class WizardPluginsPage(
}
init {
- val buttons: List = if (SystemInfo.isMac) {
- listOf(backAction, continueAction)
+ val buttons: List = buildList {
+ if (SystemInfo.isMac) {
+ if (goBackAction != null) {
+ add(backAction)
+ }
+ add(continueAction)
+ }
+ else {
+ add(continueAction)
+ if (goBackAction != null) {
+ add(backAction)
+ }
+ }
}
- else listOf(continueAction, backAction)
contentPage = WizardPagePane(pane, buttons, leftLabel)