Skip to content

Commit e8f7abb

Browse files
committed
validate tests, build, remote paths in wizard and settings
1 parent 0fd891d commit e8f7abb

File tree

10 files changed

+172
-40
lines changed

10 files changed

+172
-40
lines changed

clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/settings/UTBotConfigurable.kt

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ package org.utbot.cpp.clion.plugin.settings
55
import com.intellij.openapi.components.service
66
import com.intellij.openapi.diagnostic.Logger
77
import com.intellij.openapi.options.BoundConfigurable
8+
import com.intellij.openapi.options.ConfigurationException
89
import com.intellij.openapi.project.Project
910
import com.intellij.openapi.ui.DialogPanel
1011
import com.intellij.ui.JBIntSpinner
1112
import com.intellij.ui.components.JBTextField
1213
import com.intellij.ui.dsl.builder.BottomGap
1314
import com.intellij.ui.dsl.builder.COLUMNS_LARGE
15+
import com.intellij.ui.dsl.builder.Cell
1416
import com.intellij.ui.dsl.builder.LabelPosition
1517
import com.intellij.ui.dsl.builder.Panel
1618
import com.intellij.ui.dsl.builder.bindIntValue
@@ -22,7 +24,12 @@ import kotlin.reflect.KMutableProperty0
2224
import org.utbot.cpp.clion.plugin.UTBot
2325
import org.utbot.cpp.clion.plugin.listeners.UTBotSettingsChangedListener
2426
import org.utbot.cpp.clion.plugin.ui.sourceFoldersView.UTBotProjectViewPaneForSettings
27+
import org.utbot.cpp.clion.plugin.utils.ComponentValidationInfo
28+
import org.utbot.cpp.clion.plugin.utils.ValidationCondition
29+
import org.utbot.cpp.clion.plugin.utils.addValidation
2530
import org.utbot.cpp.clion.plugin.utils.commandLineEditor
31+
import org.utbot.cpp.clion.plugin.utils.isLookLikeUnixPath
32+
import org.utbot.cpp.clion.plugin.utils.isValidHostName
2633
import org.utbot.cpp.clion.plugin.utils.projectLifetimeDisposable
2734
import java.awt.Dimension
2835

@@ -33,6 +40,7 @@ class UTBotConfigurable(private val myProject: Project) : BoundConfigurable(
3340
private val panel by lazy { createMainPanel() }
3441

3542
private val settings: UTBotProjectStoredSettings = myProject.service()
43+
private val validationInfos: MutableList<ComponentValidationInfo> = mutableListOf()
3644
private lateinit var portComponent: JBIntSpinner
3745
private lateinit var serverNameTextField: JBTextField
3846

@@ -44,6 +52,13 @@ class UTBotConfigurable(private val myProject: Project) : BoundConfigurable(
4452
})
4553
}
4654

55+
private fun <T : JBTextField> Cell<T>.validateInput(vararg conditions: ValidationCondition): Cell<T> {
56+
return this.apply {
57+
validationInfos.add(this.addValidation(*conditions))
58+
}
59+
}
60+
61+
4762
override fun createPanel() = panel
4863

4964
private fun createMainPanel(): DialogPanel {
@@ -69,17 +84,28 @@ class UTBotConfigurable(private val myProject: Project) : BoundConfigurable(
6984
row(UTBot.message("settings.project.serverName")) {
7085
textField().bindText(projectIndependentSettings::serverName).applyToComponent {
7186
serverNameTextField = this
72-
}
87+
}.validateInput(
88+
ValidationCondition(
89+
UTBot.message("validation.invalid.host")
90+
) { it.text.isValidHostName() }
91+
)
7392
}.rowComment(UTBot.message("deployment.utbotHost.description"))
7493

7594
row(UTBot.message("settings.project.remotePath")) {
76-
textField().bindText(settings::remotePath).columns(COLUMNS_LARGE)
95+
textField().bindText(settings::remotePath).columns(COLUMNS_LARGE).validateInput(
96+
ValidationCondition(UTBot.message("validation.not.empty")) { it.text.isNotEmpty() },
97+
ValidationCondition(UTBot.message("validation.not.unix.path")) { it.text.isLookLikeUnixPath() }
98+
)
7799
}.rowComment(UTBot.message("deployment.remotePath.description"))
78100
}
79101

