Wizard: Add initial version of the new project wizard

This commit is contained in:
Ilya Kirillov
2019-12-12 15:29:27 +03:00
parent 4916f166fe
commit aca193ddd2
191 changed files with 11132 additions and 3 deletions
+10 -1
View File
@@ -621,11 +621,20 @@ tasks {
)
}
register("idea-new-project-wizard-tests") {
dependsOn("dist")
dependsOn(
":libraries:tools:new-project-wizard:test",
":libraries:tools:new-project-wizard:new-project-wizard-cli:test"
)
}
register("idea-plugin-tests") {
dependsOn("dist")
dependsOn(
"idea-plugin-main-tests",
"idea-plugin-additional-tests"
"idea-plugin-additional-tests",
"idea-new-project-wizard-tests"
)
}
+1
View File
@@ -18,6 +18,7 @@ dependencies {
compile(projectTests(":idea:idea-maven"))
compile(projectTests(":j2k"))
compile(projectTests(":nj2k"))
compile(projectTests(":libraries:tools:new-project-wizard:new-project-wizard-cli"))
compile(projectTests(":idea:idea-android"))
compile(projectTests(":idea:scripting-support"))
compile(projectTests(":jps-plugin"))
@@ -160,6 +160,7 @@ import org.jetbrains.kotlin.search.AbstractAnnotatedMembersSearchTest
import org.jetbrains.kotlin.search.AbstractInheritorsSearchTest
import org.jetbrains.kotlin.shortenRefs.AbstractShortenRefsTest
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.tools.projectWizard.cli.AbstractBuildFileGenerationTest
import org.jetbrains.kotlinx.serialization.AbstractSerializationIrBytecodeListingTest
import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginBytecodeListingTest
import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginDiagnosticTest
@@ -1066,6 +1067,15 @@ fun main(args: Array<String>) {
}
}
testGroup(
"libraries/tools/new-project-wizard/new-project-wizard-cli/tests",
"libraries/tools/new-project-wizard/new-project-wizard-cli/testData"
) {
testClass<AbstractBuildFileGenerationTest> {
model("buildFileGeneration", recursive = false, extension = null)
}
}
//TODO: move these tests into idea-completion module
testGroup("idea/tests", "idea/idea-completion/testData") {
testClass<AbstractCodeFragmentCompletionHandlerTest> {
@@ -167,6 +167,7 @@ import org.jetbrains.kotlin.search.AbstractAnnotatedMembersSearchTest
import org.jetbrains.kotlin.search.AbstractInheritorsSearchTest
import org.jetbrains.kotlin.shortenRefs.AbstractShortenRefsTest
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.tools.projectWizard.cli.AbstractBuildFileGenerationTest
import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginBytecodeListingTest
import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginDiagnosticTest
import org.jetbrains.kotlinx.serialization.AbstractSerializationIrBytecodeListingTest
@@ -1005,6 +1006,15 @@ fun main(args: Array<String>) {
}
}
testGroup(
"libraries/tools/new-project-wizard/new-project-wizard-cli/tests",
"libraries/tools/new-project-wizard/new-project-wizard-cli/testData"
) {
testClass<AbstractBuildFileGenerationTest> {
model("buildFileGeneration", recursive = false, extension = null)
}
}
//TODO: move these tests into idea-completion module
testGroup("idea/tests", "idea/idea-completion/testData") {
testClass<AbstractCodeFragmentCompletionHandlerTest> {
@@ -157,6 +157,7 @@ import org.jetbrains.kotlin.search.AbstractAnnotatedMembersSearchTest
import org.jetbrains.kotlin.search.AbstractInheritorsSearchTest
import org.jetbrains.kotlin.shortenRefs.AbstractShortenRefsTest
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.tools.projectWizard.cli.AbstractBuildFileGenerationTest
import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginBytecodeListingTest
import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginDiagnosticTest
import org.jetbrains.kotlinx.serialization.AbstractSerializationIrBytecodeListingTest
@@ -984,6 +985,15 @@ fun main(args: Array<String>) {
}
}
testGroup(
"libraries/tools/new-project-wizard/new-project-wizard-cli/tests",
"libraries/tools/new-project-wizard/new-project-wizard-cli/testData"
) {
testClass<AbstractBuildFileGenerationTest> {
model("buildFileGeneration", recursive = false, extension = null)
}
}
//TODO: move these tests into idea-completion module
testGroup("idea/tests", "idea/idea-completion/testData") {
testClass<AbstractCodeFragmentCompletionHandlerTest> {
@@ -157,6 +157,7 @@ import org.jetbrains.kotlin.search.AbstractAnnotatedMembersSearchTest
import org.jetbrains.kotlin.search.AbstractInheritorsSearchTest
import org.jetbrains.kotlin.shortenRefs.AbstractShortenRefsTest
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.tools.projectWizard.cli.AbstractBuildFileGenerationTest
import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginBytecodeListingTest
import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginDiagnosticTest
import org.jetbrains.kotlinx.serialization.AbstractSerializationIrBytecodeListingTest
@@ -984,6 +985,15 @@ fun main(args: Array<String>) {
}
}
testGroup(
"libraries/tools/new-project-wizard/new-project-wizard-cli/tests",
"libraries/tools/new-project-wizard/new-project-wizard-cli/testData"
) {
testClass<AbstractBuildFileGenerationTest> {
model("buildFileGeneration", recursive = false, extension = null)
}
}
//TODO: move these tests into idea-completion module
testGroup("idea/tests", "idea/idea-completion/testData") {
testClass<AbstractCodeFragmentCompletionHandlerTest> {
@@ -157,6 +157,7 @@ import org.jetbrains.kotlin.search.AbstractAnnotatedMembersSearchTest
import org.jetbrains.kotlin.search.AbstractInheritorsSearchTest
import org.jetbrains.kotlin.shortenRefs.AbstractShortenRefsTest
import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.tools.projectWizard.cli.AbstractBuildFileGenerationTest
import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginBytecodeListingTest
import org.jetbrains.kotlinx.serialization.AbstractSerializationPluginDiagnosticTest
import org.jetbrains.kotlinx.serialization.AbstractSerializationIrBytecodeListingTest
@@ -984,6 +985,15 @@ fun main(args: Array<String>) {
}
}
testGroup(
"libraries/tools/new-project-wizard/new-project-wizard-cli/tests",
"libraries/tools/new-project-wizard/new-project-wizard-cli/testData"
) {
testClass<AbstractBuildFileGenerationTest> {
model("buildFileGeneration", recursive = false, extension = null)
}
}
//TODO: move these tests into idea-completion module
testGroup("idea/tests", "idea/idea-completion/testData") {
testClass<AbstractCodeFragmentCompletionHandlerTest> {
@@ -0,0 +1,39 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
implementation(project(":libraries:tools:new-project-wizard"))
implementation(project(":idea:ide-common"))
implementation(project(":idea:idea-core"))
implementation(project(":idea:idea-jvm"))
compileOnly(project(":kotlin-reflect-api"))
compileOnly(intellijCoreDep())
compileOnly(intellijDep())
compileOnly(intellijPluginDep("gradle"))
excludeInAndroidStudio(rootProject) {
compileOnly(intellijPluginDep("maven"))
}
Platform[191].orLower {
compileOnly(intellijDep()) { includeJars("java-api", "java-impl") }
}
Platform[192].orHigher {
compileOnly(intellijPluginDep("java")) { includeJars("java-api", "java-impl") }
testCompileOnly(intellijPluginDep("java")) { includeJars("java-api", "java-impl") }
}
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
projectTest {
dependsOn(":dist")
workingDir = rootDir
}
@@ -0,0 +1,41 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard
import org.jetbrains.kotlin.tools.projectWizard.core.PluginsCreator
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingType
import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference
import org.jetbrains.kotlin.tools.projectWizard.core.service.Service
import org.jetbrains.kotlin.tools.projectWizard.plugins.StructurePlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemPlugin
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class IdeWizard(
createPlugins: PluginsCreator,
initialServices: List<Service>
) : Wizard(createPlugins, initialServices) {
private val allSettings = plugins.flatMap { it.declaredSettings }
init {
context.settingContext.initPluginSettings(allSettings)
}
var projectPath by setting(StructurePlugin::projectPath.reference)
var projectName by setting(StructurePlugin::name.reference)
var groupId by setting(StructurePlugin::groupId.reference)
var artifactId by setting(StructurePlugin::artifactId.reference)
var buildSystemType by setting(BuildSystemPlugin::type.reference)
private fun <V : Any, T : SettingType<V>> setting(reference: SettingReference<V, T>) =
object : ReadWriteProperty<Any?, V?> {
override fun setValue(thisRef: Any?, property: KProperty<*>, value: V?) {
context.settingContext[reference] = value ?: return
}
@Suppress("UNCHECKED_CAST")
override fun getValue(thisRef: Any?, property: KProperty<*>): V? =
context.settingContext[reference]
}
}
@@ -0,0 +1,126 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard
import com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix
import com.intellij.jarRepository.JarRepositoryManager
import com.intellij.openapi.module.ModifiableModuleModel
import com.intellij.openapi.module.ModuleTypeId
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.util.PathUtil
import org.jetbrains.idea.maven.utils.library.RepositoryLibraryProperties
import org.jetbrains.kotlin.config.ResourceKotlinRootType
import org.jetbrains.kotlin.config.SourceKotlinRootType
import org.jetbrains.kotlin.config.TestResourceKotlinRootType
import org.jetbrains.kotlin.config.TestSourceKotlinRootType
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.service.JpsService
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.library.MavenArtifact
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.SourcesetType
import java.nio.file.Path
import com.intellij.openapi.module.Module as IdeaModule
class IdeaJpsService(
private val project: Project,
private val modulesModel: ModifiableModuleModel
) : JpsService {
override fun importProject(path: Path, modulesIrs: List<ModuleIR>): TaskResult<Unit> = runWriteAction {
ProjectImporter(project, modulesModel, path, modulesIrs).import()
}
}
private class ProjectImporter(
private val project: Project,
private val modulesModel: ModifiableModuleModel,
private val path: Path,
private val modulesIrs: List<ModuleIR>
) {
private val librariesPath: Path
get() = path / "libs"
fun import() = modulesIrs.mapSequence { convertModule(it) } andThen
safe { modulesModel.commit() }
private fun convertModule(moduleIr: ModuleIR): TaskResult<IdeaModule> {
val module = modulesModel.newModule(
(moduleIr.path / "${moduleIr.name}.iml").toString(),
ModuleTypeId.JAVA_MODULE
)
val rootModel = ModuleRootManager.getInstance(module).modifiableModel
val contentRoot = rootModel.addContentEntry(moduleIr.path.url)
SourcesetType.ALL.forEach { sourceset ->
val isTest = sourceset == SourcesetType.test
contentRoot.addSourceFolder(
(moduleIr.path / "src" / sourceset.name / "kotlin").url,
if (isTest) TestSourceKotlinRootType else SourceKotlinRootType
)
contentRoot.addSourceFolder(
(moduleIr.path / "src" / sourceset.name / "resources").url,
if (isTest) TestResourceKotlinRootType else ResourceKotlinRootType
)
}
rootModel.inheritSdk()
rootModel.commit()
addLibrariesToTheModule(moduleIr, module)
return Success(module)
}
private fun addLibrariesToTheModule(moduleIr: ModuleIR, module: IdeaModule) {
moduleIr.irs.forEach { ir ->
when {
ir is LibraryDependencyIR && !ir.isKotlinStdlib -> {
attachLibraryToModule(ir, module)
}
}
}
}
private fun attachLibraryToModule(
libraryDependency: LibraryDependencyIR,
module: IdeaModule
) {
val artifact = libraryDependency.artifact as? MavenArtifact ?: return
val libraryProperties = RepositoryLibraryProperties(
artifact.groupId,
artifact.artifactId,
libraryDependency.version.toString()
)
val classesRoots = downloadLibraryAndGetItsClasses(libraryProperties)
ModuleRootModificationUtil.addModuleLibrary(
module,
if (classesRoots.size > 1) libraryProperties.artifactId else null,
OrderEntryFix.refreshAndConvertToUrls(classesRoots),
emptyList(),
when (libraryDependency.dependencyType) {
DependencyType.MAIN -> DependencyScope.COMPILE
DependencyType.TEST -> DependencyScope.TEST
}
)
}
private fun downloadLibraryAndGetItsClasses(libraryProperties: RepositoryLibraryProperties) =
JarRepositoryManager.loadDependenciesModal(
project,
libraryProperties,
false,
false,
librariesPath.toString(),
null
).asSequence()
.filter { it.type == OrderRootType.CLASSES }
.map { PathUtil.getLocalPath(it.file) }
.toList()
}
private val Path.url
get() = VfsUtil.pathToUrl(toString())
@@ -0,0 +1,183 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard
import com.intellij.ide.projectWizard.ProjectSettingsStep
import com.intellij.ide.util.projectWizard.*
import com.intellij.openapi.Disposable
import com.intellij.openapi.module.ModifiableModuleModel
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.module.ModuleType
import com.intellij.openapi.options.ConfigurationException
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ui.configuration.ModulesProvider
import com.intellij.openapi.ui.Messages
import com.intellij.util.SystemProperties
import org.jetbrains.kotlin.idea.framework.KotlinModuleSettingStep
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
import org.jetbrains.kotlin.tools.projectWizard.core.Failure
import org.jetbrains.kotlin.tools.projectWizard.core.Success
import org.jetbrains.kotlin.tools.projectWizard.core.isSuccess
import org.jetbrains.kotlin.tools.projectWizard.core.onFailure
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.plugins.Plugins
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemType
import org.jetbrains.kotlin.tools.projectWizard.plugins.projectTemplates.ProjectTemplatesPlugin
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.PomWizardStepComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.firstStep.FirstWizardStepComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.SecondStepWizardComponent
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.nio.file.Paths
import javax.swing.JComponent
import com.intellij.openapi.module.Module as IdeaModule
class NewProjectWizardModuleBuilder : ModuleBuilder() {
private val wizard = IdeWizard(Plugins.allPlugins, listOf(IdeaAndroidService()))
companion object {
const val MODULE_BUILDER_ID = "kotlin.newProjectWizard.builder"
}
private var wizardContext: WizardContext? = null
override fun getModuleType(): ModuleType<*> = NewProjectWizardModuleType()
override fun createWizardSteps(
wizardContext: WizardContext,
modulesProvider: ModulesProvider
): Array<ModuleWizardStep> {
this.wizardContext = wizardContext
return arrayOf(ModuleNewWizardSecondStep(wizard))
}
override fun commit(
project: Project,
model: ModifiableModuleModel?,
modulesProvider: ModulesProvider?
): List<IdeaModule>? {
val modulesModel = model ?: ModuleManager.getInstance(project).modifiableModel
val success = wizard.apply(
services = listOf(
IdeaMavenService(project),
IdeaGradleService(project),
IdeaJpsService(project, modulesModel),
IdeaFileSystemService(),
IdeaAndroidService()
),
phases = GenerationPhase.startingFrom(GenerationPhase.FIRST_STEP)
).onFailure { errors ->
val errorMessages = errors.joinToString(separator = "\n") { it.message }
Messages.showErrorDialog(project, errorMessages, "The following errors arose during project generation")
}.isSuccess
return when {
!success -> null
wizard.buildSystemType == BuildSystemType.Jps -> runWriteAction {
modulesModel.modules.toList().onEach { setupModule(it) }
}
else -> emptyList()
}
}
override fun modifySettingsStep(settingsStep: SettingsStep): ModuleWizardStep {
updateProjectNameAndPomDate(settingsStep)
return when (wizard.buildSystemType) {
BuildSystemType.Jps -> {
KotlinModuleSettingStep(
JvmPlatforms.defaultJvmPlatform,
this,
settingsStep,
wizardContext
)
}
else -> PomWizardStep(settingsStep, wizard)
}
}
private fun updateProjectNameAndPomDate(settingsStep: SettingsStep) {
val suggestedProjectName = with(wizard.valuesReadingContext) {
ProjectTemplatesPlugin::template.settingValue.suggestedProjectName.decapitalize()
}
settingsStep.moduleNameLocationSettings?.apply {
val projectParentDirectory = moduleContentRoot.let { Paths.get(it).parent.toString() }
moduleName = ProjectWizardUtil.findNonExistingFileName(projectParentDirectory, suggestedProjectName, "")
}
settingsStep.safeAs<ProjectSettingsStep>()?.bindModuleSettings()
wizard.artifactId = suggestedProjectName
wizard.groupId = SystemProperties.getUserName()?.let { "me.$it" } ?: suggestedProjectName
}
override fun getCustomOptionsStep(context: WizardContext?, parentDisposable: Disposable?) =
ModuleNewWizardFirstStep(wizard)
override fun setName(name: String) {
wizard.projectName = name
}
override fun setModuleFilePath(path: String) = Unit
override fun setContentEntryPath(moduleRootPath: String) {
wizard.projectPath = Paths.get(moduleRootPath)
}
}
abstract class WizardStep(protected val wizard: IdeWizard, private val phase: GenerationPhase) : ModuleWizardStep() {
override fun updateDataModel() = Unit // model is updated on every UI action
override fun validate(): Boolean =
when (val result = with(wizard.valuesReadingContext) { with(wizard) { validate(setOf(phase)) } }) {
is Success<*> -> true
is Failure -> {
val messages = result.errors.joinToString(separator = "\n") { it.message }
throw ConfigurationException(messages)
}
}
}
private class PomWizardStep(
originalSettingStep: SettingsStep,
wizard: IdeWizard
) : WizardStep(wizard, GenerationPhase.PROJECT_GENERATION) {
private val pomWizardStepComponent = PomWizardStepComponent(wizard.valuesReadingContext)
init {
originalSettingStep.addSettingsComponent(component)
pomWizardStepComponent.onInit()
}
override fun getComponent(): JComponent = pomWizardStepComponent.component
}
class ModuleNewWizardFirstStep(wizard: IdeWizard) : WizardStep(wizard, GenerationPhase.FIRST_STEP) {
private val component = FirstWizardStepComponent(wizard)
override fun getComponent(): JComponent = component.component
init {
runPreparePhase()
component.onInit()
}
private fun runPreparePhase() = ProgressManager.getInstance().runProcessWithProgressSynchronously(
{
wizard.apply(emptyList(), setOf(GenerationPhase.PREPARE)) { task ->
ProgressManager.getInstance().progressIndicator.text = task.title ?: ""
}
},
"",
true,
null
)
}
class ModuleNewWizardSecondStep(wizard: IdeWizard) : WizardStep(wizard, GenerationPhase.SECOND_STEP) {
private val component = SecondStepWizardComponent(wizard)
override fun getComponent(): JComponent = component.component
override fun _init() {
component.onInit()
}
}
@@ -0,0 +1,12 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard
import com.intellij.openapi.module.ModuleType
import org.jetbrains.kotlin.idea.KotlinIcons
import javax.swing.Icon
class NewProjectWizardModuleType: ModuleType<NewProjectWizardModuleBuilder>(NewProjectWizardModuleBuilder.MODULE_BUILDER_ID) {
override fun getName(): String = "New Kotlin Project Wizard"
override fun getDescription(): String = name
override fun getNodeIcon(isOpened: Boolean): Icon = KotlinIcons.SMALL_LOGO
override fun createModuleBuilder(): NewProjectWizardModuleBuilder = NewProjectWizardModuleBuilder()
}
@@ -0,0 +1,91 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.android.sdk.AndroidSdkData
import org.jetbrains.idea.maven.project.MavenProjectsManager
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.tools.projectWizard.core.TaskResult
import org.jetbrains.kotlin.tools.projectWizard.core.safe
import org.jetbrains.kotlin.tools.projectWizard.core.service.*
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.ModuleIR
import org.jetbrains.plugins.gradle.action.ImportProjectFromScriptAction
import java.nio.file.Path
class IdeaFileSystemService : FileSystemService {
override fun createDirectory(path: Path): TaskResult<Unit> = safe {
runWriteAction<Unit> {
VfsUtil.createDirectoryIfMissing(path.toString())
}
}
override fun createFile(path: Path, text: String): TaskResult<Unit> = safe {
runWriteAction {
val directoryPath = path.parent
val directory = VfsUtil.createDirectoryIfMissing(directoryPath.toFile().toString())!!
val virtualFile = directory.createChildData(this, path.fileName.toString())
VfsUtil.saveText(virtualFile, text)
}
}
}
class IdeaGradleService(private val project: Project) : GradleService {
// We have to call action directly as there is no common way
// to import Gradle project in all IDEAs from 183 to 193
override fun importProject(
path: Path,
modulesIrs: List<ModuleIR>
): TaskResult<Unit> = safe {
val virtualFile = LocalFileSystem.getInstance().findFileByPath(path.toString())!!
val dataContext = SimpleDataContext.getSimpleContext(
mapOf(
CommonDataKeys.PROJECT.name to project,
CommonDataKeys.VIRTUAL_FILE.name to virtualFile
),
null
)
val action = ImportProjectFromScriptAction()
val event = AnActionEvent.createFromAnAction(action, null, ActionPlaces.UNKNOWN, dataContext)
action.actionPerformed(event)
}
}
class IdeaMavenService(private val project: Project) : MavenService {
override fun importProject(
path: Path,
modulesIrs: List<ModuleIR>
): TaskResult<Unit> = safe {
val mavenProjectManager = MavenProjectsManager.getInstance(project)
val rootFile = LocalFileSystem.getInstance().findFileByPath(path.toString())!!
mavenProjectManager.addManagedFilesOrUnignore(rootFile.findAllPomFiles())
}
private fun VirtualFile.findAllPomFiles(): List<VirtualFile> {
val result = mutableListOf<VirtualFile>()
fun VirtualFile.find() {
when {
!isDirectory && name == "pom.xml" -> result += this
isDirectory -> children.forEach(VirtualFile::find)
}
}
find()
return result
}
}
class IdeaAndroidService : AndroidService {
override fun isValidAndroidSdk(path: Path): Boolean =
//todo use android plugin for that?
AndroidServiceImpl().isValidAndroidSdk(path)
}
@@ -0,0 +1,46 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
abstract class Component : Displayable {
private val subComponents = mutableListOf<Component>()
open fun onInit() {
subComponents.forEach(Component::onInit)
}
protected fun <C : Component> C.asSubComponent(): C = also {
this@Component.subComponents += it
}
}
abstract class DynamicComponent(private val valuesReadingContext: ValuesReadingContext) : Component() {
//TODO do not use it in future
protected val context = valuesReadingContext.context
protected val eventManager
get() = valuesReadingContext.context.eventManager
private var isInitialized: Boolean = false
override fun onInit() {
super.onInit()
isInitialized = true
}
var <V : Any, T : SettingType<V>> SettingReference<V, T>.value: V?
get() = with(valuesReadingContext) { notRequiredSettingValue() }
set(value) {
with(context) { settingContext[this@value] = value!! }
}
inline val <V : Any, reified T : SettingType<V>> PluginSettingPropertyReference<V, T>.value: V?
get() = reference.value
init {
valuesReadingContext.context.eventManager.addSettingUpdaterEventListener { reference ->
if (isInitialized) onValueUpdated(reference)
}
}
open fun onValueUpdated(reference: SettingReference<*, *>?) {}
}
@@ -0,0 +1,19 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import com.intellij.util.ui.HtmlPanel
import com.intellij.util.ui.UIUtil
import java.awt.Font
import javax.swing.event.HyperlinkEvent
class DescriptionPanel(initialText: String? = null) : HtmlPanel() {
private var bodyText: String? = initialText
fun updateText(text: String) {
bodyText = text.asHtml()
update()
}
override fun getBody() = bodyText.orEmpty()
override fun getBodyFont(): Font = UIUtil.getButtonFont().deriveFont(Font.PLAIN)
}
@@ -0,0 +1,7 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import javax.swing.JComponent
interface Displayable {
val component: JComponent
}
@@ -0,0 +1,40 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import com.intellij.ui.components.panels.VerticalLayout
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference
import org.jetbrains.kotlin.tools.projectWizard.plugins.StructurePlugin
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.StringSettingComponent
import javax.swing.Box
import javax.swing.JSeparator
import javax.swing.SwingConstants
class PomWizardStepComponent(valuesReadingContext: ValuesReadingContext) : WizardStepComponent(valuesReadingContext) {
private val groupIdComponent = StringSettingComponent(
StructurePlugin::groupId.reference,
valuesReadingContext,
showLabel = true
).asSubComponent()
private val artifactIdComponent = StringSettingComponent(
StructurePlugin::artifactId.reference,
valuesReadingContext,
showLabel = true
).asSubComponent()
private val versionComponent = StringSettingComponent(
StructurePlugin::version.reference,
valuesReadingContext,
showLabel = true
).asSubComponent()
override val component = panel(VerticalLayout(0)) {
add(Box.createVerticalStrut(10))
add(JSeparator(SwingConstants.HORIZONTAL))
add(Box.createVerticalStrut(10))
add(groupIdComponent.component)
add(artifactIdComponent.component)
add(versionComponent.component)
}
}
@@ -0,0 +1,68 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import com.intellij.ui.ColoredListCellRenderer
import com.intellij.ui.components.JBList
import org.jetbrains.kotlin.tools.projectWizard.core.ignore
import javax.swing.DefaultListModel
import javax.swing.JList
import javax.swing.ListModel
import javax.swing.ListSelectionModel
import javax.swing.event.ListDataEvent
import javax.swing.event.ListDataListener
abstract class AbstractSingleSelectableListWithIcon<V>(initialValues: List<V> = emptyList()) : JBList<V>() {
protected abstract fun ColoredListCellRenderer<V>.render(value: V)
protected open fun onSelected(value: V?) {}
protected val model: DefaultListModel<V>
get() = super.getModel() as DefaultListModel<V>
fun updateValues(newValues: List<V>) {
setModel(createDefaultListModel(newValues))
if (newValues.isNotEmpty()) {
selectedIndex = 0
}
}
init {
selectionMode = ListSelectionModel.SINGLE_SELECTION
updateValues(initialValues)
selectionModel.addListSelectionListener { e ->
if (e.valueIsAdjusting) return@addListSelectionListener
val selectedValue = when (selectedIndex) {
-1 -> null
else -> this.model.getElementAt(selectedIndex)
}
onSelected(selectedValue)
}
cellRenderer = object : ColoredListCellRenderer<V>() {
override fun customizeCellRenderer(
list: JList<out V>,
value: V?,
index: Int,
selected: Boolean,
hasFocus: Boolean
) {
render(value ?: return)
}
}
}
}
class ImmutableSingleSelectableListWithIcon<V>(
values: List<V>,
private val renderValue: ColoredListCellRenderer<V>.(V) -> Unit,
emptyMessage: String? = null,
private val onValueSelected: (V?) -> Unit = {}
) : AbstractSingleSelectableListWithIcon<V>(values) {
override fun ColoredListCellRenderer<V>.render(value: V) = renderValue(value)
override fun onSelected(value: V?) = onValueSelected(value)
init {
emptyMessage?.let { text ->
emptyText.text = text
}
}
}
@@ -0,0 +1,18 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import java.awt.BorderLayout
import javax.swing.JComponent
abstract class SubStep(
val valuesReadingContext: ValuesReadingContext
) : DynamicComponent(valuesReadingContext) {
protected abstract fun buildContent(): JComponent
final override val component: JComponent by lazy(LazyThreadSafetyMode.NONE) {
panel {
add(buildContent(), BorderLayout.CENTER)
}
}
}
@@ -0,0 +1,7 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
abstract class WizardStepComponent(
valuesReadingContext: ValuesReadingContext
) : DynamicComponent(valuesReadingContext)
@@ -0,0 +1,120 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components
import com.intellij.openapi.ui.ComboBox
import com.intellij.ui.ColoredListCellRenderer
import com.intellij.ui.SimpleTextAttributes
import com.intellij.ui.layout.selectedValueIs
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingValidator
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settingValidator
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Component
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import java.awt.BorderLayout
import javax.swing.JComponent
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ValidationIndicator
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.awt.event.ItemEvent
import javax.swing.DefaultComboBoxModel
import javax.swing.Icon
import javax.swing.JList
class DropDownComponent<T : DisplayableSettingItem>(
private val valuesReadingContext: ValuesReadingContext,
initialValues: List<T> = emptyList(),
labelText: String? = null,
private val validator: SettingValidator<T> = settingValidator { ValidationResult.OK },
private val iconProvider: (T) -> Icon? = { null },
private val onAnyValueUpdate: (T) -> Unit = {}
) : Component() {
@Suppress("UNCHECKED_CAST")
private val validationIndicator = ValidationIndicator(defaultText = labelText, showText = true)
private var allowFireEventWhenValueUpdated: Boolean = true
private fun withoutActionFiring(action: () -> Unit) {
val initialAllowFireEventWhenValueUpdated = allowFireEventWhenValueUpdated
allowFireEventWhenValueUpdated = false
action()
allowFireEventWhenValueUpdated = initialAllowFireEventWhenValueUpdated
}
@Suppress("UNCHECKED_CAST")
private val comboBox = ComboBox<T>(initialValues.toTypedArray<DisplayableSettingItem>() as Array<T>).apply {
renderer = object : ColoredListCellRenderer<T>() {
override fun customizeCellRenderer(
list: JList<out T>,
value: T?,
index: Int,
selected: Boolean,
hasFocus: Boolean
) {
if (value == null) return
icon = iconProvider(value)
append(value.text)
value.greyText?.let {
append(" $it", SimpleTextAttributes.GRAYED_ATTRIBUTES)
}
if (!this@apply.selectedValueIs(value).invoke()) {
validator.validate(valuesReadingContext, value)
.safeAs<ValidationResult.ValidationError>()
?.messages
?.firstOrNull()
?.let { error ->
append(" $error.", SimpleTextAttributes.ERROR_ATTRIBUTES)
}
}
}
}
addItemListener { e ->
if (e?.stateChange == ItemEvent.SELECTED) {
value = e.item as T
}
}
}
fun updateValues(newValues: List<T>) = withoutActionFiring {
val oldValue = comboBox.selectedItem
@Suppress("UNCHECKED_CAST")
comboBox.model = DefaultComboBoxModel(newValues.toTypedArray<DisplayableSettingItem>() as Array<T>)
if (newValues.isNotEmpty() && oldValue !in newValues) {
value = newValues.first()
}
}
override val component: JComponent = panel {
add(validationIndicator, BorderLayout.NORTH)
add(comboBox, BorderLayout.CENTER)
}
@Suppress("UNCHECKED_CAST")
var value: T
get() = comboBox.selectedItem as T
set(value) {
comboBox.selectedItem = value
fireValueChanged(value)
}
private fun fireValueChanged(newValue: T) {
validate(newValue)
if (allowFireEventWhenValueUpdated) {
onAnyValueUpdate(newValue)
}
}
init {
initialValues.firstOrNull()?.let { first ->
value = first
validate(first)
}
}
fun validate(value: T = this.value) {
validationIndicator.validationState = validator.validate(valuesReadingContext, value)
}
}
@@ -0,0 +1,65 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.ui.TextBrowseFolderListener
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import org.jetbrains.kotlin.tools.projectWizard.core.entity.StringValidator
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Component
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ValidationIndicator
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.textField
import java.awt.BorderLayout
import javax.swing.JComponent
class PathFieldComponent(
private val valuesReadingContext: ValuesReadingContext,
private val initialText: String = "",
labelText: String? = "",
private val validator: StringValidator = org.jetbrains.kotlin.tools.projectWizard.core.entity.settingValidator { ValidationResult.OK },
private val onSuccessValueUpdate: (String) -> Unit = {},
private val onAnyValueUpdate: (String) -> Unit = {}
) : Component() {
private val validationIndicator = ValidationIndicator(defaultText = labelText, showText = true)
private val pathField = TextFieldWithBrowseButton(
textField(initialText, this::fireValueChanged)
).apply {
addBrowseFolderListener(
TextBrowseFolderListener(
FileChooserDescriptorFactory.createSingleFolderDescriptor(),
null
)
)
}
override val component: JComponent = panel {
add(validationIndicator, BorderLayout.NORTH)
add(pathField, BorderLayout.CENTER)
}
var value: String
get() = pathField.text
set(value) {
pathField.text = value
fireValueChanged(value)
}
private fun fireValueChanged(text: String) {
validate(text)
if (validationIndicator.validationState == ValidationResult.OK) {
onSuccessValueUpdate(text)
}
onAnyValueUpdate(text)
}
override fun onInit() {
super.onInit()
validate(initialText)
}
private fun validate(text: String) {
validationIndicator.validationState = validator.validate(valuesReadingContext, text)
}
}
@@ -0,0 +1,54 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components
import org.jetbrains.kotlin.tools.projectWizard.core.entity.StringValidator
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settingValidator
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Component
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ValidationIndicator
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.textField
import java.awt.BorderLayout
import javax.swing.JComponent
class TextFieldComponent(
private val valuesReadingContext: ValuesReadingContext,
private val initialText: String = "",
labelText: String? = "",
private val validator: StringValidator = settingValidator { ValidationResult.OK },
private val onSuccessValueUpdate: (String) -> Unit = {},
private val onAnyValueUpdate: (String) -> Unit = {}
) : Component() {
private val validationIndicator = ValidationIndicator(defaultText = labelText, showText = true)
private val textField = textField(initialText, this::fireValueChanged)
override val component: JComponent = panel {
add(validationIndicator, BorderLayout.NORTH)
add(textField, BorderLayout.CENTER)
}
var value: String
get() = textField.text
set(value) {
textField.text = value
fireValueChanged(value)
}
private fun fireValueChanged(text: String) {
validate(text)
if (validationIndicator.validationState == ValidationResult.OK) {
onSuccessValueUpdate(text)
}
onAnyValueUpdate(text)
}
override fun onInit() {
super.onInit()
validate(initialText)
}
private fun validate(text: String) {
validationIndicator.validationState = validator.validate(valuesReadingContext, text)
}
}
@@ -0,0 +1,56 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.firstStep
import com.intellij.icons.AllIcons
import icons.GradleIcons
import icons.MavenIcons
import org.jetbrains.kotlin.tools.projectWizard.core.entity.DropDownSettingType
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.BuildSystemType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components.DropDownComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ValidationIndicator
import java.awt.BorderLayout
class BuildSystemTypeSettingComponent(
valuesReadingContext: ValuesReadingContext
) : SettingComponent<BuildSystemType, DropDownSettingType<BuildSystemType>>(
BuildSystemPlugin::type.reference,
valuesReadingContext
) {
override val validationIndicator: ValidationIndicator? = null
private val listComponent = DropDownComponent(
valuesReadingContext,
setting.type.values,
labelText = "Build System",
validator = setting.validator,
iconProvider = BuildSystemType::icon,
onAnyValueUpdate = { value = it }
).asSubComponent()
override val component = panel {
add(listComponent.component, BorderLayout.CENTER)
}
override fun onValueUpdated(reference: SettingReference<*, *>?) {
super.onValueUpdated(reference)
if (reference == KotlinPlugin::projectKind.reference) {
listComponent.component.updateUI()
}
listComponent.validate()
}
}
@Suppress("DEPRECATION")
private val BuildSystemType.icon
get() = when (this) {
BuildSystemType.GradleKotlinDsl -> GradleIcons.Gradle
BuildSystemType.GradleGroovyDsl -> GradleIcons.Gradle
BuildSystemType.Maven -> MavenIcons.MavenLogo
BuildSystemType.Jps -> AllIcons.Nodes.Module
}
@@ -0,0 +1,78 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.firstStep
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference
import org.jetbrains.kotlin.tools.projectWizard.plugins.projectTemplates.ProjectTemplatesPlugin
import org.jetbrains.kotlin.tools.projectWizard.projectTemplates.ProjectTemplate
import org.jetbrains.kotlin.tools.projectWizard.wizard.IdeWizard
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.*
import java.awt.BorderLayout
import javax.swing.JComponent
class FirstWizardStepComponent(wizard: IdeWizard) : WizardStepComponent(wizard.valuesReadingContext) {
private val buildSystemSubStep = BuildSystemSubStep(wizard.valuesReadingContext).asSubComponent()
private val templatesSubStep = TemplatesSubStep(wizard.valuesReadingContext).asSubComponent()
override val component: JComponent = panel {
add(templatesSubStep.component, BorderLayout.CENTER)
add(buildSystemSubStep.component, BorderLayout.SOUTH)
}
}
class BuildSystemSubStep(valuesReadingContext: ValuesReadingContext) :
SubStep(valuesReadingContext) {
private val buildSystemSetting = BuildSystemTypeSettingComponent(valuesReadingContext).asSubComponent()
override fun buildContent(): JComponent = panel {
bordered(needBottomEmptyBorder = false)
add(buildSystemSetting.component, BorderLayout.CENTER)
}
}
class TemplatesSubStep(valuesReadingContext: ValuesReadingContext) :
SubStep(valuesReadingContext) {
private val projectTemplateSettingComponent =
ProjectTemplateSettingComponent(valuesReadingContext) { projectTemplate ->
templateDescriptionComponent.setTemplate(projectTemplate)
}.asSubComponent()
private val templateDescriptionComponent = TemplateDescriptionComponent().asSubComponent()
override fun buildContent(): JComponent = panel {
add(projectTemplateSettingComponent.component, BorderLayout.CENTER)
add(templateDescriptionComponent.component, BorderLayout.SOUTH)
}
override fun onInit() {
super.onInit()
applySelectedTemplate()
}
private fun applySelectedTemplate() {
projectTemplateSettingComponent.value?.setsValues?.forEach { (setting, value) ->
// TODO do not use settingContext directly
context.settingContext[setting] = value
}
}
override fun onValueUpdated(reference: SettingReference<*, *>?) {
super.onValueUpdated(reference)
if (reference == ProjectTemplatesPlugin::template.reference) {
applySelectedTemplate()
}
}
}
class TemplateDescriptionComponent : Component() {
private val descriptionPanel = DescriptionPanel()
fun setTemplate(template: ProjectTemplate) {
descriptionPanel.updateText(template.htmlDescription)
}
override val component: JComponent = panel {
bordered()
add(descriptionPanel, BorderLayout.CENTER)
}
}
@@ -0,0 +1,63 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.firstStep
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.tools.projectWizard.core.entity.DropDownSettingType
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference
import org.jetbrains.kotlin.tools.projectWizard.plugins.projectTemplates.ProjectTemplatesPlugin
import org.jetbrains.kotlin.tools.projectWizard.projectTemplates.ProjectTemplate
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.ImmutableSingleSelectableListWithIcon
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.UiConstants
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.bordered
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ValidationIndicator
import java.awt.BorderLayout
import javax.swing.BorderFactory
import javax.swing.JComponent
class ProjectTemplateSettingComponent(
valuesReadingContext: ValuesReadingContext,
private val onSelected: (ProjectTemplate) -> Unit
) : SettingComponent<ProjectTemplate, DropDownSettingType<ProjectTemplate>>(
ProjectTemplatesPlugin::template.reference,
valuesReadingContext
) {
override val validationIndicator: ValidationIndicator? get() = null
private val list = ImmutableSingleSelectableListWithIcon(
setting.type.values,
renderValue = { value ->
icon = KotlinIcons.SMALL_LOGO
append(value.title)
},
onValueSelected = {
onValueSelected(it!!)
}
).apply {
border = BorderFactory.createEmptyBorder(
UiConstants.GAP_BORDER_SIZE,
UiConstants.GAP_BORDER_SIZE,
UiConstants.GAP_BORDER_SIZE,
UiConstants.GAP_BORDER_SIZE
)
}
private fun onValueSelected(selected: ProjectTemplate) {
value = selected
onSelected(selected)
}
override val component: JComponent = panel {
bordered(needTopEmptyBorder = false, needInnerEmptyBorder = false)
add(list, BorderLayout.CENTER)
}
override fun onInit() {
super.onInit()
if (setting.type.values.isNotEmpty()) {
list.selectedIndex = 0
onValueSelected(setting.type.values.first())
}
}
}
@@ -0,0 +1,85 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep
import com.intellij.ui.components.panels.VerticalLayout
import org.jetbrains.kotlin.tools.projectWizard.core.entity.StringValidators
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.ModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.configuratorSettings
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.*
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components.DropDownComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components.TextFieldComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingsList
import javax.swing.BorderFactory
import javax.swing.JComponent
class ModuleSettingsComponent(valuesReadingContext: ValuesReadingContext) : DynamicComponent(valuesReadingContext) {
private val validateModuleName =
StringValidators.shouldNotBeBlank("Module name") and
StringValidators.shouldBeValidIdentifier("Module Name")
private val moduleConfigurator = DropDownComponent<ModuleConfigurator>(
valuesReadingContext,
iconProvider = { it.icon },
labelText = "Consider as"
) { value ->
module?.configurator = value
}
private val moduleConfiguratorSettingsList = SettingsList(emptyList(), valuesReadingContext)
private val nameField = TextFieldComponent(
valuesReadingContext,
labelText = "Name",
onAnyValueUpdate = { value ->
module?.name = value
eventManager.fireListeners(null)
},
validator = validateModuleName
).asSubComponent()
override val component: JComponent = panel(VerticalLayout(5)) {
border = BorderFactory.createEmptyBorder(
UiConstants.GAP_BORDER_SIZE,
UiConstants.GAP_BORDER_SIZE,
UiConstants.GAP_BORDER_SIZE,
UiConstants.GAP_BORDER_SIZE
)
add(nameField.component)
add(moduleConfigurator.component)
add(moduleConfiguratorSettingsList.component)
}
var module: Module? = null
set(value) {
field = value
if (value != null) {
updateModule(value)
}
}
private fun updateModule(module: Module) {
nameField.value = module.name
val newValues = ModuleConfigurator.BY_MODULE_KIND.getValue(module.configurator.moduleKind)
.filter { configurator ->
if (configurator.moduleKind == ModuleKind.target)
configurator.moduleType == module.configurator.moduleType
else false
}
nameField.component.isVisible = module.kind != ModuleKind.target
|| module.configurator.moduleType != ModuleType.common
moduleConfiguratorSettingsList.setSettings(module.configuratorSettings)
moduleConfigurator.apply {
updateValues(newValues)
component.isVisible = newValues.size > 1
value = module.configurator
component.updateUI()
}
}
}
@@ -0,0 +1,23 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep
import com.intellij.util.ui.StatusText
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Component
import java.awt.Graphics
import javax.swing.JPanel
class NothingSelectedComponent : Component() {
override val component: JPanel = object : JPanel() {
override fun paint(g: Graphics?) {
super.paint(g)
statusText.paint(this, g)
}
}
private val statusText = object : StatusText(component) {
override fun isStatusVisible() = true
init {
appendText("Neither Module nor Sourceset is selected")
}
}
}
@@ -0,0 +1,80 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Sourceset
import org.jetbrains.kotlin.tools.projectWizard.wizard.IdeWizard
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.*
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor.ModulesEditorComponent
import java.awt.BorderLayout
import javax.swing.BorderFactory
import javax.swing.JComponent
class SecondStepWizardComponent(wizard: IdeWizard) : WizardStepComponent(wizard.valuesReadingContext) {
private val moduleEditorSubStep =
ModulesEditorSubStep(wizard.valuesReadingContext, ::onNodeSelected).asSubComponent()
private val templatesSubStep = ModuleSettingsSubStep(wizard).asSubComponent()
override val component = splitterFor(
moduleEditorSubStep.component,
templatesSubStep.component
)
private fun onNodeSelected(data: DisplayableSettingItem?) {
templatesSubStep.selectedNode = data
}
}
class ModulesEditorSubStep(
valuesReadingContext: ValuesReadingContext,
onNodeSelected: (data: DisplayableSettingItem?) -> Unit
) : SubStep(valuesReadingContext) {
private val moduleSettingComponent = ModulesEditorComponent(
valuesReadingContext,
onNodeSelected
).asSubComponent()
override fun buildContent(): JComponent = panel {
bordered(needInnerEmptyBorder = false, needLineBorder = false)
border = BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(0, 0, 0, UiConstants.GAP_BORDER_SIZE),
border
)
add(moduleSettingComponent.component, BorderLayout.CENTER)
}
}
class ModuleSettingsSubStep(wizard: IdeWizard) : SubStep(wizard.valuesReadingContext) {
private val sourcesetSettingsComponent = SourcesetSettingsComponent(wizard.valuesReadingContext).asSubComponent()
private val moduleSettingsComponent = ModuleSettingsComponent(valuesReadingContext).asSubComponent()
private val nothingSelectedComponent = NothingSelectedComponent().asSubComponent()
private val panel = panel {
bordered()
add(nothingSelectedComponent.component, BorderLayout.CENTER)
}
var selectedNode: DisplayableSettingItem? = null
set(value) {
field = value
sourcesetSettingsComponent.sourceset = value as? Sourceset
moduleSettingsComponent.module = value as? Module
changeComponent()
}
private fun changeComponent() {
panel.removeAll()
val component = when (selectedNode) {
is Sourceset -> sourcesetSettingsComponent
is Module -> moduleSettingsComponent
else -> nothingSelectedComponent
}
panel.add(component.component, BorderLayout.CENTER)
panel.updateUI()
}
override fun buildContent() = panel
}
@@ -0,0 +1,137 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep
import com.intellij.openapi.actionSystem.ActionToolbarPosition
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.ui.ColoredListCellRenderer
import com.intellij.ui.SimpleTextAttributes
import com.intellij.ui.ToolbarDecorator
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.AndroidSinglePlatformModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.IOSSinglePlatformModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.withAllSubModules
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.*
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.awt.BorderLayout
import java.awt.Dimension
import javax.swing.JComponent
class SourcesetDependenciesSettingsComponent(
valuesReadingContext: ValuesReadingContext
) : DynamicComponent(valuesReadingContext) {
private val sourcesetDependenciesList = SourcesetDependenciesList()
private val toolbarDecorator: ToolbarDecorator = ToolbarDecorator.createDecorator(sourcesetDependenciesList).apply {
setToolbarPosition(ActionToolbarPosition.RIGHT)
setAddAction {
val dialog = ChooseModuleDialog(allModulesExcludingCurrent, component)
if (dialog.showAndGet()) {
dialog.selectedModule?.let(sourcesetDependenciesList::addDependency)
}
}
setRemoveAction {
sourcesetDependenciesList.removeSelected()
}
setMoveDownAction(null)
setMoveUpAction(null)
}
var sourceset: Sourceset? = null
set(value) {
field = value
sourcesetDependenciesList.sourceset = value
}
private val allModulesExcludingCurrent: List<Module>
get() {
val currentModule = sourceset?.parent
return KotlinPlugin::modules.value!!
.withAllSubModules()
.filter { module ->
if (sourceset in module.sourcesets) return@filter false
if (currentModule?.kind == ModuleKind.target && currentModule in module.subModules)
return@filter false
if (module.configurator == AndroidSinglePlatformModuleConfigurator
|| module.configurator == IOSSinglePlatformModuleConfigurator
) return@filter false
true
}
}
override val component = panel {
add(toolbarDecorator.createPanelWithPopupHandler(sourcesetDependenciesList), BorderLayout.CENTER)
}
}
private class ChooseModuleDialog(modules: List<Module>, parent: JComponent) : DialogWrapper(parent, false) {
private val list = ImmutableSingleSelectableListWithIcon(
values = modules,
emptyMessage = "There are no dependencies to add",
renderValue = { value ->
renderModule(value)
}
).bordered(needTopEmptyBorder = false, needBottomEmptyBorder = false)
override fun createCenterPanel() = panel {
preferredSize = Dimension(preferredSize.width, 300)
add(
label("Please select the modules to add as dependencies:")
.bordered(
needLineBorder = false,
needInnerEmptyBorder = false,
needTopEmptyBorder = false
),
BorderLayout.NORTH
)
add(list, BorderLayout.CENTER)
}
val selectedModule: Module?
get() = list.selectedValue
init {
title = "Choose Modules"
init()
}
}
private class SourcesetDependenciesList : AbstractSingleSelectableListWithIcon<Module>() {
override fun ColoredListCellRenderer<Module>.render(value: Module) {
renderModule(value)
}
var sourceset: Sourceset? = null
set(value) {
field = value
updateValues(
value?.dependencies?.mapNotNull { it.safeAs<ModuleBasedSourcesetDependency>()?.module } ?: return
)
updateUI()
}
fun addDependency(dependency: Module) {
model.addElement(dependency)
sourceset?.let { it.dependencies += ModuleBasedSourcesetDependency(dependency) }
}
fun removeSelected() {
val index = selectedIndex
model.removeElementAt(selectedIndex)
sourceset?.let { it.dependencies = it.dependencies.toMutableList().also { it.removeAt(index) } }
if (model.size() > 0) {
selectedIndex = index.coerceAtMost(model.size - 1)
}
}
}
private fun ColoredListCellRenderer<Module>.renderModule(module: Module) {
append(module.path.asString())
append(" ")
append("(${module.greyText})", SimpleTextAttributes.GRAYED_ATTRIBUTES)
icon = module.icon
}
@@ -0,0 +1,22 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep
import com.intellij.ui.components.JBTabbedPane
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Sourceset
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent
class SourcesetSettingsComponent(valuesReadingContext: ValuesReadingContext) : DynamicComponent(valuesReadingContext) {
private val templatesComponent = TemplatesComponent(valuesReadingContext).asSubComponent()
private val dependenciesComponent = SourcesetDependenciesSettingsComponent(valuesReadingContext).asSubComponent()
override val component = JBTabbedPane().apply {
add("Template", templatesComponent.component)
add("Dependencies", dependenciesComponent.component)
}
var sourceset: Sourceset? = null
set(value) {
field = value
dependenciesComponent.sourceset = value
templatesComponent.sourceset = value
}
}
@@ -0,0 +1,264 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep
import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.ui.Messages
import com.intellij.openapi.ui.ex.MessagesEx
import com.intellij.ui.JBSplitter
import com.intellij.ui.SimpleTextAttributes
import com.intellij.util.ui.StatusText
import com.intellij.util.ui.UIUtil
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Sourceset
import org.jetbrains.kotlin.tools.projectWizard.templates.Template
import org.jetbrains.kotlin.tools.projectWizard.templates.settings
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.*
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Component
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingsList
import java.awt.*
import javax.swing.JComponent
import javax.swing.JPanel
class TemplatesComponent(valuesReadingContext: ValuesReadingContext) : DynamicComponent(valuesReadingContext) {
private val chooseTemplateComponent: ChooseTemplateComponent =
ChooseTemplateComponent(valuesReadingContext) { template ->
sourceset?.template = template
switchState(template)
}
private val templateSettingsComponent = TemplateSettingsComponent(valuesReadingContext) {
if (MessagesEx.showOkCancelDialog(
component,
"Do you want to remove selected template from module",
"Remove selected template",
"Remove",
"Cancel",
null
) == Messages.OK
) {
switchState(null)
}
}
private fun switchState(selectedTemplate: Template?) {
panel.removeAll()
when (selectedTemplate) {
null -> panel.add(chooseTemplateComponent.component, BorderLayout.CENTER)
else -> {
if (sourceset != null) {
templateSettingsComponent.setTemplate(sourceset!!, selectedTemplate)
}
panel.add(templateSettingsComponent.component, BorderLayout.CENTER)
}
}
panel.updateUI()
}
val panel = panel {
add(chooseTemplateComponent.component, BorderLayout.CENTER)
}
override val component: JComponent = panel
var sourceset: Sourceset? = null
set(value) {
field = value
switchState(value?.template)
chooseTemplateComponent.selectedModule = value
}
}
class ChooseTemplateComponent(
private val valuesReadingContext: ValuesReadingContext,
private val onTemplateChosen: (Template) -> Unit
) : DynamicComponent(valuesReadingContext) {
private enum class State(val text: String) {
MODULE_SELECTED_AND_TEMPLATES_AVAILABLE("You can configure a template for selected module"),
MODULE_SELECTED_AND_NO_TEMPLATES_AVAILABLE("No templates available for selected module"),
NO_MODULE_SELECTED("Please select a module to configure")
}
override val component: JPanel = object : JPanel() {
override fun paint(g: Graphics?) {
super.paint(g)
statusText.paint(this, g)
}
}
private val statusText = object : StatusText(component) {
override fun isStatusVisible() = true
}
private val allTemplates
get() = with(valuesReadingContext) {
TemplatesPlugin::templates.propertyValue
}
var selectedModule: Sourceset? = null
set(value) {
field = value
onStateUpdated()
}
init {
onStateUpdated()
}
private val availableTemplates
get() = allTemplates.values.filter { template ->
selectedModule!!.containingModuleType in template.moduleTypes
&& selectedModule!!.sourcesetType in template.sourcesetTypes
}
private val state: State
get() = when {
selectedModule == null -> State.NO_MODULE_SELECTED
availableTemplates.isEmpty() -> State.MODULE_SELECTED_AND_NO_TEMPLATES_AVAILABLE
else -> State.MODULE_SELECTED_AND_TEMPLATES_AVAILABLE
}
private fun onStateUpdated() {
statusText.clear()
statusText.appendText(state.text)
if (state == State.MODULE_SELECTED_AND_TEMPLATES_AVAILABLE) {
statusText.appendSecondaryText("Configure", SimpleTextAttributes.LINK_ATTRIBUTES) {
showDialog()
}
}
}
private fun showDialog() {
val availableTemplates = availableTemplates
if (availableTemplates.isEmpty()) {
MessagesEx.error(null, "No templates available for the selected module")
} else {
val dialog = TemplateListDialog(availableTemplates, component)
if (dialog.showAndGet()) {
onTemplateChosen(dialog.selectedTemplate ?: return)
}
}
}
}
private class TemplateListDialog(
values: List<Template>,
parent: JComponent
) : DialogWrapper(parent, false) {
private val templateDescriptionComponent = TemplateDescriptionComponent(
needRemoveButton = false
).apply {
component.bordered(needTopEmptyBorder = false, needBottomEmptyBorder = false)
}
private val templatesList = ImmutableSingleSelectableListWithIcon(
values = values,
renderValue = { value ->
icon = when {
value.moduleTypes.size == 1 -> value.moduleTypes.single().icon
else -> KotlinIcons.MPP
}
append(value.title)
},
onValueSelected = { templateDescriptor ->
templateDescriptionComponent.updateSelectedTemplate(templateDescriptor)
}
).bordered(needTopEmptyBorder = false, needBottomEmptyBorder = false)
val selectedTemplate: Template?
get() = templatesList.selectedValue
init {
title = "Choose a template to configure"
init()
if (values.isNotEmpty()) {
templateDescriptionComponent.updateSelectedTemplate(values.first())
}
}
override fun createCenterPanel() = panel(BorderLayout()) {
preferredSize = Dimension(700, 300)
val splitter = JBSplitter(false, .3f).apply {
firstComponent = templatesList
secondComponent = templateDescriptionComponent.component
orientation = false
}
add(splitter, BorderLayout.CENTER)
}
}
class TemplateDescriptionComponent(
needRemoveButton: Boolean = false,
private val nonDefaultBackgroundColor: Color? = null,
onRemoveButtonClicked: () -> Unit = {}
) : Component() {
private val removeButton = if (needRemoveButton) {
hyperlinkLabel("Remove template", onClick = onRemoveButtonClicked)
} else null
private val titleLabel = label("", bold = true) {
font = UIUtil.getListFont().let { font ->
Font(font.family, Font.BOLD, (font.size * 1.3).toInt())
}
}
private val title = panel {
nonDefaultBackgroundColor?.let { background = it }
add(titleLabel, BorderLayout.CENTER)
removeButton?.let { add(it, BorderLayout.EAST) }
}
private val descriptionLabel = DescriptionPanel()
override val component: JComponent by lazy(LazyThreadSafetyMode.NONE) {
panel {
nonDefaultBackgroundColor?.let { background = it }
add(title, BorderLayout.NORTH)
add(descriptionLabel, BorderLayout.CENTER)
}
}
private fun setComponentsVisibility(isVisible: Boolean) {
titleLabel.isVisible = isVisible
descriptionLabel.isVisible = isVisible
}
fun updateSelectedTemplate(template: Template?) {
setComponentsVisibility(template != null)
if (template != null) {
titleLabel.text = template.title
descriptionLabel.updateText(template.htmlDescription)
}
}
}
private class TemplateSettingsComponent(
valuesReadingContext: ValuesReadingContext,
removeTemplate: () -> Unit
) : DynamicComponent(valuesReadingContext) {
private val templateDescriptionComponent = TemplateDescriptionComponent(
needRemoveButton = true,
nonDefaultBackgroundColor = UIUtil.getEditorPaneBackground(),
onRemoveButtonClicked = removeTemplate
).apply {
component.bordered(needTopEmptyBorder = false, needBottomEmptyBorder = false)
}
private val settings = SettingsList(emptyList(), valuesReadingContext).apply {
component.bordered()
}
fun setTemplate(sourceset: Sourceset, selectedTemplate: Template) {
settings.setSettings(selectedTemplate.settings(sourceset))
templateDescriptionComponent.updateSelectedTemplate(selectedTemplate)
}
override val component: JComponent = splitterFor(
templateDescriptionComponent.component,
settings.component,
vertical = true
).apply {
(this as JBSplitter).proportion = .7f
}
}
@@ -0,0 +1,118 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor
import com.intellij.openapi.ui.popup.ListPopup
import com.intellij.openapi.ui.popup.PopupStep
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
import com.intellij.ui.popup.PopupFactoryImpl
import org.jetbrains.kotlin.tools.projectWizard.core.buildList
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.*
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.settings.fullText
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.asHtml
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.htmlText
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.icon
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import javax.swing.Icon
class CreateModuleOrTargetPopup private constructor(
private val target: Module?,
private val allowMultiplatform: Boolean,
private val allowAndroid: Boolean,
private val allowIos: Boolean,
private val createTarget: (TargetConfigurator) -> Unit,
private val createModule: (ModuleConfigurator) -> Unit
) {
private fun TargetConfigurator.needToShow(): Boolean {
val allTargets = target?.subModules ?: return false
return canCoexistsWith(allTargets.map { it.configurator as TargetConfigurator })
}
private fun DisplayableSettingItem.needToShow(): Boolean = when (this) {
is TargetConfigurator -> needToShow()
is TargetConfiguratorGroupWithSubItems -> subItems.any { it.needToShow() }
else -> false
}
private inner class ChooseModuleOrMppModuleStep : BaseListPopupStep<ModuleConfigurator>(
"Module Type",
buildList {
if (allowMultiplatform) +MppModuleConfigurator
+JvmSinglePlatformModuleConfigurator
if (allowAndroid) +AndroidSinglePlatformModuleConfigurator
//todo ios support
//if (allowIos) +IOSSinglePlatformModuleConfigurator
}
) {
override fun getIconFor(value: ModuleConfigurator): Icon = value.icon
override fun getTextFor(value: ModuleConfigurator): String = value.htmlText
override fun onChosen(selectedValue: ModuleConfigurator?, finalChoice: Boolean): PopupStep<*>? =
when (selectedValue) {
null -> PopupStep.FINAL_CHOICE
else -> {
createModule(selectedValue)
PopupStep.FINAL_CHOICE
}
}
}
private inner class ChooseTargetTypeStep(
targetConfiguratorGroup: TargetConfiguratorGroupWithSubItems,
showTitle: Boolean
) : BaseListPopupStep<DisplayableSettingItem>(
"Target".takeIf { showTitle },
targetConfiguratorGroup.subItems.filter { it.needToShow() }
) {
override fun getIconFor(value: DisplayableSettingItem): Icon? = when (value) {
is DisplayableTargetConfiguratorGroup -> value.moduleType.icon
is ModuleConfigurator -> value.moduleType.icon
else -> null
}
override fun getTextFor(value: DisplayableSettingItem): String = value.htmlText
override fun onChosen(selectedValue: DisplayableSettingItem?, finalChoice: Boolean): PopupStep<*>? {
when {
finalChoice && selectedValue is TargetConfigurator -> createTarget(selectedValue)
selectedValue is TargetConfiguratorGroupWithSubItems ->
return ChooseTargetTypeStep(selectedValue, showTitle = false)
}
return PopupStep.FINAL_CHOICE
}
}
private fun create(): ListPopup? = when (target?.kind) {
ModuleKind.target -> null
ModuleKind.multiplatform -> ChooseTargetTypeStep(TargetConfigurationGroups.FIRST, showTitle = true)
else -> when {
allowMultiplatform || allowAndroid || allowIos -> ChooseModuleOrMppModuleStep()
else -> {
createModule(JvmSinglePlatformModuleConfigurator)
null
}
}
}?.let { PopupFactoryImpl.getInstance().createListPopup(it) }
companion object {
fun create(
target: Module?,
allowMultiplatform: Boolean,
allowAndroid: Boolean,
allowIos: Boolean,
createTarget: (TargetConfigurator) -> Unit,
createModule: (ModuleConfigurator) -> Unit
) = CreateModuleOrTargetPopup(
target = target,
allowMultiplatform = allowMultiplatform,
allowAndroid = allowAndroid,
allowIos = allowIos,
createTarget = createTarget,
createModule = createModule
).create()
}
}
@@ -0,0 +1,67 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor
import com.intellij.ui.JBColor
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ListSettingType
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ProjectKind
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ValidationIndicator
import java.awt.BorderLayout
import javax.swing.BorderFactory
import javax.swing.JComponent
class ModulesEditorComponent(
valuesReadingContext: ValuesReadingContext,
oneEntrySelected: (data: DisplayableSettingItem?) -> Unit
) : SettingComponent<List<Module>, ListSettingType<Module>>(KotlinPlugin::modules.reference, valuesReadingContext) {
private val tree: ModulesEditorTree =
ModulesEditorTree(
onSelected = { oneEntrySelected(it) },
addModule = { component ->
val isMppProject = KotlinPlugin::projectKind.value == ProjectKind.Multiplatform
moduleCreator.create(
target = null, // The empty tree case
allowMultiplatform = isMppProject,
allowAndroid = isMppProject,
allowIos = isMppProject,
allModules = value ?: emptyList(),
createModule = { model.add(it) }
)?.showInCenterOf(component)
}
)
private val model = TargetsModel(tree, ::value)
override fun onInit() {
super.onInit()
model.update()
}
private val moduleCreator = NewModuleCreator()
private val toolbarDecorator = ModulesEditorToolbarDecorator(
tree = tree,
moduleCreator = moduleCreator,
model = model,
getModules = { value ?: emptyList() },
isMultiplatformProject = { KotlinPlugin::projectKind.value == ProjectKind.Multiplatform }
)
override val component: JComponent by lazy(LazyThreadSafetyMode.NONE) {
panel {
border = BorderFactory.createLineBorder(JBColor.border())
add(toolbarDecorator.createToolPanel(), BorderLayout.CENTER)
add(validationIndicator, BorderLayout.SOUTH)
}
}
override val validationIndicator = ValidationIndicator(showText = true).apply {
background = tree.background
}
}
@@ -0,0 +1,109 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.ActionToolbarPosition
import com.intellij.openapi.ui.Messages
import com.intellij.ui.ToolbarDecorator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.AndroidSinglePlatformModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.IOSSinglePlatformModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.MppModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.withAllSubModules
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.*
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.createPanelWithPopupHandler
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import javax.swing.JComponent
class ModulesEditorToolbarDecorator(
private val tree: ModulesEditorTree,
private val moduleCreator: NewModuleCreator,
private val model: TargetsModel,
private val getModules: () -> List<Module>,
private val isMultiplatformProject: () -> Boolean
) {
private val toolbarDecorator: ToolbarDecorator = ToolbarDecorator.createDecorator(tree).apply {
setToolbarPosition(ActionToolbarPosition.TOP)
setAddAction { button ->
val allModules = getModules().withAllSubModules(includeSourcesets = false)
val target = tree.selectedSettingItem?.safeAs<Module>()
val isRootModule = target == null
val popup = moduleCreator.create(
target = tree.selectedSettingItem?.safeAs(),
allowMultiplatform = isRootModule
&& allModules.none { it.configurator == MppModuleConfigurator },
allowAndroid = isRootModule
&& isMultiplatformProject()
&& allModules.none { it.configurator == AndroidSinglePlatformModuleConfigurator },
allowIos = isRootModule
&& isMultiplatformProject()
&& allModules.none { it.configurator == IOSSinglePlatformModuleConfigurator },
allModules = allModules,
createModule = model::add
)
popup?.show(button.preferredPopupPoint!!)
}
setAddActionUpdater { event ->
event.presentation.apply {
isEnabled = when (val selected = tree.selectedSettingItem) {
is Module -> selected.configurator.canContainSubModules
is Sourceset -> false
null -> true
else -> false
}
text = "Add" + when (tree.selectedSettingItem?.safeAs<Module>()?.kind) {
ModuleKind.multiplatform -> " Target"
ModuleKind.singleplatform -> " Module"
ModuleKind.target -> ""
null -> ""
}
}
event.presentation.isEnabled
}
setRemoveAction {
val moduleKindText = selectedModuleKindText ?: "Module"
if (Messages.showOkCancelDialog(
tree,
buildString {
appendln("Do you want to remove selected $moduleKindText?")
if (tree.selectedSettingItem.safeAs<Module>()?.kind != ModuleKind.target) {
appendln("This will also remove all submodules.")
}
appendln()
appendln("This action cannot be undone.")
},
"Remove selected $moduleKindText?",
"Remove",
"Cancel",
AllIcons.General.QuestionDialog
) == Messages.OK
) {
model.removeSelected()
}
}
setRemoveActionUpdater { event ->
event.presentation.apply {
isEnabled = tree.selectedSettingItem is Module
text = "Remove" + selectedModuleKindText?.let { " $it" }.orEmpty()
}
event.presentation.isEnabled
}
setMoveDownAction(null)
setMoveUpAction(null)
}
private val selectedModuleKindText
get() = tree.selectedSettingItem.safeAs<Module>()?.kindText
fun createToolPanel(): JComponent = toolbarDecorator
.createPanelWithPopupHandler(tree)
.apply {
border = null
}
}
private val Module.kindText
get() = when (kind) {
ModuleKind.multiplatform -> "Module"
ModuleKind.singleplatform -> "Module"
ModuleKind.target -> "Target"
}
@@ -0,0 +1,91 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor
import com.intellij.icons.AllIcons
import com.intellij.ui.ColoredTreeCellRenderer
import com.intellij.ui.SimpleTextAttributes
import com.intellij.ui.treeStructure.Tree
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Sourceset
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.icon
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import javax.swing.JComponent
import javax.swing.JTree
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.DefaultTreeModel
import javax.swing.tree.TreeSelectionModel
class ModulesEditorTree(
private val onSelected: (DisplayableSettingItem?) -> Unit,
private val addModule: (JComponent) -> Unit
) : Tree(DefaultMutableTreeNode(PROJECT_USER_OBJECT)) {
val model: DefaultTreeModel get() = super.getModel() as DefaultTreeModel
@Suppress("ClassName")
object PROJECT_USER_OBJECT
fun reload() {
val openPaths = (0 until rowCount).mapNotNull { row ->
getPathForRow(row).takeIf { isExpanded(it) }
}
model.reload()
openPaths.forEach(::expandPath)
}
val selectedSettingItem
get() = selectedNode?.userObject?.safeAs<DisplayableSettingItem>()
val selectedNode
get() = getSelectedNodes(DefaultMutableTreeNode::class.java) {
it.userObject is DisplayableSettingItem
}.singleOrNull()
init {
getSelectionModel().selectionMode = TreeSelectionModel.SINGLE_TREE_SELECTION
setShowsRootHandles(true)
addTreeSelectionListener {
onSelected(selectedSettingItem)
}
emptyText.clear()
emptyText.appendText("No modules created")
emptyText.appendSecondaryText(
"Add a module to the project",
SimpleTextAttributes.LINK_PLAIN_ATTRIBUTES
) {
addModule(this)
}
setCellRenderer(object : ColoredTreeCellRenderer() {
override fun customizeCellRenderer(
tree: JTree,
value: Any?,
selected: Boolean,
expanded: Boolean,
leaf: Boolean,
row: Int,
hasFocus: Boolean
) {
if (value?.safeAs<DefaultMutableTreeNode>()?.userObject == PROJECT_USER_OBJECT) {
icon = AllIcons.Nodes.Project
append("Project")
return
}
val setting = (value as? DefaultMutableTreeNode)?.userObject as? DisplayableSettingItem
?: return
icon = when (setting) {
is Module -> setting.icon
is Sourceset -> AllIcons.Nodes.Module
else -> null
}
append(setting.text)
setting.greyText?.let { greyText ->
append(" ")
append("($greyText)", SimpleTextAttributes.GRAYED_ATTRIBUTES)
}
}
})
}
}
@@ -0,0 +1,75 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.TargetConfigurator
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.*
class NewModuleCreator {
private fun suggestName(name: String, modules: List<Module>): String {
val names = modules.map(Module::name).toSet()
if (name !in names) return name
var index = 1
while ("${name}_$index" in names) {
index++
}
return "${name}_$index"
}
private fun newTarget(
configurator: TargetConfigurator,
allTargets: List<Module>
): Module = MultiplatformTargetModule(
suggestName(
configurator.suggestedModuleName ?: configurator.moduleType.name,
allTargets
),
configurator,
SourcesetType.values().map { sourcesetType ->
Sourceset(
sourcesetType,
configurator.moduleType,
template = null,
dependencies = emptyList()
)
}
)
fun create(
target: Module?,
allowMultiplatform: Boolean,
allowAndroid: Boolean,
allowIos: Boolean,
allModules: List<Module>,
createModule: (Module) -> Unit
) = CreateModuleOrTargetPopup.create(
target = target,
allowMultiplatform = allowMultiplatform,
allowAndroid = allowAndroid,
allowIos = allowIos,
createTarget = { targetConfigurator ->
createModule(newTarget(targetConfigurator, target?.subModules.orEmpty()))
},
createModule = { configurator ->
val name = suggestName(configurator.suggestedModuleName ?: "module", allModules)
val sourcesets = when (configurator.moduleKind) {
ModuleKind.multiplatform -> emptyList()
else -> SourcesetType.values().map { sourcesetType ->
Sourceset(
sourcesetType,
ModuleType.jvm,
template = null,
dependencies = emptyList()
)
}
}
val createdModule = Module(
name,
configurator.moduleKind,
configurator,
sourcesets,
emptyList()
)
createModule(createdModule)
}
)
}
@@ -0,0 +1,86 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.ModuleKind
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.TreePath
import kotlin.reflect.KMutableProperty0
class TargetsModel(
private val tree: ModulesEditorTree,
private val value: KMutableProperty0<List<Module>?>
) {
private val root get() = tree.model.root as DefaultMutableTreeNode
private fun addToTheTree(module: Module, modifyValue: Boolean, parent: DefaultMutableTreeNode = root) {
val pathsToExpand = mutableListOf<TreePath>()
fun add(moduleToAdd: Module, parentToAdd: DefaultMutableTreeNode) {
DefaultMutableTreeNode(moduleToAdd).apply {
val userObject = parentToAdd.userObject
when {
userObject is Module && userObject.kind == ModuleKind.singleplatform -> {
val indexOfLastModule = parent.children()
.toList()
.indexOfLast {
it.safeAs<DefaultMutableTreeNode>()?.userObject is Module
}
if (indexOfLastModule == -1) parent.insert(this, 0)
else parent.insert(this, indexOfLastModule)
}
else -> parentToAdd.add(this)
}
pathsToExpand += TreePath(path)
moduleToAdd.subModules.forEach { subModule ->
add(subModule, this)
}
moduleToAdd.sourcesets.forEach { sourceset ->
val sourcesetNode = DefaultMutableTreeNode(sourceset)
add(sourcesetNode)
pathsToExpand += TreePath(sourcesetNode.path)
}
}
}
add(module, parent)
if (modifyValue) {
when (val parentModule = parent.userObject) {
ModulesEditorTree.PROJECT_USER_OBJECT -> value.set(value.get().orEmpty() + module)
is Module -> parentModule.subModules += module
}
}
tree.reload()
pathsToExpand.forEach(tree::expandPath)
}
fun add(module: Module) {
addToTheTree(module, modifyValue = true, parent = tree.selectedNode ?: root)
}
fun update() {
root.removeAllChildren()
value.get()?.forEach { addToTheTree(it, modifyValue = false) }
if (value.get()?.isEmpty() == true) {
tree.reload()
}
}
fun removeSelected() {
val selectedNode = tree.selectedNode?.takeIf { it.userObject is Module } ?: return
when (val parent = selectedNode.parent.safeAs<DefaultMutableTreeNode>()?.userObject) {
ModulesEditorTree.PROJECT_USER_OBJECT -> {
val index = selectedNode.parent.getIndex(selectedNode)
selectedNode.removeFromParent()
value.set(value.get()?.toMutableList().also { it?.removeAt(index) })
}
is Module -> {
parent.subModules = parent.subModules.filterNot { it === selectedNode.userObject }
selectedNode.removeFromParent()
}
}
tree.reload()
}
}
@@ -0,0 +1,200 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting
import com.intellij.openapi.ui.ComboBox
import com.intellij.ui.components.JBCheckBox
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components.DropDownComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components.PathFieldComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.components.TextFieldComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.label
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import java.awt.BorderLayout
import java.awt.event.ItemEvent
import java.nio.file.Path
import java.nio.file.Paths
import javax.swing.DefaultComboBoxModel
import javax.swing.JComponent
sealed class DefaultSettingComponent<V : Any, T : SettingType<V>>(
reference: SettingReference<V, T>,
valuesReadingContext: ValuesReadingContext
) : SettingComponent<V, T>(reference, valuesReadingContext) {
companion object {
@Suppress("UNCHECKED_CAST")
fun <V : Any, T : SettingType<V>> create(
setting: SettingReference<V, T>,
valuesReadingContext: ValuesReadingContext
): DefaultSettingComponent<V, T> = when (setting.type) {
VersionSettingType::class ->
VersionSettingComponent(
setting as SettingReference<Version, VersionSettingType>,
valuesReadingContext
) as DefaultSettingComponent<V, T>
BooleanSettingType::class ->
BooleanSettingComponent(
setting as SettingReference<Boolean, BooleanSettingType>,
valuesReadingContext
) as DefaultSettingComponent<V, T>
DropDownSettingType::class ->
DropdownSettingComponent(
setting as SettingReference<DisplayableSettingItem, DropDownSettingType<DisplayableSettingItem>>,
valuesReadingContext
) as DefaultSettingComponent<V, T>
StringSettingType::class ->
StringSettingComponent(
setting as SettingReference<String, StringSettingType>,
valuesReadingContext
) as DefaultSettingComponent<V, T>
PathSettingType::class ->
PathSettingComponent(
setting as SettingReference<Path, PathSettingType>,
valuesReadingContext
) as DefaultSettingComponent<V, T>
else -> TODO(setting.type.qualifiedName!!)
}
}
}
class VersionSettingComponent(
reference: SettingReference<Version, VersionSettingType>,
valuesReadingContext: ValuesReadingContext
) : DefaultSettingComponent<Version, VersionSettingType>(reference, valuesReadingContext) {
private val settingLabel = label(setting.title)
private val comboBox = ComboBox<Version>().apply {
addItemListener { e ->
if (e?.stateChange == ItemEvent.SELECTED) {
value = e.item as Version
}
}
}
override fun onInit() {
super.onInit()
val values = setting.defaultValue?.let(::listOf).orEmpty()
comboBox.model = DefaultComboBoxModel<Version>(values.toTypedArray())
if (values.isNotEmpty()) {
value = values.first()
}
}
override val validationIndicator = ValidationIndicator(showText = false)
override val component: JComponent by lazy(LazyThreadSafetyMode.NONE) {
panel {
add(
panel {
add(validationIndicator, BorderLayout.WEST)
add(settingLabel, BorderLayout.CENTER)
},
BorderLayout.WEST
)
add(comboBox, BorderLayout.CENTER)
}
}
}
class DropdownSettingComponent(
reference: SettingReference<DisplayableSettingItem, DropDownSettingType<DisplayableSettingItem>>,
valuesReadingContext: ValuesReadingContext
) : DefaultSettingComponent<DisplayableSettingItem, DropDownSettingType<DisplayableSettingItem>>(
reference,
valuesReadingContext
) {
private val dropDownComponent = DropDownComponent(
valuesReadingContext,
setting.type.values,
labelText = setting.title,
onAnyValueUpdate = { newValue ->
value = newValue
}
).asSubComponent()
override fun onInit() {
super.onInit()
val valuesFiltered = setting.type.values.filter { setting.type.filter(reference, it) }
dropDownComponent.updateValues(valuesFiltered)
if (valuesFiltered.isNotEmpty()) {
value = valuesFiltered.first()
}
}
override val validationIndicator: ValidationIndicator? = null
override val component: JComponent = dropDownComponent.component
}
class BooleanSettingComponent(
reference: SettingReference<Boolean, BooleanSettingType>,
valuesReadingContext: ValuesReadingContext
) : DefaultSettingComponent<Boolean, BooleanSettingType>(reference, valuesReadingContext) {
private val checkBox = JBCheckBox(setting.title).apply {
addChangeListener {
value = this@apply.isSelected
}
}
override fun onValueUpdated(reference: SettingReference<*, *>?) {
super.onValueUpdated(reference)
checkBox.isSelected = value ?: false
}
override val validationIndicator = ValidationIndicator(showText = false)
override val component: JComponent by lazy(LazyThreadSafetyMode.NONE) {
panel {
// TODO add validation indicator
add(checkBox, BorderLayout.CENTER)
}
}
}
class StringSettingComponent(
reference: SettingReference<String, StringSettingType>,
valuesReadingContext: ValuesReadingContext,
showLabel: Boolean = true
) : DefaultSettingComponent<String, StringSettingType>(reference, valuesReadingContext) {
private val textFieldComponent = TextFieldComponent(
valuesReadingContext,
setting.defaultValue.orEmpty(),
labelText = if (showLabel) setting.title else null,
validator = setting.validator,
onAnyValueUpdate = {
value = it
}
).asSubComponent()
override val validationIndicator: ValidationIndicator? = null
override fun onInit() {
super.onInit()
textFieldComponent.value = value.orEmpty()
}
override val component = textFieldComponent.component
}
class PathSettingComponent(
reference: SettingReference<Path, PathSettingType>,
valuesReadingContext: ValuesReadingContext
) : DefaultSettingComponent<Path, PathSettingType>(reference, valuesReadingContext) {
private val pathFieldComponent = PathFieldComponent(
valuesReadingContext,
"",
setting.title,
validator = settingValidator { path ->
setting.validator.validate(this, Paths.get(path))
},
onAnyValueUpdate = {
value = Paths.get(it)
}
).asSubComponent()
override val validationIndicator: ValidationIndicator? = null
override val component = pathFieldComponent.component
}
@@ -0,0 +1,41 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Displayable
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent
abstract class SettingComponent<V : Any, T: SettingType<V>>(
val reference: SettingReference<V, T>,
private val valuesReadingContext: ValuesReadingContext
) : DynamicComponent(valuesReadingContext), Displayable {
var value: V?
get() = reference.value
set(value) {
reference.value = value
}
val setting: Setting<V, T>
get() = with(valuesReadingContext.context) {
with(reference) { getSetting() }
}
abstract val validationIndicator: ValidationIndicator?
override fun onInit() {
super.onInit()
updateValidationState()
}
override fun onValueUpdated(reference: SettingReference<*, *>?) {
component.isVisible = setting.isActive(valuesReadingContext)
updateValidationState()
}
private fun updateValidationState() {
val value = value
if (validationIndicator != null && value != null) {
validationIndicator!!.validationState = setting.validator.validate(valuesReadingContext, value)
}
}
}
@@ -0,0 +1,37 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting
import com.intellij.ui.components.panels.VerticalLayout
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.core.ValuesReadingContext
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.Component
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent
import javax.swing.JComponent
import javax.swing.JPanel
class SettingsList(
settings: List<SettingReference<*, *>>,
private val valuesReadingContext: ValuesReadingContext
) : DynamicComponent(valuesReadingContext) {
private val panel = JPanel(VerticalLayout(5))
private var settingComponents: List<Component> = emptyList()
init {
setSettings(settings)
}
override val component: JComponent = panel
override fun onInit() {
super.onInit()
settingComponents.forEach { it.onInit() }
}
fun setSettings(settings: List<SettingReference<*, *>>) {
panel.removeAll()
settingComponents = settings.map { setting ->
DefaultSettingComponent.create(setting, valuesReadingContext)
}
settingComponents.forEach { setting -> setting.onInit(); panel.add(setting.component) }
}
}
@@ -0,0 +1,52 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting
import com.intellij.icons.AllIcons
import com.intellij.ui.JBColor
import com.intellij.ui.components.JBLabel
import com.intellij.util.ui.EmptyIcon
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.label
import java.awt.BorderLayout
import javax.swing.BorderFactory
import javax.swing.Icon
import javax.swing.JPanel
class ValidationIndicator(
defaultText: String? = null,
private val showText: Boolean
) : JPanel(BorderLayout()) {
private val textLabel = defaultText?.let { label("$it: ") }
private val errorLabel = label(" ").apply {
foreground = JBColor.red
}
init {
border = BorderFactory.createEmptyBorder(3, 3, 3, 3)
textLabel?.let { add(it, BorderLayout.WEST) }
add(errorLabel, BorderLayout.CENTER)
}
private fun setIcon(icon: Icon?) {
if (textLabel != null) textLabel.icon = icon
else errorLabel.icon = icon ?: EmptyIcon.ICON_16
}
var validationState: ValidationResult = ValidationResult.OK
set(value) {
field = value
when (value) {
ValidationResult.OK -> {
setIcon(null)
errorLabel.text = " "
toolTipText = null
}
is ValidationResult.ValidationError -> {
setIcon(AllIcons.General.Error)
val errors = value.messages.firstOrNull().orEmpty()
toolTipText = errors
if (showText) errorLabel.text = errors
}
}
}
}
@@ -0,0 +1,162 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.DefaultActionGroup
import com.intellij.ui.JBColor
import com.intellij.ui.JBSplitter
import com.intellij.ui.PopupHandler
import com.intellij.ui.ToolbarDecorator
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBTextField
import com.intellij.util.ui.JBUI
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.UiConstants.GAP_BORDER_SIZE
import com.intellij.util.ui.UIUtil
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.ModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.SimpleTargetConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.TargetConfigurator
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleSubType
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.ModuleType
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.*
import java.awt.*
import java.awt.event.MouseAdapter
import java.awt.event.MouseEvent
import javax.swing.*
import javax.swing.event.DocumentEvent
import javax.swing.event.DocumentListener
internal inline fun label(text: String, bold: Boolean = false, init: JBLabel.() -> Unit = {}) = JBLabel().apply {
font = UIUtil.getButtonFont().deriveFont(if (bold) Font.BOLD else Font.PLAIN)
this.text = text
init()
}
inline fun panel(layout: LayoutManager = BorderLayout(), init: JPanel.() -> Unit = {}) = JPanel(layout).apply(init)
fun textField(defaultValue: String?, onUpdated: (value: String) -> Unit) = JBTextField(defaultValue).apply {
document.addDocumentListener(object : DocumentListener {
override fun insertUpdate(e: DocumentEvent?) = onUpdated(this@apply.text)
override fun removeUpdate(e: DocumentEvent?) = onUpdated(this@apply.text)
override fun changedUpdate(e: DocumentEvent?) = onUpdated(this@apply.text)
})
}
internal fun hyperlinkLabel(
text: String,
onClick: () -> Unit
) = DescriptionPanel().apply {
cursor = Cursor(Cursor.HAND_CURSOR)
addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent?) = onClick()
})
updateText("<a href='javascript:void(0)'>$text</a>".asHtml())
}
internal fun String.asHtml() = "<html><body>$this</body></html>"
val DisplayableSettingItem.htmlText
get() = (text + greyText?.let { " <i>($greyText)</i>" }.orEmpty()).asHtml()
fun splitterFor(
vararg components: JComponent,
vertical: Boolean = false
) = components.reduce { left, right ->
JBSplitter(vertical, 1f / components.size).apply {
firstComponent = left
secondComponent = right
dividerWidth = 1
}
}
val ModuleType.icon: Icon
get() = when (this) {
ModuleType.jvm -> KotlinIcons.SMALL_LOGO
ModuleType.js -> KotlinIcons.JS
ModuleType.native -> KotlinIcons.NATIVE
ModuleType.common -> KotlinIcons.SMALL_LOGO
}
val Module.icon: Icon
get() = when (kind) {
ModuleKind.target -> configurator.moduleType.icon
ModuleKind.multiplatform -> AllIcons.Nodes.Module
ModuleKind.singleplatform -> AllIcons.Nodes.Module
}
val ModuleSubType.icon: Icon
get() = moduleType.icon
val ModuleConfigurator.icon: Icon
get() = when (this) {
is SimpleTargetConfigurator -> moduleSubType.icon
is TargetConfigurator -> moduleType.icon
else -> AllIcons.Nodes.Module
}
fun ToolbarDecorator.createPanelWithPopupHandler(popupTarget: JComponent) = createPanel().apply toolbarApply@{
val actionGroup = DefaultActionGroup().apply {
ToolbarDecorator.findAddButton(this@toolbarApply)?.let(this::add)
ToolbarDecorator.findRemoveButton(this@toolbarApply)?.let(this::add)
}
PopupHandler.installPopupHandler(
popupTarget,
actionGroup,
ActionPlaces.UNKNOWN,
ActionManager.getInstance()
)
}
fun <C : JComponent> C.bordered(
needTopEmptyBorder: Boolean = true,
needBottomEmptyBorder: Boolean = true,
needInnerEmptyBorder: Boolean = true,
needLineBorder: Boolean = true
) = apply {
val lineBorder = if (needLineBorder) BorderFactory.createLineBorder(JBColor.border()) else null
border = BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(
if (needTopEmptyBorder) GAP_BORDER_SIZE else 0,
0,
if (needBottomEmptyBorder) GAP_BORDER_SIZE else 0,
0
),
when {
needInnerEmptyBorder -> BorderFactory.createCompoundBorder(
lineBorder,
BorderFactory.createEmptyBorder(GAP_BORDER_SIZE, GAP_BORDER_SIZE, GAP_BORDER_SIZE, GAP_BORDER_SIZE)
)
else -> lineBorder
}
)
}
// Copied from com.intellij.ide.projectWizard.ProjectSettingsStep.addField
fun JPanel.addFieldWithLabel(label: String, field: JComponent) {
val jLabel = JBLabel(label)
jLabel.labelFor = field
jLabel.verticalAlignment = SwingConstants.TOP
add(
jLabel,
GridBagConstraints(
0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, GridBagConstraints.WEST,
GridBagConstraints.VERTICAL, JBUI.insets(5, 0, 5, 0), 4, 0
)
)
add(
field,
GridBagConstraints(
1, GridBagConstraints.RELATIVE, 1, 1, 1.0, 0.0, GridBagConstraints.CENTER,
GridBagConstraints.HORIZONTAL, JBUI.insetsBottom(5), 0, 0
)
)
}
object UiConstants {
const val GAP_BORDER_SIZE = 5
}
@@ -0,0 +1,11 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui
import org.jetbrains.kotlin.tools.projectWizard.library.LibraryArtifact
import org.jetbrains.kotlin.tools.projectWizard.library.MavenArtifact
import org.jetbrains.kotlin.tools.projectWizard.library.NpmArtifact
fun LibraryArtifact.render() = when (this) {
is MavenArtifact -> "$groupId:$artifactId"
is NpmArtifact -> name
}
@@ -122,5 +122,6 @@
restartRequired="false"/>
<fileEditorProvider implementation="org.jetbrains.kotlin.idea.scratch.ui.KtScratchFileEditorProvider"/>
<moduleBuilder builderClass="org.jetbrains.kotlin.tools.projectWizard.wizard.NewProjectWizardModuleBuilder"/>
</extensions>
</idea-plugin>
@@ -121,6 +121,6 @@
defaultValue="false"
restartRequired="true"/>
<fileEditorProvider implementation="org.jetbrains.kotlin.idea.scratch.ui.KtScratchFileEditorProvider"/>
<moduleBuilder builderClass="org.jetbrains.kotlin.tools.projectWizard.wizard.NewProjectWizardModuleBuilder"/>
</extensions>
</idea-plugin>
@@ -112,6 +112,6 @@
defaultValue="false"
restartRequired="true"/>
<fileEditorProvider implementation="org.jetbrains.kotlin.idea.scratch.ui.KtScratchFileEditorProvider"/>
<moduleBuilder builderClass="org.jetbrains.kotlin.tools.projectWizard.wizard.NewProjectWizardModuleBuilder"/>
</extensions>
</idea-plugin>
@@ -0,0 +1,24 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
api("org.apache.velocity:velocity:1.7") // we have to use the old version as it is the same as bundled into IntelliJ
compileOnly(project(":kotlin-reflect-api"))
testImplementation(project(":kotlin-test:kotlin-test-junit"))
testImplementation(commonDep("junit:junit"))
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
projectTest {
dependsOn(":dist")
workingDir = rootDir
}
testsJar()
@@ -0,0 +1,29 @@
plugins {
kotlin("jvm")
id("jps-compatible")
}
dependencies {
implementation(kotlinStdlib())
compileOnly(project(":kotlin-reflect-api"))
implementation(project(":libraries:tools:new-project-wizard"))
compileOnly(intellijDep()) { includeJars("snakeyaml-1.24") }
testImplementation(projectTests(":compiler:tests-common"))
testImplementation(project(":kotlin-test:kotlin-test-junit"))
testImplementation(commonDep("junit:junit"))
testImplementation(intellijDep())
}
sourceSets {
"main" { projectDefault() }
"test" { projectDefault() }
}
projectTest {
dependsOn(":dist")
workingDir = rootDir
}
testsJar()
@@ -0,0 +1,64 @@
package org.jetbrains.kotlin.tools.projectWizard
import YamlParsingError
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.yaml.snakeyaml.Yaml
import org.yaml.snakeyaml.parser.ParserException
import java.nio.file.Path
class YamlSettingsParser(settings: List<PluginSetting<Any, *>>, private val parsingState: ParsingState) {
private val settingByName = settings.associateBy { it.path }
fun parseYamlText(yaml: String): TaskResult<Map<SettingReference<*, *>, Any>> {
val yamlObject = try {
Success(Yaml().load<Any>(yaml) ?: emptyMap<String, Any>())
} catch (e: ParserException) {
Failure(YamlParsingError(e))
} catch (e: Exception) {
Failure(ExceptionErrorImpl(e))
}
return yamlObject.flatMap { map ->
if (map is Map<*, *>) {
val result = ComputeContext.runInComputeContextWithState(parsingState) {
parseSettingValues(map, "")
}
result.map { (pluginSettings, newState) ->
pluginSettings + newState.settingValues
}
} else Failure(
BadSettingValueError("Settings file should be a map of settings")
)
}
}
fun parseYamlFile(file: Path) = computeM {
val (yaml) = safe { file.toFile().readText() }
parseYamlText(yaml)
}
private fun ParsingContext.parseSettingValues(
data: Any,
settingPath: String
): TaskResult<Map<PluginSettingReference<*, *>, Any>> =
if (settingPath in settingByName) compute {
val setting = settingByName.getValue(settingPath)
val (parsed) = setting.type.parse(this, data, settingPath)
listOf(PluginSettingReference(setting) to parsed)
} else {
when (data) {
is Map<*, *> -> {
data.entries.mapCompute { (name, value) ->
if (value == null) fail(BadSettingValueError("No value was found for a key `$settingPath`"))
val prefix = if (settingPath.isEmpty()) "" else "$settingPath."
val (children) = parseSettingValues(value, "$prefix$name")
children.entries.map { (key, value) -> key to value }
}.sequence().map { it.flatten() }
}
else -> Failure(
BadSettingValueError("No value was found for a key `$settingPath`")
)
}
}.map { it.toMap() }
}
@@ -0,0 +1,42 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard
import org.jetbrains.kotlin.tools.projectWizard.YamlSettingsParser
import org.jetbrains.kotlin.tools.projectWizard.core.*
import org.jetbrains.kotlin.tools.projectWizard.core.entity.PipelineTask
import org.jetbrains.kotlin.tools.projectWizard.core.entity.PluginSettingReference
import org.jetbrains.kotlin.tools.projectWizard.core.entity.reference
import org.jetbrains.kotlin.tools.projectWizard.core.service.AndroidServiceImpl
import org.jetbrains.kotlin.tools.projectWizard.core.service.Service
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.plugins.StructurePlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin
import java.nio.file.Paths
class YamlWizard(
private val yaml: String,
private val path: String,
createPlugins: (Context) -> List<Plugin>
) : Wizard(createPlugins, listOf(AndroidServiceImpl())) {
override fun apply(
services: List<Service>,
phases: Set<GenerationPhase>,
onTaskExecuting: (PipelineTask) -> Unit
): TaskResult<Unit> = computeM {
super.apply(services, setOf(GenerationPhase.PREPARE), onTaskExecuting)
val parsingData = with(valuesReadingContext) {
ParsingState(TemplatesPlugin::templates.propertyValue, emptyMap())
}
val yamlParser = YamlSettingsParser(pluginSettings, parsingData)
val (settingsValuesFromYaml) = yamlParser.parseYamlText(yaml)
val settingValues = defaultPluginSettingValues + settingsValuesFromYaml
settingValues.forEach { (path, value) -> context.settingContext[path] = value }
context.settingContext[StructurePlugin::projectPath.reference] = Paths.get(path)
super.apply(services, phases, onTaskExecuting)
}
private val defaultPluginSettingValues
get() = pluginSettings.mapNotNull { setting ->
val defaultValue = setting.defaultValue ?: return@mapNotNull null
PluginSettingReference(setting) to defaultValue
} .toMap()
}
@@ -0,0 +1,8 @@
import org.jetbrains.kotlin.tools.projectWizard.core.Error
import org.jetbrains.kotlin.tools.projectWizard.core.asString
import org.yaml.snakeyaml.parser.ParserException
data class YamlParsingError(val exception: ParserException) : Error() {
override val message: String
get() = exception.asString()
}
@@ -0,0 +1,36 @@
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
}
group = 'testGroupId'
version = '1.0-SNAPSHOT'
repositories {
gradlePluginPortal()
google()
jcenter()
mavenCentral()
}
android {
compileSdkVersion 29
defaultConfig {
applicationId 'testArtifactId'
minSdkVersion 24
targetSdkVersion 29
versionCode 1
versionName '1.0'
}
buildTypes {
'release' {
isMinifyEnabled false
}
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.1.0'
implementation kotlin('stdlib-jdk7')
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation kotlin('stdlib-jdk8')
}
@@ -0,0 +1,36 @@
plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-android-extensions")
}
group = "testGroupId"
version = "1.0-SNAPSHOT"
repositories {
gradlePluginPortal()
google()
jcenter()
mavenCentral()
}
android {
compileSdkVersion(29)
defaultConfig {
applicationId = "testArtifactId"
minSdkVersion(24)
targetSdkVersion(29)
versionCode = 1
versionName = "1.0"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
}
dependencies {
implementation("androidx.appcompat:appcompat:1.1.0")
implementation("androidx.core:core-ktx:1.1.0")
implementation(kotlin("stdlib-jdk7"))
implementation("androidx.constraintlayout:constraintlayout:1.1.3")
implementation(kotlin("stdlib-jdk8"))
}
@@ -0,0 +1,17 @@
buildscript {
repositories {
gradlePluginPortal()
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath kotlin('gradle-plugin', '1.3.61')
}
}
group = 'testGroupId'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
@@ -0,0 +1,17 @@
buildscript {
repositories {
gradlePluginPortal()
google()
jcenter()
}
dependencies {
classpath("com.android.tools.build:gradle:3.2.1")
classpath(kotlin("gradle-plugin", "1.3.61"))
}
}
group = "testGroupId"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
@@ -0,0 +1,3 @@
org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GroovyDslPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.KotlinDslPlugin
@@ -0,0 +1,12 @@
kotlin:
version: 1.3.61
projectKind: Multiplatform
modules:
- type:
name: android
androidSdkPath: /home/ilya/Android/Sdk
kind: singleplatform
name: android
sourcesets:
- type: main
- type: test
@@ -0,0 +1,39 @@
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.61'
}
group = 'testGroupId'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
kotlin {
js('nodeJs') {
nodejs {
}
}
js('browser') {
browser {
}
}
sourceSets {
nodeJsMain {
dependencies {
implementation kotlin('stdlib-js')
}
}
nodeJsTest {
}
browserMain {
dependencies {
implementation kotlin('stdlib-js')
}
}
browserTest {
}
}
}
@@ -0,0 +1,35 @@
plugins {
kotlin("multiplatform") version "1.3.61"
}
group = "testGroupId"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
kotlin {
js("nodeJs") {
nodejs {
}
}
js("browser") {
browser {
}
}
sourceSets {
val nodeJsMain by getting {
dependencies {
implementation(kotlin("stdlib-js"))
}
}
val nodeJsTest by getting
val browserMain by getting {
dependencies {
implementation(kotlin("stdlib-js"))
}
}
val browserTest by getting
}
}
@@ -0,0 +1,3 @@
org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GroovyDslPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.KotlinDslPlugin
@@ -0,0 +1,20 @@
kotlin:
version: 1.3.61
projectKind: Multiplatform
modules:
- type: multiplatform
kind: multiplatform
name: a
subModules:
- type: jsNode
kind: target
name: nodeJs
sourcesets:
- type: main
- type: test
- type: jsBrowser
kind: target
name: browser
sourcesets:
- type: main
- type: test
@@ -0,0 +1,22 @@
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.61'
}
group = 'testGroupId'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
kotlin {
jvm()
sourceSets {
jvmMain {
dependencies {
implementation kotlin('stdlib-jdk8')
}
}
jvmTest {
}
}
}
@@ -0,0 +1,20 @@
plugins {
kotlin("multiplatform") version "1.3.61"
}
group = "testGroupId"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
kotlin {
jvm()
sourceSets {
val jvmMain by getting {
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
}
val jvmTest by getting
}
}
@@ -0,0 +1,3 @@
org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GroovyDslPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.KotlinDslPlugin
@@ -0,0 +1,14 @@
kotlin:
version: 1.3.61
projectKind: Multiplatform
modules:
- type: multiplatform
kind: multiplatform
name: a
subModules:
- type: jvmTarget
kind: target
name: jvm
sourcesets:
- type: main
- type: test
@@ -0,0 +1,12 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.61'
}
group = 'testGroupId'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation kotlin('stdlib-jdk8')
}
@@ -0,0 +1,12 @@
plugins {
kotlin("jvm") version "1.3.61"
}
group = "testGroupId"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
@@ -0,0 +1,5 @@
org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.MavenPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GroovyDslPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.KotlinDslPlugin
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>nya</artifactId>
<groupId>testGroupId</groupId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>nya</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.code.style>official</kotlin.code.style>
</properties>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>1.3.61</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>1.3.61</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,10 @@
kotlin:
version: 1.3.61
projectKind: Singleplatform
modules:
- type: JVM Module
name: nya
kind: singleplatform
sourcesets:
- type: main
@@ -0,0 +1,34 @@
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.61'
}
group = 'testGroupId'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
kotlin {
def hostOs = System.getProperty("os.name")
def isMingwX64 = hostOs.startsWith("Windows")
org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithTests nativeTarget
if (hostOs == "Mac OS X") nativeTarget = macosX64('myNative')
else if (hostOs == "Linux") nativeTarget = linuxX64("myNative")
else if (isMingwX64) return nativeTarget = mingwX64("myNative")
else throw new GradleException("Host OS is not supported in Kotlin/Native.")
nativeTarget.with {
binaries {
executable {
entryPoint = 'MAIN CLASS'
}
}
}
sourceSets {
myNativeMain {
}
myNativeTest {
}
}
}
@@ -0,0 +1,31 @@
plugins {
kotlin("multiplatform") version "1.3.61"
}
group = "testGroupId"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
kotlin {
val hostOs = System.getProperty("os.name")
val isMingwX64 = hostOs.startsWith("Windows")
val nativeTarget = when {
hostOs == "Mac OS X" -> macosX64("myNative")
hostOs == "Linux" -> linuxX64("myNative")
isMingwX64 -> mingwX64("myNative")
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
}
nativeTarget.apply {
binaries {
executable {
entryPoint = "MAIN CLASS"
}
}
}
sourceSets {
val myNativeMain by getting
val myNativeTest by getting
}
}
@@ -0,0 +1,3 @@
org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GroovyDslPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.KotlinDslPlugin
@@ -0,0 +1,14 @@
kotlin:
version: 1.3.61
projectKind: Multiplatform
modules:
- type: multiplatform
kind: multiplatform
name: a
subModules:
- type: nativeForCurrentSystem
kind: target
name: myNative
sourcesets:
- type: main
- type: test
@@ -0,0 +1,35 @@
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.61'
}
group = 'testGroupId'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
kotlin {
jvm()
js('a') {
browser {
}
}
sourceSets {
jvmMain {
dependencies {
implementation kotlin('stdlib-jdk8')
}
}
jvmTest {
}
aMain {
dependencies {
implementation kotlin('stdlib-js')
}
}
aTest {
}
}
}
@@ -0,0 +1,31 @@
plugins {
kotlin("multiplatform") version "1.3.61"
}
group = "testGroupId"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
kotlin {
jvm()
js("a") {
browser {
}
}
sourceSets {
val jvmMain by getting {
dependencies {
implementation(kotlin("stdlib-jdk8"))
}
}
val jvmTest by getting
val aMain by getting {
dependencies {
implementation(kotlin("stdlib-js"))
}
}
val aTest by getting
}
}
@@ -0,0 +1,3 @@
org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GroovyDslPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.KotlinDslPlugin
@@ -0,0 +1,20 @@
kotlin:
version: 1.3.61
projectKind: Multiplatform
modules:
- type: multiplatform
kind: multiplatform
name: a
subModules:
- type: jvmTarget
kind: target
name: jvm
sourcesets:
- type: main
- type: test
- type: jsBrowser
kind: target
name: a
sourcesets:
- type: main
- type: test
@@ -0,0 +1,26 @@
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.3.61'
}
group = 'testGroupId'
version = '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
kotlin {
linuxX64 {
binaries {
executable {
entryPoint = 'MAIN CLASS'
}
}
}
sourceSets {
linuxX64Main {
}
linuxX64Test {
}
}
}
@@ -0,0 +1,22 @@
plugins {
kotlin("multiplatform") version "1.3.61"
}
group = "testGroupId"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
kotlin {
linuxX64 {
binaries {
executable {
entryPoint = "MAIN CLASS"
}
}
}
sourceSets {
val linuxX64Main by getting
val linuxX64Test by getting
}
}
@@ -0,0 +1,3 @@
org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GroovyDslPlugin
org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.KotlinDslPlugin
@@ -0,0 +1,14 @@
kotlin:
version: 1.3.61
projectKind: Multiplatform
modules:
- type: multiplatform
kind: multiplatform
name: a
subModules:
- type: linuxX64Target
kind: target
name: linuxX64
sourcesets:
- type: main
- type: test
@@ -0,0 +1,99 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.tools.projectWizard.cli
import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.kotlin.tools.projectWizard.core.ExceptionError
import org.jetbrains.kotlin.tools.projectWizard.core.div
import org.jetbrains.kotlin.tools.projectWizard.core.onFailure
import org.jetbrains.kotlin.tools.projectWizard.core.service.Services
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.MavenPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.GroovyDslPlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.buildSystem.gradle.KotlinDslPlugin
import org.jetbrains.kotlin.tools.projectWizard.wizard.YamlWizard
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
abstract class AbstractBuildFileGenerationTest : AbstractPluginBasedTest() {
fun doTest(directoryPath: String) {
val directory = Paths.get(directoryPath)
val testData = init(directory)
if (KotlinDslPlugin::class in testData.pluginClasses) {
doTest(directory, testData, BuildSystem.GRADLE_KOTLIN_DSL)
}
if (GroovyDslPlugin::class in testData.pluginClasses) {
doTest(directory, testData, BuildSystem.GRADLE_GROOVY_DSL)
}
if (MavenPlugin::class in testData.pluginClasses) {
doTest(directory, testData, BuildSystem.MAVEN)
}
}
private fun doTest(directory: Path, testData: WizardTestData, buildSystem: BuildSystem) {
val yaml = directory.resolve("settings.yaml").toFile().readText() + "\n" +
defaultStructure + "\n" +
buildSystem.yaml
val tempDir = Files.createTempDirectory(null)
val wizard = YamlWizard(yaml, tempDir.toString(), testData.createPlugins)
val result = wizard.apply(Services.osServices, GenerationPhase.ALL)
result.onFailure { errors ->
errors.forEach { error ->
if (error is ExceptionError) {
throw error.exception
}
}
fail(errors.joinToString("\n"))
}
val expectedDirectory = (directory / EXPECTED_DIRECTORY_NAME).takeIf { Files.exists(it) } ?: directory
compareFiles(
expectedDirectory.allBuildFiles(buildSystem), expectedDirectory,
tempDir.allBuildFiles(buildSystem), tempDir
)
}
private fun Path.allBuildFiles(buildSystem: BuildSystem) =
listFiles { it.fileName.toString() == buildSystem.buildFileName }
private enum class BuildSystem(val buildFileName: String, val yaml: String) {
GRADLE_KOTLIN_DSL(
buildFileName = "build.gradle.kts",
yaml = """buildSystem:
type: GradleKotlinDsl
gradle:
createGradleWrapper: false
version: 5.4.1""".trimIndent()
),
GRADLE_GROOVY_DSL(
buildFileName = "build.gradle",
yaml = """buildSystem:
type: GradleGroovyDsl
gradle:
createGradleWrapper: false
version: 5.4.1""".trimIndent()
),
MAVEN(
buildFileName = "pom.xml",
yaml = """buildSystem:
type: Maven""".trimIndent()
)
}
companion object {
private const val EXPECTED_DIRECTORY_NAME = "expected"
private val defaultStructure =
"""structure:
name: generatedProject
groupId: testGroupId
artifactId: testArtifactId
""".trimIndent()
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.tools.projectWizard.cli
import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.Plugin
import org.jetbrains.kotlin.tools.projectWizard.core.PluginReference
import org.jetbrains.kotlin.tools.projectWizard.plugins.StructurePlugin
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin
import java.nio.file.Path
import kotlin.reflect.full.primaryConstructor
abstract class AbstractPluginBasedTest : UsefulTestCase() {
open val defaultPlugins: List<PluginReference> = listOf(
StructurePlugin::class,
TemplatesPlugin::class
)
protected fun init(directory: Path): WizardTestData {
val pluginNames = directory.resolve("plugins.txt").toFile().readLines().mapNotNull { name ->
name.trim().takeIf { it.isNotBlank() }
}.distinct()
@Suppress("UNCHECKED_CAST")
val pluginClasses = defaultPlugins + pluginNames.map { pluginName ->
Class.forName(pluginName).kotlin as PluginReference
}
val createPlugins = { context: Context ->
pluginClasses.map { pluginClass ->
pluginClass.primaryConstructor!!.call(context)
}
}
return WizardTestData(pluginClasses, createPlugins)
}
}
data class WizardTestData(
val pluginClasses: List<PluginReference>,
val createPlugins: (Context) -> List<Plugin>
)
@@ -0,0 +1,65 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.tools.projectWizard.cli;
import com.intellij.testFramework.TestDataPath;
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestMetadata;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public class BuildFileGenerationTestGenerated extends AbstractBuildFileGenerationTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
public void testAllFilesPresentInBuildFileGeneration() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration"), Pattern.compile("^([^\\.]+)$"), false);
}
@TestMetadata("android")
public void testAndroid() throws Exception {
runTest("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration/android/");
}
@TestMetadata("jsNodeAndBrowserTargets")
public void testJsNodeAndBrowserTargets() throws Exception {
runTest("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration/jsNodeAndBrowserTargets/");
}
@TestMetadata("jvmTarget")
public void testJvmTarget() throws Exception {
runTest("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration/jvmTarget/");
}
@TestMetadata("kotlinJvm")
public void testKotlinJvm() throws Exception {
runTest("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration/kotlinJvm/");
}
@TestMetadata("nativeForCurrentSystem")
public void testNativeForCurrentSystem() throws Exception {
runTest("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration/nativeForCurrentSystem/");
}
@TestMetadata("simpleMultiplatform")
public void testSimpleMultiplatform() throws Exception {
runTest("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration/simpleMultiplatform/");
}
@TestMetadata("simpleNativeTarget")
public void testSimpleNativeTarget() throws Exception {
runTest("libraries/tools/new-project-wizard/new-project-wizard-cli/testData/buildFileGeneration/simpleNativeTarget/");
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.tools.projectWizard.cli
import org.hamcrest.core.Is
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.junit.Assert.assertThat
import java.nio.file.Files
import java.nio.file.Path
import java.util.stream.Collectors
internal fun Path.readFile() = toFile().readText().trim()
internal fun Path.listFiles(filter: (Path) -> Boolean) =
Files.walk(this).filter { path ->
Files.isRegularFile(path) && filter(path)
}.collect(Collectors.toList()).sorted()
internal fun compareFiles(
expectedFiles: List<Path>, expectedDir: Path,
actualFiles: List<Path>, actualDir: Path
) {
val expectedFilesSorted = expectedFiles.sorted()
val actualFilesSorted = actualFiles.sorted()
assertThat(
actualFilesSorted.map { actualDir.relativize(it) },
Is.`is`(expectedFilesSorted.map { expectedDir.relativize(it) })
)
for ((actualFile, expectedFile) in actualFilesSorted zip expectedFilesSorted) {
KotlinTestUtils.assertEqualsToFile(expectedFile.toFile(), actualFile.readFile())
}
}
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="${package.asCodePackage()}">
<application
android:allowBackup="true"
android:supportsRtl="true">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
@@ -0,0 +1,11 @@
package ${package.asCodePackage()}
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,3 @@
fun main(args: Array<String>) {
println("Hello World!")
}
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-${version}-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@@ -0,0 +1,3 @@
#foreach($property in $properties)
${property.getFirst()}=${property.getSecond()}
#end
@@ -0,0 +1,3 @@
#foreach($property in $properties)
${property.getFirst()}=${property.getSecond()}
#end
@@ -0,0 +1,5 @@
rootProject.name = "${projectName}"
#foreach($subProject in $subProjects)
include("${subProject}")
#end
@@ -0,0 +1,9 @@
import kotlin.test.Test
import kotlin.test.assertTrue
class Test {
@Test
fun testIt() {
assertTrue(true)
}
}
@@ -0,0 +1,25 @@
package org.jetbrains.kotlin.tools.projectWizard
import org.jetbrains.kotlin.tools.projectWizard.core.RandomIdGenerator
import org.jetbrains.kotlin.tools.projectWizard.core.safeAs
sealed class Identificator {
abstract val id: String
final override fun equals(other: Any?): Boolean = other.safeAs<Identificator>()?.id == id
final override fun hashCode(): Int = id.hashCode()
final override fun toString(): String = id
}
class GeneratedIdentificator(prefix: String?) : Identificator() {
override val id = """${prefix.orEmpty()}_${RandomIdGenerator.generate()}"""
}
interface IdentificatorOwner {
val identificator: Identificator
}
val IdentificatorOwner.id
get() = identificator.id
@@ -0,0 +1,79 @@
package org.jetbrains.kotlin.tools.projectWizard
import org.jetbrains.kotlin.tools.projectWizard.core.EntitiesOwnerDescriptor
import org.jetbrains.kotlin.tools.projectWizard.core.Parser
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.settings.DisplayableSettingItem
import kotlin.properties.ReadOnlyProperty
interface SettingsOwner : EntitiesOwnerDescriptor {
fun <V : Any, T : SettingType<V>> settingDelegate(
create: (path: String) -> SettingBuilder<V, T>
): ReadOnlyProperty<Any, Setting<V, T>>
// the functions to create different kinds of settings
// for now should be overridden with specific settings types :(
// TODO think a way yo emulate higher-kind types
fun <V : DisplayableSettingItem> dropDownSetting(
title: String,
neededAtPhase: GenerationPhase,
parser: Parser<V>,
init: DropDownSettingType.Builder<V>.() -> Unit = {}
): ReadOnlyProperty<Any, Setting<V, DropDownSettingType<V>>> = settingDelegate { path ->
DropDownSettingType.Builder(path, title, neededAtPhase, parser).apply(init)
}
fun stringSetting(
title: String,
neededAtPhase: GenerationPhase,
init: StringSettingType.Builder.() -> Unit = {}
) = settingDelegate { path ->
StringSettingType.Builder(path, title, neededAtPhase).apply(init)
}
fun booleanSetting(
title: String,
neededAtPhase: GenerationPhase,
init: BooleanSettingType.Builder.() -> Unit = {}
) = settingDelegate { path ->
BooleanSettingType.Builder(path, title, neededAtPhase).apply(init)
}
fun <V : Any> valueSetting(
title: String,
neededAtPhase: GenerationPhase,
parser: Parser<V>,
init: ValueSettingType.Builder<V>.() -> Unit = {}
) = settingDelegate { path ->
ValueSettingType.Builder(path, title, neededAtPhase, parser).apply(init)
}
fun versionSetting(
title: String,
neededAtPhase: GenerationPhase,
init: VersionSettingType.Builder.() -> Unit = {}
) = settingDelegate { path ->
VersionSettingType.Builder(path, title, neededAtPhase).apply(init)
}
fun <V : Any> listSetting(
title: String,
neededAtPhase: GenerationPhase,
parser: Parser<V>,
init: ListSettingType.Builder<V>.() -> Unit = {}
) = settingDelegate { path ->
ListSettingType.Builder(path, title, neededAtPhase, parser).apply(init)
}
fun pathSetting(
title: String,
neededAtPhase: GenerationPhase,
init: PathSettingType.Builder.() -> Unit = {}
) = settingDelegate { path ->
PathSettingType.Builder(path, title, neededAtPhase).apply(init)
}
}
@@ -0,0 +1,68 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.core.entity.SettingType
sealed class CheckerRule {
abstract fun check(context: ValuesReadingContext): Boolean
}
data class RuleBySettingValue(
val settingReference: SettingReference<Any, SettingType<Any>>,
val expectedValue: Any
) : CheckerRule() {
override fun check(context: ValuesReadingContext): Boolean = with(context) {
settingReference.notRequiredSettingValue() == expectedValue
}
}
data class OrRule(
val left: CheckerRule,
val right: CheckerRule
) : CheckerRule() {
override fun check(context: ValuesReadingContext): Boolean = left.check(context) || right.check(context)
}
data class Checker(val rules: List<CheckerRule>) {
fun check(context: ValuesReadingContext) =
rules.all { rule -> rule.check(context) }
class Builder {
private val rules = mutableListOf<CheckerRule>()
infix fun <V : Any, T: SettingType<V>> SettingReference<V, T>.shouldBeEqual(value: V) =
RuleBySettingValue(this, value)
infix fun CheckerRule.or(other: CheckerRule) =
OrRule(this, other)
fun extend(parent: Checker) {
this.rules += parent.rules
}
fun rule(rule: CheckerRule) {
rules += rule
}
fun build() = Checker(rules)
}
companion object {
val ALWAYS_AVAILABLE = Checker(emptyList())
}
}
fun checker(init: Checker.Builder.() -> Unit) =
Checker.Builder().apply(init).build()
interface ContextOwner {
val context: Context
}
interface ActivityCheckerOwner {
val activityChecker: Checker
fun isActive(valuesReadingContext: ValuesReadingContext) = activityChecker.check(valuesReadingContext)
}
@@ -0,0 +1,72 @@
package org.jetbrains.kotlin.tools.projectWizard.core
import org.jetbrains.kotlin.tools.projectWizard.core.entity.*
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.memberProperties
class Context(private val pluginsCreator: PluginsCreator, val eventManager: EventManager) {
private fun initPlugin(plugin: Plugin) {
for (entityReference in plugin::class.memberProperties) {
val type = entityReference.returnType.classifier.safeAs<KClass<*>>() ?: continue
if (type.isSubclassOf(Entity::class)) {
when (val entity = entityReference.getter.call(plugin)) {
is Property<*> -> {
@Suppress("UNCHECKED_CAST")
propertyContext[entityReference as PropertyReference<Any>] = entity.defaultValue
}
}
}
}
}
val settingContext = SettingContext(eventManager::fireListeners)
val propertyContext = PropertyContext()
val taskContext = TaskContext()
val plugins = pluginsCreator(this).onEach(::initPlugin)
@Suppress("UNUSED_PARAMETER")
fun getUnspecifiedSettings(phases: Set<GenerationPhase>): List<AnySetting> {
val required = plugins
.flatMap { plugin ->
plugin.declaredSettings.mapNotNull { setting ->
if (setting.neededAtPhase !in phases) return@mapNotNull null
if (setting.isRequired) setting else null
}
}.toSet()
val provided = settingContext.allPluginSettings.map { it.path }.toSet()
return required.filterNot { it.path in provided }
}
private val pipelineLineTasks: List<PipelineTask>
get() = plugins
.flatMap { it.declaredTasks.filterIsInstance<PipelineTask>() }
private fun task(reference: PipelineTaskReference) =
taskContext.getEntity(reference) as? PipelineTask
?: error(reference.path)
private val dependencyList: Map<PipelineTask, List<PipelineTask>>
get() {
val dependeeMap = pipelineLineTasks.flatMap { task ->
task.after.map { after -> task to task(after) }
}
val dependencyMap = pipelineLineTasks.flatMap { task ->
task.before.map { before -> task(before) to task }
}
return (dependeeMap + dependencyMap)
.groupBy { it.first }
.mapValues {
it.value.map { it.second }
}
}
fun copy() = Context(pluginsCreator, eventManager)
fun sortTasks(): TaskResult<List<PipelineTask>> =
TaskSorter().sort(pipelineLineTasks, dependencyList)
}

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