@@ -17,11 +17,15 @@ import com.intellij.openapi.Disposable
17
17
import com.intellij.openapi.application.ApplicationManager
18
18
import com.intellij.openapi.diagnostic.Logger
19
19
import com.intellij.openapi.ui.ComboBox
20
+ import com.intellij.openapi.ui.ComponentValidator
21
+ import com.intellij.openapi.ui.ValidationInfo
22
+ import com.intellij.openapi.util.Disposer
20
23
import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenUIManager
21
24
import com.intellij.remote.AuthType
22
25
import com.intellij.remote.RemoteCredentialsHolder
23
26
import com.intellij.ui.AnimatedIcon
24
27
import com.intellij.ui.ColoredListCellRenderer
28
+ import com.intellij.ui.DocumentAdapter
25
29
import com.intellij.ui.components.JBTextField
26
30
import com.intellij.ui.dsl.builder.BottomGap
27
31
import com.intellij.ui.dsl.builder.RowLayout
@@ -30,6 +34,8 @@ import com.intellij.ui.dsl.builder.panel
30
34
import com.intellij.ui.dsl.gridLayout.HorizontalAlign
31
35
import com.intellij.util.ui.JBFont
32
36
import com.intellij.util.ui.UIUtil
37
+ import com.intellij.util.ui.update.MergingUpdateQueue
38
+ import com.intellij.util.ui.update.Update
33
39
import com.jetbrains.gateway.api.GatewayUI
34
40
import com.jetbrains.gateway.ssh.CachingProductsJsonWrapper
35
41
import com.jetbrains.gateway.ssh.DeployTargetOS
@@ -47,6 +53,7 @@ import kotlinx.coroutines.async
47
53
import kotlinx.coroutines.cancel
48
54
import kotlinx.coroutines.cancelAndJoin
49
55
import kotlinx.coroutines.launch
56
+ import kotlinx.coroutines.runBlocking
50
57
import kotlinx.coroutines.withContext
51
58
import java.awt.Component
52
59
import java.awt.FlowLayout
@@ -58,6 +65,7 @@ import javax.swing.JList
58
65
import javax.swing.JPanel
59
66
import javax.swing.ListCellRenderer
60
67
import javax.swing.SwingConstants
68
+ import javax.swing.event.DocumentEvent
61
69
62
70
class CoderLocateRemoteProjectStepView (private val disableNextAction : () -> Unit ) : CoderWorkspacesWizardStep, Disposable {
63
71
private val cs = CoroutineScope (Dispatchers .Main )
@@ -68,10 +76,10 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
68
76
private lateinit var titleLabel: JLabel
69
77
private lateinit var wizard: CoderWorkspacesWizardModel
70
78
private lateinit var cbIDE: IDEComboBox
71
- private lateinit var tfProject: JBTextField
79
+ private var tfProject = JBTextField ()
72
80
private lateinit var terminalLink: LazyBrowserLink
73
-
74
81
private lateinit var ideResolvingJob: Job
82
+ private val pathValidationJobs = MergingUpdateQueue (" remote-path-validation" , 1000 , true , tfProject)
75
83
76
84
override val component = panel {
77
85
indent {
@@ -92,9 +100,7 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
92
100
93
101
row {
94
102
label(" Project directory:" )
95
- tfProject = textField()
96
- .resizableColumn()
97
- .horizontalAlign(HorizontalAlign .FILL ).component
103
+ cell(tfProject).resizableColumn().horizontalAlign(HorizontalAlign .FILL ).component
98
104
cell()
99
105
}.topGap(TopGap .NONE ).bottomGap(BottomGap .NONE ).layout(RowLayout .PARENT_GRID )
100
106
row {
@@ -113,6 +119,7 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
113
119
override val nextActionText = CoderGatewayBundle .message(" gateway.connector.view.coder.remoteproject.next.text" )
114
120
115
121
override fun onInit (wizardModel : CoderWorkspacesWizardModel ) {
122
+ cbIDE.renderer = IDECellRenderer ()
116
123
ideComboBoxModel.removeAllElements()
117
124
wizard = wizardModel
118
125
val selectedWorkspace = wizardModel.selectedWorkspace
@@ -127,7 +134,11 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
127
134
128
135
ideResolvingJob = cs.launch {
129
136
try {
130
- retrieveIDES(selectedWorkspace)
137
+ val executor = withContext(Dispatchers .IO ) { createRemoteExecutor() }
138
+ retrieveIDES(executor, selectedWorkspace)
139
+ if (ComponentValidator .getInstance(tfProject).isEmpty) {
140
+ installRemotePathValidator(executor)
141
+ }
131
142
} catch (e: Exception ) {
132
143
when (e) {
133
144
is InterruptedException -> Unit
@@ -150,23 +161,56 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
150
161
}
151
162
}
152
163
153
- private suspend fun retrieveIDES (selectedWorkspace : WorkspaceAgentModel ) {
154
- logger.info(" Retrieving available IDE's for ${selectedWorkspace.name} workspace..." )
155
- val hostAccessor = HighLevelHostAccessor .create(
164
+ private fun installRemotePathValidator (executor : HighLevelHostAccessor ) {
165
+ var disposable = Disposer .newDisposable(ApplicationManager .getApplication(), CoderLocateRemoteProjectStepView .javaClass.name)
166
+ ComponentValidator (disposable).installOn(tfProject)
167
+
168
+ tfProject.document.addDocumentListener(object : DocumentAdapter () {
169
+ override fun textChanged (event : DocumentEvent ) {
170
+ pathValidationJobs.queue(Update .create(" validate-remote-path" ) {
171
+ runBlocking {
172
+ try {
173
+ val isPathPresent = executor.isPathPresentOnRemote(tfProject.text)
174
+ if (! isPathPresent) {
175
+ ComponentValidator .getInstance(tfProject).ifPresent {
176
+ it.updateInfo(ValidationInfo (" Can't find directory: ${tfProject.text} " , tfProject))
177
+ }
178
+ } else {
179
+ ComponentValidator .getInstance(tfProject).ifPresent {
180
+ it.updateInfo(null )
181
+ }
182
+ }
183
+ } catch (e: Exception ) {
184
+ ComponentValidator .getInstance(tfProject).ifPresent {
185
+ it.updateInfo(ValidationInfo (" Can't validate directory: ${tfProject.text} " , tfProject))
186
+ }
187
+ }
188
+ }
189
+ })
190
+ }
191
+ })
192
+ }
193
+
194
+ private suspend fun createRemoteExecutor (): HighLevelHostAccessor {
195
+ return HighLevelHostAccessor .create(
156
196
RemoteCredentialsHolder ().apply {
157
- setHost(" coder.${selectedWorkspace.name} " )
197
+ setHost(" coder.${wizard. selectedWorkspace? .name} " )
158
198
userName = " coder"
159
199
authType = AuthType .OPEN_SSH
160
200
},
161
201
true
162
202
)
203
+ }
204
+
205
+ private suspend fun retrieveIDES (executor : HighLevelHostAccessor , selectedWorkspace : WorkspaceAgentModel ) {
206
+ logger.info(" Retrieving available IDE's for ${selectedWorkspace.name} workspace..." )
163
207
val workspaceOS = if (selectedWorkspace.agentOS != null && selectedWorkspace.agentArch != null ) toDeployedOS(selectedWorkspace.agentOS, selectedWorkspace.agentArch) else withContext(Dispatchers .IO ) {
164
- hostAccessor .guessOs()
208
+ executor .guessOs()
165
209
}
166
210
167
211
logger.info(" Resolved OS and Arch for ${selectedWorkspace.name} is: $workspaceOS " )
168
212
val installedIdesJob = cs.async(Dispatchers .IO ) {
169
- hostAccessor .getInstalledIDEs().map { ide -> IdeWithStatus (ide.product, ide.buildNumber, IdeStatus .ALREADY_INSTALLED , null , ide.pathToIde, ide.presentableVersion, ide.remoteDevType) }
213
+ executor .getInstalledIDEs().map { ide -> IdeWithStatus (ide.product, ide.buildNumber, IdeStatus .ALREADY_INSTALLED , null , ide.pathToIde, ide.presentableVersion, ide.remoteDevType) }
170
214
}
171
215
val idesWithStatusJob = cs.async(Dispatchers .IO ) {
172
216
IntelliJPlatformProduct .values()
0 commit comments