Wizard: don't show error dialog on validation, instead navigate to the error

This commit is contained in:
Ilya Kirillov
2020-03-16 19:53:48 +03:00
parent f640477464
commit c71972afc9
13 changed files with 154 additions and 345 deletions
@@ -61,11 +61,8 @@ class NewProjectWizardModuleBuilder : EmptyModuleBuilder() {
companion object {
const val MODULE_BUILDER_ID = "kotlin.newProjectWizard.builder"
private const val DEFAULT_GROUP_ID = "me.user"
private val projectNameValidator = StringValidators.shouldBeValidIdentifier(
KotlinNewProjectWizardBundle.message("editor.entity.project.name"),
setOf('-', '_')
)
private val INVALID_PROJECT_NAME_MESSAGE = KotlinNewProjectWizardBundle.message("error.invalid.project.name")
private val projectNameValidator = StringValidators.shouldBeValidIdentifier("Project name", setOf('-', '_'))
private const val INVALID_PROJECT_NAME_MESSAGE = "Invalid project name"
}
override fun isAvailable(): Boolean = ExperimentalFeatures.NewWizard.isEnabled
@@ -96,7 +93,7 @@ class NewProjectWizardModuleBuilder : EmptyModuleBuilder() {
phases = GenerationPhase.startingFrom(GenerationPhase.FIRST_STEP)
).onFailure { errors ->
val errorMessages = errors.joinToString(separator = "\n") { it.message }
Messages.showErrorDialog(project, errorMessages, KotlinNewProjectWizardBundle.message("error.following"))
Messages.showErrorDialog(project, errorMessages, "The following errors arose during project generation")
}.isSuccess
if (success) {
val projectCreationStats = ProjectCreationStats(
@@ -186,11 +183,16 @@ abstract class WizardStep(protected val wizard: IdeWizard, private val phase: Ge
override fun updateDataModel() = Unit // model is updated on every UI action
override fun validate(): Boolean =
when (val result = wizard.context.read { with(wizard) { validate(setOf(phase)) } }) {
is Success<*> -> true
is Failure -> {
throw ConfigurationException(result.asHtml(), KotlinNewProjectWizardBundle.message("error.validation"))
ValidationResult.OK -> true
is ValidationResult.ValidationError -> {
handleErrors(result)
false
}
}
protected open fun handleErrors(error: ValidationResult.ValidationError) {
throw ConfigurationException(error.asHtml(), "Validation Error")
}
}
private class PomWizardStep(
@@ -256,4 +258,8 @@ class ModuleNewWizardSecondStep(
override fun _init() {
component.onInit()
}
override fun handleErrors(error: ValidationResult.ValidationError) {
component.navigateTo(error)
}
}
@@ -5,12 +5,13 @@ import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.Reader
import org.jetbrains.kotlin.tools.projectWizard.core.SettingsWriter
import org.jetbrains.kotlin.tools.projectWizard.core.Writer
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.PluginSettingPropertyReference
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.SettingReference
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.SettingType
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.reference
abstract class Component : Displayable {
abstract class Component : Displayable, ErrorNavigatable {
private val subComponents = mutableListOf<Component>()
open fun onInit() {
@@ -20,6 +21,10 @@ abstract class Component : Displayable {
protected fun <C : Component> C.asSubComponent(): C = also {
this@Component.subComponents += it
}
override fun navigateTo(error: ValidationResult.ValidationError) {
subComponents.forEach { it.navigateTo(error) }
}
}
abstract class DynamicComponent(private val context: Context) : Component() {
@@ -66,4 +71,8 @@ abstract class TitledComponent(context: Context) : DynamicComponent(context) {
interface FocusableComponent {
fun focusOn() {}
}
interface ErrorNavigatable {
fun navigateTo(error: ValidationResult.ValidationError)
}
@@ -17,7 +17,6 @@ import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.FocusableComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.label
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.panel
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.ErrorAwareComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.IdeaBasedComponentValidator
import java.awt.BorderLayout
import javax.swing.JComponent
@@ -27,7 +26,7 @@ abstract class UIComponent<V : Any>(
labelText: String? = null,
private val validator: SettingValidator<V>? = null,
private val onValueUpdate: (V) -> Unit = {}
) : DynamicComponent(context), ErrorAwareComponent, FocusableComponent, Disposable {
) : DynamicComponent(context), FocusableComponent, Disposable {
private val validationIndicator by lazy(LazyThreadSafetyMode.NONE) {
if (validator != null)
IdeaBasedComponentValidator(this, getValidatorTarget())
@@ -83,10 +82,14 @@ abstract class UIComponent<V : Any>(
uiComponent.requestFocus()
}
override fun findComponentWithError(error: ValidationResult.ValidationError): FocusableComponent? = takeIf {
read {
override fun navigateTo(error: ValidationResult.ValidationError) {
val errorInThisComponent = read {
getUiValue()?.let { validator?.validate?.invoke(this, it)?.isSpecificError(error) } == true
}
if (errorInThisComponent) {
focusOn()
}
}
}
@@ -65,7 +65,7 @@ class ModuleSettingsComponent(
}
private class ModuleNameComponent(context: Context, module: Module) : TitledComponent(context) {
override val component: JComponent = TextFieldComponent(
private val textField = TextFieldComponent(
context,
labelText = null,
initialValue = module.name,
@@ -73,7 +73,10 @@ private class ModuleNameComponent(context: Context, module: Module) : TitledComp
) { value ->
module.name = value
context.write { eventManager.fireListeners(null) }
}.component
}.asSubComponent()
override val component: JComponent
get() = textField.component
override val title: String = "Name"
@@ -91,7 +94,7 @@ private class ModuleTemplateComponent(
onTemplateChanged: () -> Unit
) : TitledComponent(context) {
@OptIn(ExperimentalStdlibApi::class)
override val component = DropDownComponent(
private val dropDown = DropDownComponent(
context,
initialValues = buildList {
add(NoneTemplate)
@@ -102,7 +105,10 @@ private class ModuleTemplateComponent(
) { value ->
module.template = value.takeIf { it != NoneTemplate }
onTemplateChanged()
}.component
}.asSubComponent()
override val component: JComponent
get() = dropDown.component
override val title: String = "Template"
@@ -24,6 +24,11 @@ class SecondStepWizardComponent(
addToCenter(moduleSettingsComponent.component)
}
override fun navigateTo(error: ValidationResult.ValidationError) {
moduleEditorComponent.navigateTo(error)
moduleSettingsComponent.navigateTo(error)
}
private fun onNodeSelected(data: DisplayableSettingItem?) {
moduleSettingsComponent.selectedNode = data
}
@@ -1,281 +0,0 @@
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.idea.projectWizard.UiEditorUsageStats
import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.moduleType
import org.jetbrains.kotlin.tools.projectWizard.plugins.templates.TemplatesPlugin
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
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.ErrorAwareComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.SettingComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.setting.TitledComponentsList
import java.awt.*
import javax.swing.JComponent
import javax.swing.JPanel
class TemplatesComponent(
context: Context,
uiEditorUsagesStats: UiEditorUsageStats
) : DynamicComponent(context), ErrorAwareComponent {
private val chooseTemplateComponent: ChooseTemplateComponent =
ChooseTemplateComponent(context) { template ->
uiEditorUsagesStats.moduleTemplatesSet++
module?.template = template
switchState(template)
}.asSubComponent()
private val templateSettingsComponent = TemplateSettingsComponent(context) {
if (MessagesEx.showOkCancelDialog(
component,
"Do you want to remove selected template from module",
"Remove selected template",
"Remove",
"Cancel",
null
) == Messages.OK
) {
uiEditorUsagesStats.moduleTemplatesRemoved++
module?.template = null
switchState(null)
}
}.asSubComponent()
override fun findComponentWithError(error: ValidationResult.ValidationError): SettingComponent<*, *>? =
templateSettingsComponent.findComponentWithError(error)
private fun switchState(selectedTemplate: Template?) {
panel.removeAll()
when (selectedTemplate) {
null -> panel.add(chooseTemplateComponent.component, BorderLayout.CENTER)
else -> {
if (module != null) {
templateSettingsComponent.setTemplate(module!!, selectedTemplate)
}
panel.add(templateSettingsComponent.component, BorderLayout.CENTER)
}
}
panel.updateUI()
}
val panel = panel {
add(chooseTemplateComponent.component, BorderLayout.CENTER)
}
override val component: JComponent = panel
var module: Module? = null
set(value) {
field = value
switchState(value?.template)
chooseTemplateComponent.selectedModule = value
}
}
class ChooseTemplateComponent(
context: Context,
private val onTemplateChosen: (Template) -> Unit
) : DynamicComponent(context) {
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() = read {
TemplatesPlugin::templates.propertyValue
}
var selectedModule: Module? = null
set(value) {
field = value
onStateUpdated()
}
init {
onStateUpdated()
}
private val availableTemplates
get() = allTemplates.values.filter { template ->
selectedModule!!.configurator.moduleType in template.moduleTypes
&& template.isApplicableTo(selectedModule!!)
}
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(
context: Context,
removeTemplate: () -> Unit
) : DynamicComponent(context), ErrorAwareComponent {
private val templateDescriptionComponent = TemplateDescriptionComponent(
needRemoveButton = true,
nonDefaultBackgroundColor = UIUtil.getEditorPaneBackground(),
onRemoveButtonClicked = removeTemplate
).apply {
component.bordered(needTopEmptyBorder = false, needBottomEmptyBorder = false)
}
private val settings = TitledComponentsList(emptyList(), context).apply {
component.bordered()
}
override fun findComponentWithError(error: ValidationResult.ValidationError): SettingComponent<*, *>? = null
fun setTemplate(module: Module, selectedTemplate: Template) {
settings.setSettings(selectedTemplate.settings(module))
templateDescriptionComponent.updateSelectedTemplate(selectedTemplate)
}
override val component: JComponent = splitterFor(
templateDescriptionComponent.component,
settings.component,
vertical = true
).apply {
(this as JBSplitter).proportion = .7f
}
}
@@ -1,8 +1,10 @@
package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEditor
import com.intellij.ui.JBColor
import com.intellij.util.ui.JBUI
import org.jetbrains.kotlin.idea.projectWizard.UiEditorUsageStats
import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.ListSettingType
import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.reference
import org.jetbrains.kotlin.tools.projectWizard.plugins.kotlin.KotlinPlugin
@@ -27,6 +29,8 @@ class ModulesEditorComponent(
private val tree: ModulesEditorTree =
ModulesEditorTree(
onSelected = { oneEntrySelected(it) },
context = context,
isTreeEditable = editable,
addModule = { component ->
val isMppProject = KotlinPlugin::projectKind.value == ProjectKind.Singleplatform
moduleCreator.create(
@@ -39,7 +43,9 @@ class ModulesEditorComponent(
createModule = model::add
)?.showInCenterOf(component)
}
)
).apply {
border = JBUI.Borders.emptyRight(10)
}
private val model = TargetsModel(tree, ::value, context, uiEditorUsagesStats)
@@ -52,6 +58,11 @@ class ModulesEditorComponent(
model.update()
}
override fun navigateTo(error: ValidationResult.ValidationError) {
val targetModule = error.target as? Module ?: return
tree.selectModule(targetModule)
}
private val moduleCreator = NewModuleCreator()
private val toolbarDecorator = if (editable) ModulesEditorToolbarDecorator(
@@ -3,23 +3,28 @@ package org.jetbrains.kotlin.tools.projectWizard.wizard.ui.secondStep.modulesEdi
import com.intellij.icons.AllIcons
import com.intellij.ui.ColoredTreeCellRenderer
import com.intellij.ui.SimpleTextAttributes
import com.intellij.ui.components.JBLabel
import com.intellij.ui.treeStructure.Tree
import org.jetbrains.kotlin.tools.projectWizard.core.Context
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
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.settings.buildsystem.path
import org.jetbrains.kotlin.tools.projectWizard.wizard.KotlinNewProjectWizardBundle
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.borderPanel
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.icon
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import java.awt.Component
import java.awt.Graphics2D
import javax.swing.JComponent
import javax.swing.JLabel
import javax.swing.JTree
import javax.swing.tree.DefaultMutableTreeNode
import javax.swing.tree.DefaultTreeModel
import javax.swing.tree.TreePath
import javax.swing.tree.TreeSelectionModel
import javax.swing.tree.*
class ModulesEditorTree(
private val onSelected: (DisplayableSettingItem?) -> Unit,
context: Context,
isTreeEditable: Boolean,
private val addModule: (JComponent) -> Unit
) : Tree(DefaultMutableTreeNode(PROJECT_USER_OBJECT)) {
val model: DefaultTreeModel get() = super.getModel() as DefaultTreeModel
@@ -64,41 +69,81 @@ class ModulesEditorTree(
}
emptyText.clear()
emptyText.appendText(KotlinNewProjectWizardBundle.message("editor.no.modules.created"))
emptyText.appendText("No modules created")
emptyText.appendSecondaryText(
KotlinNewProjectWizardBundle.message("editor.add.module.to.project"),
"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(KotlinNewProjectWizardBundle.message("editor.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)
setCellRenderer(CellRenderer(context, renderErrors = isTreeEditable))
}
}
private class CellRenderer(private val context: Context, renderErrors: Boolean) : TreeCellRenderer {
override fun getTreeCellRendererComponent(
tree: JTree?,
value: Any?,
selected: Boolean,
expanded: Boolean,
leaf: Boolean,
row: Int,
hasFocus: Boolean
): Component {
setError(null)
val renderedCell = coloredCellRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus)
return borderPanel {
addToCenter(renderedCell)
addToRight(iconLabel)
}
}
private val iconLabel = JBLabel("")
private fun setError(error: ValidationResult.ValidationError?) {
val message = error?.messages?.firstOrNull()
if (message == null) {
iconLabel.icon = null
} else {
iconLabel.icon = AllIcons.General.Error
}
}
private val coloredCellRenderer = object : ColoredTreeCellRenderer() {
override fun customizeCellRenderer(
tree: JTree,
value: Any?,
selected: Boolean,
expanded: Boolean,
leaf: Boolean,
row: Int,
hasFocus: Boolean
) {
if (value?.safeAs<DefaultMutableTreeNode>()?.userObject == ModulesEditorTree.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)
}
if (renderErrors) {
if (setting is Module) context.read {
val validationResult = setting.validator.validate(this, setting)
setError(validationResult as? ValidationResult.ValidationError)
} else {
setError(null)
}
}
})
}
}
}
@@ -9,10 +9,6 @@ import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.DynamicComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.FocusableComponent
import org.jetbrains.kotlin.tools.projectWizard.wizard.ui.TitledComponent
interface ErrorAwareComponent {
fun findComponentWithError(error: ValidationResult.ValidationError): FocusableComponent?
}
class TitledComponentsList(
private var components: List<TitledComponent>,
private val context: Context
@@ -25,6 +21,10 @@ class TitledComponentsList(
override val component get() = ui
override fun navigateTo(error: ValidationResult.ValidationError) {
components.forEach { it.navigateTo(error) }
}
override fun onInit() {
super.onInit()
components.forEach { it.onInit() }
@@ -15,6 +15,7 @@ import com.intellij.util.ui.UIUtil
import com.intellij.util.ui.components.BorderLayoutPanel
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.tools.projectWizard.core.Failure
import org.jetbrains.kotlin.tools.projectWizard.core.entity.ValidationResult
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.ModuleConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.SimpleTargetConfigurator
import org.jetbrains.kotlin.tools.projectWizard.moduleConfigurators.TargetConfigurator
@@ -69,11 +70,11 @@ internal fun hyperlinkLabel(
internal fun String.asHtml() = "<html><body>$this</body></html>"
fun Failure.asHtml() = when (errors.size) {
fun ValidationResult.ValidationError.asHtml() = when (messages.size) {
0 -> ""
1 -> errors.single().message
1 -> messages.single()
else -> {
val errorsList = errors.joinToString(separator = "") { "<li>${it.message}</li>" }
val errorsList = messages.joinToString(separator = "") { "<li>${it}</li>" }
"<ul>$errorsList</ul>".asHtml()
}
}
@@ -61,7 +61,7 @@ sealed class ValidationResult {
infix fun and(other: ValidationResult) = when {
this is OK -> other
this is ValidationError && other is ValidationError -> ValidationError(messages + other.messages)
this is ValidationError && other is ValidationError -> ValidationError(messages + other.messages, target ?: other.target)
else -> this
}
@@ -8,6 +8,7 @@ import org.jetbrains.kotlin.tools.projectWizard.core.entity.settings.reference
import org.jetbrains.kotlin.tools.projectWizard.core.service.FileSystemWizardService
import org.jetbrains.kotlin.tools.projectWizard.ir.buildsystem.PomIR
import org.jetbrains.kotlin.tools.projectWizard.phases.GenerationPhase
import org.jetbrains.kotlin.tools.projectWizard.settings.buildsystem.Module
import org.jetbrains.kotlin.tools.projectWizard.settings.version.Version
import java.nio.file.Paths
@@ -15,7 +16,10 @@ class StructurePlugin(context: Context) : Plugin(context) {
val projectPath by pathSetting("Location", GenerationPhase.PROJECT_GENERATION) {
defaultValue = value(Paths.get("."))
}
val name by stringSetting("Name", GenerationPhase.PROJECT_GENERATION)
val name by stringSetting("Name", GenerationPhase.PROJECT_GENERATION) {
shouldNotBeBlank()
validate(StringValidators.shouldBeValidIdentifier("Name", Module.ALLOWED_SPECIAL_CHARS_IN_MODULE_NAMES))
}
val groupId by stringSetting("Group ID", GenerationPhase.PROJECT_GENERATION) {
isSavable = true
@@ -39,13 +39,13 @@ abstract class Wizard(createPlugins: PluginsCreator, servicesManager: ServicesMa
}
}
fun validate(phases: Set<GenerationPhase>): TaskResult<Unit> = context.read {
fun validate(phases: Set<GenerationPhase>): ValidationResult = context.read {
pluginSettings.map { setting ->
val value = setting.reference.notRequiredSettingValue ?: return@map ValidationResult.OK
if (setting.neededAtPhase in phases && setting.isActive(this))
(setting.validator as SettingValidator<Any>).validate(this, value)
else ValidationResult.OK
}.fold().toResult()
}.fold()
}
private fun saveSettingValues(phases: Set<GenerationPhase>) = context.read {
@@ -69,7 +69,7 @@ abstract class Wizard(createPlugins: PluginsCreator, servicesManager: ServicesMa
initPluginSettingsDefaultValues()
initNonPluginDefaultValues()
checkAllRequiredSettingPresent(phases).ensure()
validate(phases).ensure()
validate(phases).toResult().ensure()
saveSettingValues(phases)
val (tasksSorted) = context.sortTasks().map { tasks ->
tasks.groupBy { it.phase }.toList().sortedBy { it.first }.flatMap { it.second }