@@ -17,11 +17,15 @@ import com.intellij.openapi.Disposable
1717import com.intellij.openapi.application.ApplicationManager
1818import com.intellij.openapi.diagnostic.Logger
1919import 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
2023import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenUIManager
2124import com.intellij.remote.AuthType
2225import com.intellij.remote.RemoteCredentialsHolder
2326import com.intellij.ui.AnimatedIcon
2427import com.intellij.ui.ColoredListCellRenderer
28+ import com.intellij.ui.DocumentAdapter
2529import com.intellij.ui.components.JBTextField
2630import com.intellij.ui.dsl.builder.BottomGap
2731import com.intellij.ui.dsl.builder.RowLayout
@@ -30,6 +34,8 @@ import com.intellij.ui.dsl.builder.panel
3034import com.intellij.ui.dsl.gridLayout.HorizontalAlign
3135import com.intellij.util.ui.JBFont
3236import com.intellij.util.ui.UIUtil
37+ import com.intellij.util.ui.update.MergingUpdateQueue
38+ import com.intellij.util.ui.update.Update
3339import com.jetbrains.gateway.api.GatewayUI
3440import com.jetbrains.gateway.ssh.CachingProductsJsonWrapper
3541import com.jetbrains.gateway.ssh.DeployTargetOS
@@ -47,6 +53,7 @@ import kotlinx.coroutines.async
4753import kotlinx.coroutines.cancel
4854import kotlinx.coroutines.cancelAndJoin
4955import kotlinx.coroutines.launch
56+ import kotlinx.coroutines.runBlocking
5057import kotlinx.coroutines.withContext
5158import java.awt.Component
5259import java.awt.FlowLayout
@@ -58,6 +65,7 @@ import javax.swing.JList
5865import javax.swing.JPanel
5966import javax.swing.ListCellRenderer
6067import javax.swing.SwingConstants
68+ import javax.swing.event.DocumentEvent
6169
6270class CoderLocateRemoteProjectStepView (private val disableNextAction : () -> Unit ) : CoderWorkspacesWizardStep, Disposable {
6371 private val cs = CoroutineScope (Dispatchers .Main )
@@ -68,10 +76,10 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
6876 private lateinit var titleLabel: JLabel
6977 private lateinit var wizard: CoderWorkspacesWizardModel
7078 private lateinit var cbIDE: IDEComboBox
71- private lateinit var tfProject: JBTextField
79+ private var tfProject = JBTextField ()
7280 private lateinit var terminalLink: LazyBrowserLink
73-
7481 private lateinit var ideResolvingJob: Job
82+ private val pathValidationJobs = MergingUpdateQueue (" remote-path-validation" , 1000 , true , tfProject)
7583
7684 override val component = panel {
7785 indent {
@@ -92,9 +100,7 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
92100
93101 row {
94102 label(" Project directory:" )
95- tfProject = textField()
96- .resizableColumn()
97- .horizontalAlign(HorizontalAlign .FILL ).component
103+ cell(tfProject).resizableColumn().horizontalAlign(HorizontalAlign .FILL ).component
98104 cell()
99105 }.topGap(TopGap .NONE ).bottomGap(BottomGap .NONE ).layout(RowLayout .PARENT_GRID )
100106 row {
@@ -113,6 +119,7 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
113119 override val nextActionText = CoderGatewayBundle .message(" gateway.connector.view.coder.remoteproject.next.text" )
114120
115121 override fun onInit (wizardModel : CoderWorkspacesWizardModel ) {
122+ cbIDE.renderer = IDECellRenderer ()
116123 ideComboBoxModel.removeAllElements()
117124 wizard = wizardModel
118125 val selectedWorkspace = wizardModel.selectedWorkspace
@@ -127,7 +134,11 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
127134
128135 ideResolvingJob = cs.launch {
129136 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+ }
131142 } catch (e: Exception ) {
132143 when (e) {
133144 is InterruptedException -> Unit
@@ -150,23 +161,56 @@ class CoderLocateRemoteProjectStepView(private val disableNextAction: () -> Unit
150161 }
151162 }
152163
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(
156196 RemoteCredentialsHolder ().apply {
157- setHost(" coder.${selectedWorkspace.name} " )
197+ setHost(" coder.${wizard. selectedWorkspace? .name} " )
158198 userName = " coder"
159199 authType = AuthType .OPEN_SSH
160200 },
161201 true
162202 )
203+ }
204+
205+ private suspend fun retrieveIDES (executor : HighLevelHostAccessor , selectedWorkspace : WorkspaceAgentModel ) {
206+ logger.info(" Retrieving available IDE's for ${selectedWorkspace.name} workspace..." )
163207 val workspaceOS = if (selectedWorkspace.agentOS != null && selectedWorkspace.agentArch != null ) toDeployedOS(selectedWorkspace.agentOS, selectedWorkspace.agentArch) else withContext(Dispatchers .IO ) {
164- hostAccessor .guessOs()
208+ executor .guessOs()
165209 }
166210
167211 logger.info(" Resolved OS and Arch for ${selectedWorkspace.name} is: $workspaceOS " )
168212 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) }
170214 }
171215 val idesWithStatusJob = cs.async(Dispatchers .IO ) {
172216 IntelliJPlatformProduct .values()
0 commit comments