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)