80102
private fun Panel.createPathsSettings() {
81103
row(UTBot.message("settings.project.buildDir")) {
104+
val validator: (JBTextField) -> Boolean = {
105+
it.text.isNotEmpty()
106+
}
82107
textField().bindText(settings::buildDirRelativePath).columns(COLUMNS_LARGE)
108+
.validateInput(ValidationCondition(UTBot.message("validation.not.empty")) { it.text.isNotEmpty() })
83109
}.rowComment(UTBot.message("paths.buildDirectory.description"))
84110

85111
row(UTBot.message("settings.project.target")) {
@@ -93,6 +119,7 @@ class UTBotConfigurable(private val myProject: Project) : BoundConfigurable(
93119

94120
row(UTBot.message("settings.project.testsDir")) {
95121
textField().bindText(settings::testDirRelativePath).columns(COLUMNS_LARGE)
122+
.validateInput(ValidationCondition(UTBot.message("validation.not.empty")) { it.text.isNotEmpty() })
96123
}.rowComment(UTBot.message("paths.testsDir.description"))
97124

98125
row {
@@ -179,6 +206,12 @@ class UTBotConfigurable(private val myProject: Project) : BoundConfigurable(
179206
}
180207

181208
override fun apply() {
209+
val invalidComponentValidationInfo = validationInfos.find { !it.isValid() }
210+
if (invalidComponentValidationInfo != null) {
211+
panel.scrollRectToVisible(invalidComponentValidationInfo.component.visibleRect)
212+
invalidComponentValidationInfo.component.requestFocus()
213+
throw ConfigurationException("Some fields have invalid values")
214+
}
182215
val wereConnectionSettingsModified =
183216
portComponent.number != projectIndependentSettings.port || serverNameTextField.text != projectIndependentSettings.serverName
184217
panel.apply()
@@ -194,4 +227,4 @@ class UTBotConfigurable(private val myProject: Project) : BoundConfigurable(
194227
companion object {
195228
val TEXT_FIELD_MAX_SIZE = Dimension(370, 100)
196229
}
197-
}
230+
}

clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/wizard/UTBotBaseWizardStep.kt

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
11
package org.utbot.cpp.clion.plugin.ui.wizard
22

33
import com.intellij.ide.wizard.Step
4+
import com.intellij.openapi.Disposable
45
import com.intellij.openapi.ui.DialogPanel
6+
import com.intellij.ui.components.JBTextField
7+
import com.intellij.ui.dsl.builder.Cell
58
import com.intellij.util.ui.HtmlPanel
69
import com.intellij.util.ui.UIUtil
710
import javax.swing.Box
811
import javax.swing.BoxLayout
912
import javax.swing.Icon
1013
import javax.swing.JComponent
1114
import javax.swing.JPanel
15+
import org.utbot.cpp.clion.plugin.utils.ComponentValidationInfo
16+
import org.utbot.cpp.clion.plugin.utils.ValidationCondition
17+
import org.utbot.cpp.clion.plugin.utils.validateInput
1218
import java.awt.Component
1319
import java.awt.Dimension
1420
import java.awt.Font
1521

16-
abstract class UTBotBaseWizardStep : Step {
22+
abstract class UTBotBaseWizardStep(private val parentDisposable: Disposable) : Step {
1723
private val panel by lazy { JPanel() }
1824
private var isInitialized = false
19-
25+
private val validators = mutableListOf<ComponentValidationInfo>()
2026
private val onApplyCallbacks = mutableListOf<() -> Unit>()
21-
27+
class Validator(val component: JComponent, val isValid: ()->Boolean)
2228
abstract fun createUI()
2329

2430
override fun _init() {
@@ -39,6 +45,12 @@ abstract class UTBotBaseWizardStep : Step {
3945
addComponentToStep(this)
4046
}
4147

48+
protected fun <T : JBTextField> Cell<T>.validateWith(vararg conditions: ValidationCondition): Cell<T> {
49+
return this.applyToComponent {
50+
validators.add(this.validateInput(parentDisposable, *conditions))
51+
}
52+
}
53+
4254
override fun _commit(finishChosen: Boolean) = onApplyCallbacks.forEach { it.invoke() }
4355

4456
//TODO: find a good icon and use here
@@ -60,7 +72,15 @@ abstract class UTBotBaseWizardStep : Step {
6072
panel.add(component)
6173
}
6274

63-
open fun canProceedToNextStep(): Boolean = true
75+
protected fun validate(): JComponent? {
76+
return validators.find { !it.isValid() }?.component
77+
}
78+
79+
open fun canProceedToNextStep(): Boolean {
80+
val validationResult = validate()
81+
validationResult?.requestFocus()
82+
return validationResult == null
83+
}
6484

6585
private fun createHtmlComponent(html: String): JComponent = object : HtmlPanel() {
6686
init {

clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/wizard/UTBotWizard.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package org.utbot.cpp.clion.plugin.ui.wizard
33
import com.intellij.ide.BrowserUtil
44
import com.intellij.ide.wizard.AbstractWizard
55
import com.intellij.openapi.project.Project
6-
import javax.swing.JButton
76
import org.utbot.cpp.clion.plugin.UTBot
87
import org.utbot.cpp.clion.plugin.settings.UTBotSettingsModel
98
import org.utbot.cpp.clion.plugin.settings.projectIndependentSettings
@@ -17,13 +16,14 @@ import java.awt.event.KeyEvent
1716

1817
class UTBotWizard(private val project: Project) : AbstractWizard<UTBotBaseWizardStep>("UTBot: Quickstart", project) {
1918
// copy of settings to make changes during wizard steps
20-
private val mySettingsModel = UTBotSettingsModel(project.settings.storedSettings.copy(), projectIndependentSettings.copy())
19+
private val mySettingsModel =
20+
UTBotSettingsModel(project.settings.storedSettings.copy(), projectIndependentSettings.copy())
2121

2222
init {
23-
addStep(IntroStep())
24-
addStep(ConnectionStep(project, mySettingsModel))
25-
addStep(BuildOptionsStep(mySettingsModel))
26-
addStep(FinalStep())
23+
addStep(IntroStep(disposable))
24+
addStep(ConnectionStep(disposable, project, mySettingsModel))
25+
addStep(BuildOptionsStep(disposable, mySettingsModel))
26+
addStep(FinalStep(disposable))
2727
super.init()
2828
isResizable = true
2929
setSize(400, 400)
@@ -56,6 +56,7 @@ class UTBotWizard(private val project: Project) : AbstractWizard<UTBotBaseWizard
5656
}
5757
}
5858

59+
5960
override fun proceedToNextStep() {
6061
if (currentStepObject?.canProceedToNextStep() != false) {
6162
super.proceedToNextStep()

clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/wizard/steps/BuildOptionsStep.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,26 @@
22

33
package org.utbot.cpp.clion.plugin.ui.wizard.steps
44

5+
import com.intellij.openapi.Disposable
56
import com.intellij.ui.dsl.builder.COLUMNS_LARGE
67
import com.intellij.ui.dsl.builder.bindText
78
import com.intellij.ui.dsl.builder.columns
89
import com.intellij.ui.dsl.builder.panel
10+
import org.utbot.cpp.clion.plugin.UTBot
911
import org.utbot.cpp.clion.plugin.settings.UTBotSettingsModel
1012
import org.utbot.cpp.clion.plugin.ui.wizard.UTBotBaseWizardStep
13+
import org.utbot.cpp.clion.plugin.utils.ValidationCondition
1114
import org.utbot.cpp.clion.plugin.utils.commandLineEditor
1215

13-
class BuildOptionsStep(private val settingsModel: UTBotSettingsModel) : UTBotBaseWizardStep() {
16+
class BuildOptionsStep(parentDisposable: Disposable, private val settingsModel: UTBotSettingsModel) : UTBotBaseWizardStep(parentDisposable) {
1417
override fun createUI() {
1518
addHtml("media/options_wizard_text.html")
1619
panel {
1720
row("Relative path to Build directory") {
1821
textField().bindText(settingsModel.projectSettings::buildDirRelativePath).columns(COLUMNS_LARGE)
22+
.validateWith(ValidationCondition(UTBot.message("validation.not.empty")) { it.text.isNotEmpty() })
1923
}
24+
2025
}.addToUI()
2126
addHtml("media/cmake_options.html")
2227
panel {

clion-plugin/src/main/kotlin/org/utbot/cpp/clion/plugin/ui/wizard/steps/ConnectionStep.kt

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
package org.utbot.cpp.clion.plugin.ui.wizard.steps
44

5+
import com.intellij.openapi.Disposable
56
import com.intellij.openapi.project.Project
67
import com.intellij.openapi.ui.DialogWrapper
78
import com.intellij.ui.AnimatedIcon
@@ -30,8 +31,13 @@ import org.utbot.cpp.clion.plugin.ui.wizard.UTBotBaseWizardStep
3031
import org.utbot.cpp.clion.plugin.utils.toWslFormatIfNeeded
3132
import javax.swing.JComponent
3233
import javax.swing.event.DocumentEvent
34+
import kotlinx.coroutines.CoroutineExceptionHandler
35+
import org.utbot.cpp.clion.plugin.UTBot
3336
import org.utbot.cpp.clion.plugin.settings.UTBotProjectStoredSettings
3437
import org.utbot.cpp.clion.plugin.ui.ObservableValue
38+
import org.utbot.cpp.clion.plugin.utils.ValidationCondition
39+
import org.utbot.cpp.clion.plugin.utils.isLookLikeUnixPath
40+
import org.utbot.cpp.clion.plugin.utils.isValidHostName
3541
import org.utbot.cpp.clion.plugin.utils.isWindows
3642
import org.utbot.cpp.clion.plugin.utils.ourPluginVersion
3743
import org.utbot.cpp.clion.plugin.utils.path
@@ -44,9 +50,10 @@ enum class ConnectionStatus {
4450
}
4551

4652
class ConnectionStep(
53+
parentDisposable: Disposable,
4754
private val project: Project,
4855
private val settingsModel: UTBotSettingsModel,
49-
) : UTBotBaseWizardStep() {
56+
) : UTBotBaseWizardStep(parentDisposable) {
5057
private lateinit var hostTextField: JBTextField
5158
private lateinit var portComponent: JBIntSpinner
5259
private lateinit var remotePathTextField: JBTextField
@@ -57,7 +64,8 @@ class ConnectionStep(
5764
private val useConnectionDefaults = ObservableValue(false)
5865

5966
inner class ConnectionInfo(val port: Int, val host: String, val remotePath: String) {
60-
constructor(): this(portComponent.number, hostTextField.text, remotePathTextField.text)
67+
constructor() : this(portComponent.number, hostTextField.text, remotePathTextField.text)
68+
6169
fun apply() {
6270
portComponent.number = port
6371
hostTextField.text = host
@@ -69,7 +77,7 @@ class ConnectionStep(
6977
UTBotAllProjectSettings.DEFAULT_PORT,
7078
UTBotAllProjectSettings.DEFAULT_HOST,
7179
if (isWindows) project.path.toWslFormatIfNeeded()
72-
else UTBotProjectStoredSettings.REMOTE_PATH_VALUE_FOR_LOCAL_SCENARIO
80+
else UTBotProjectStoredSettings.REMOTE_PATH_VALUE_FOR_LOCAL_SCENARIO
7381
)
7482

7583
private var beforeCheckingBoxConnectionInfo: ConnectionInfo? = null
@@ -91,10 +99,16 @@ class ConnectionStep(
9199
}
92100

93101
override fun canProceedToNextStep(): Boolean {
94-
if (connectionStatus.value != ConnectionStatus.Failed) {
102+
val validationResult = validate()
103+
if (connectionStatus.value != ConnectionStatus.Failed && validationResult == null) {
95104
return true
96105
}
97106

107+
if (validationResult != null) {
108+
validationResult.requestFocus()
109+
return false
110+
}
111+
98112
return NotConnectedWarningDialog(project).showAndGet()
99113
}
100114

@@ -119,7 +133,11 @@ class ConnectionStep(
119133
override fun addListener(listener: (Boolean) -> Unit) {
120134
useConnectionDefaults.addOnChangeListener { newValue -> listener(!newValue) }
121135
}
122-
})
136+
}).validateWith(
137+
ValidationCondition(
138+
UTBot.message("validation.invalid.host")
139+
) { it.text.isValidHostName() }
140+
)
123141
}
124142

125143
row("Port") {
@@ -194,7 +212,10 @@ class ConnectionStep(
194212
override fun addListener(listener: (Boolean) -> Unit) {
195213
useConnectionDefaults.addOnChangeListener { newValue -> listener(!newValue) }
196214
}
197-
})
215+
}).validateWith(
216+
ValidationCondition(UTBot.message("validation.not.empty")) { it.text.isNotEmpty() },
217+
ValidationCondition(UTBot.message("validation.not.unix.path")) { it.text.isLookLikeUnixPath() }
218+
)
198219
}
199220
}.addToUI()
200221
}
@@ -221,15 +242,19 @@ class ConnectionStep(
221242
}
222243

223244
private fun pingServer() {
224-
CoroutineScope(Dispatchers.IO + SupervisorJob()).launch {
245+
val handler = CoroutineExceptionHandler { _, e ->
246+
e.printStackTrace()
247+
}
248+
CoroutineScope(Dispatchers.IO + SupervisorJob() + handler).launch {
225249
connectionStatus.value = pingServer(portComponent.number, hostTextField.text)
226250
}
227251
}
228252

229253
private fun setupPingOnPortOrHostChange() {
230254
hostTextField.document.addDocumentListener(object : DocumentAdapter() {
231255
override fun textChanged(e: DocumentEvent) {
232-
pingServer()
256+
if (hostTextField.text.isValidHostName())
257+
pingServer()
233258
}
234259
})
235260
portComponent.addChangeListener {
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package org.utbot.cpp.clion.plugin.ui.wizard.steps
22

3+
import com.intellij.openapi.Disposable
34
import org.utbot.cpp.clion.plugin.ui.wizard.UTBotBaseWizardStep
45

5-
class FinalStep : UTBotBaseWizardStep() {
6+
class FinalStep(parentDisposable: Disposable) : UTBotBaseWizardStep(parentDisposable) {
67
override fun createUI() = addHtml("media/final_wizard_text.html")
78
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package org.utbot.cpp.clion.plugin.ui.wizard.steps
22

3+
import com.intellij.openapi.Disposable
34
import org.utbot.cpp.clion.plugin.ui.wizard.UTBotBaseWizardStep
45

5-
class IntroStep : UTBotBaseWizardStep() {
6+
class IntroStep(parentDisposable: Disposable) : UTBotBaseWizardStep(parentDisposable) {
67
override fun createUI() = addHtml("media/intro_wizard_text.html")
78
}

0 commit comments

Comments
 (0)