@@ -4,6 +4,7 @@ import com.coder.gateway.CoderGatewayBundle
44import com.coder.gateway.cli.CoderCLIManager
55import com.coder.gateway.icons.CoderIcons
66import com.coder.gateway.models.WorkspaceProjectIDE
7+ import com.coder.gateway.models.filterOutAvailableReleasedIdes
78import com.coder.gateway.models.toIdeWithStatus
89import com.coder.gateway.models.withWorkspaceProject
910import com.coder.gateway.sdk.v2.models.Workspace
@@ -82,9 +83,12 @@ import javax.swing.SwingConstants
8283import javax.swing.event.DocumentEvent
8384
8485// Just extracting the way we display the IDE info into a helper function.
85- private fun displayIdeWithStatus (ideWithStatus : IdeWithStatus ): String = " ${ideWithStatus.product.productCode} ${ideWithStatus.presentableVersion} ${ideWithStatus.buildNumber} | ${ideWithStatus.status.name.lowercase(
86- Locale .getDefault(),
87- )} "
86+ private fun displayIdeWithStatus (ideWithStatus : IdeWithStatus ): String =
87+ " ${ideWithStatus.product.productCode} ${ideWithStatus.presentableVersion} ${ideWithStatus.buildNumber} | ${
88+ ideWithStatus.status.name.lowercase(
89+ Locale .getDefault(),
90+ )
91+ } "
8892
8993/* *
9094 * View for a single workspace. In particular, show available IDEs and a button
@@ -222,12 +226,21 @@ class CoderWorkspaceProjectIDEStepView(
222226 cbIDE.renderer =
223227 if (attempt > 1 ) {
224228 IDECellRenderer (
225- CoderGatewayBundle .message(" gateway.connector.view.coder.connect-ssh.retry" , attempt),
229+ CoderGatewayBundle .message(
230+ " gateway.connector.view.coder.connect-ssh.retry" ,
231+ attempt
232+ ),
226233 )
227234 } else {
228235 IDECellRenderer (CoderGatewayBundle .message(" gateway.connector.view.coder.connect-ssh" ))
229236 }
230- val executor = createRemoteExecutor(CoderCLIManager (data.client.url).getBackgroundHostName(data.workspace, data.client.me, data.agent))
237+ val executor = createRemoteExecutor(
238+ CoderCLIManager (data.client.url).getBackgroundHostName(
239+ data.workspace,
240+ data.client.me,
241+ data.agent
242+ )
243+ )
231244
232245 if (ComponentValidator .getInstance(tfProject).isEmpty) {
233246 logger.info(" Installing remote path validator..." )
@@ -238,7 +251,10 @@ class CoderWorkspaceProjectIDEStepView(
238251 cbIDE.renderer =
239252 if (attempt > 1 ) {
240253 IDECellRenderer (
241- CoderGatewayBundle .message(" gateway.connector.view.coder.retrieve-ides.retry" , attempt),
254+ CoderGatewayBundle .message(
255+ " gateway.connector.view.coder.retrieve-ides.retry" ,
256+ attempt
257+ ),
242258 )
243259 } else {
244260 IDECellRenderer (CoderGatewayBundle .message(" gateway.connector.view.coder.retrieve-ides" ))
@@ -247,9 +263,9 @@ class CoderWorkspaceProjectIDEStepView(
247263 },
248264 retryIf = {
249265 it is ConnectionException ||
250- it is TimeoutException ||
251- it is SSHException ||
252- it is DeployException
266+ it is TimeoutException ||
267+ it is SSHException ||
268+ it is DeployException
253269 },
254270 onException = { attempt, nextMs, e ->
255271 logger.error(" Failed to retrieve IDEs (attempt $attempt ; will retry in $nextMs ms)" )
@@ -311,7 +327,10 @@ class CoderWorkspaceProjectIDEStepView(
311327 * Validate the remote path whenever it changes.
312328 */
313329 private fun installRemotePathValidator (executor : HighLevelHostAccessor ) {
314- val disposable = Disposer .newDisposable(ApplicationManager .getApplication(), CoderWorkspaceProjectIDEStepView ::class .java.name)
330+ val disposable = Disposer .newDisposable(
331+ ApplicationManager .getApplication(),
332+ CoderWorkspaceProjectIDEStepView ::class .java.name
333+ )
315334 ComponentValidator (disposable).installOn(tfProject)
316335
317336 tfProject.document.addDocumentListener(
@@ -324,7 +343,12 @@ class CoderWorkspaceProjectIDEStepView(
324343 val isPathPresent = validateRemotePath(tfProject.text, executor)
325344 if (isPathPresent.pathOrNull == null ) {
326345 ComponentValidator .getInstance(tfProject).ifPresent {
327- it.updateInfo(ValidationInfo (" Can't find directory: ${tfProject.text} " , tfProject))
346+ it.updateInfo(
347+ ValidationInfo (
348+ " Can't find directory: ${tfProject.text} " ,
349+ tfProject
350+ )
351+ )
328352 }
329353 } else {
330354 ComponentValidator .getInstance(tfProject).ifPresent {
@@ -333,7 +357,12 @@ class CoderWorkspaceProjectIDEStepView(
333357 }
334358 } catch (e: Exception ) {
335359 ComponentValidator .getInstance(tfProject).ifPresent {
336- it.updateInfo(ValidationInfo (" Can't validate directory: ${tfProject.text} " , tfProject))
360+ it.updateInfo(
361+ ValidationInfo (
362+ " Can't validate directory: ${tfProject.text} " ,
363+ tfProject
364+ )
365+ )
337366 }
338367 }
339368 }
@@ -377,27 +406,34 @@ class CoderWorkspaceProjectIDEStepView(
377406 }
378407
379408 logger.info(" Resolved OS and Arch for $name is: $workspaceOS " )
380- val installedIdesJob =
381- cs.async( Dispatchers . IO ) {
382- executor.getInstalledIDEs().map { it.toIdeWithStatus() }
383- }
384- val idesWithStatusJob =
385- cs.async( Dispatchers . IO ) {
386- IntelliJPlatformProduct .entries
387- .filter { it.showInGateway }
388- .flatMap { CachingProductsJsonWrapper .getInstance().getAvailableIdes(it, workspaceOS) }
389- .map { it.toIdeWithStatus() }
390- }
409+ val installedIdesJob = cs.async( Dispatchers . IO ) {
410+ executor.getInstalledIDEs()
411+ }
412+ val availableToDownloadIdesJob = cs.async( Dispatchers . IO ) {
413+ IntelliJPlatformProduct .entries
414+ .filter { it.showInGateway }
415+ .flatMap { CachingProductsJsonWrapper .getInstance().getAvailableIdes(it, workspaceOS) }
416+ }
417+
418+ val installedIdes = installedIdesJob.await()
419+ val availableIdes = availableToDownloadIdesJob.await()
391420
392- val installedIdes = installedIdesJob.await().sorted()
393- val idesWithStatus = idesWithStatusJob.await().sorted()
394421 if (installedIdes.isEmpty()) {
395422 logger.info(" No IDE is installed in $name " )
396423 }
397- if (idesWithStatus .isEmpty()) {
424+ if (availableIdes .isEmpty()) {
398425 logger.warn(" Could not resolve any IDE for $name , probably $workspaceOS is not supported by Gateway" )
399426 }
400- return installedIdes + idesWithStatus
427+
428+ val remainingInstalledIdes = installedIdes.filterOutAvailableReleasedIdes(availableIdes)
429+ if (remainingInstalledIdes.size < installedIdes.size) {
430+ logger.info(
431+ " Skipping the following list of installed IDEs because there is already a released version " +
432+ " available for download: ${(installedIdes - remainingInstalledIdes).joinToString { " ${it.product.productCode} ${it.presentableVersion} " }} "
433+ )
434+ }
435+ return remainingInstalledIdes.map { it.toIdeWithStatus() }.sorted() + availableIdes.map { it.toIdeWithStatus() }
436+ .sorted()
401437 }
402438
403439 private fun toDeployedOS (
@@ -455,7 +491,8 @@ class CoderWorkspaceProjectIDEStepView(
455491 override fun getSelectedItem (): IdeWithStatus ? = super .getSelectedItem() as IdeWithStatus ?
456492 }
457493
458- private class IDECellRenderer (message : String , cellIcon : Icon = AnimatedIcon .Default .INSTANCE ) : ListCellRenderer<IdeWithStatus> {
494+ private class IDECellRenderer (message : String , cellIcon : Icon = AnimatedIcon .Default .INSTANCE ) :
495+ ListCellRenderer <IdeWithStatus > {
459496 private val loadingComponentRenderer: ListCellRenderer <IdeWithStatus > =
460497 object : ColoredListCellRenderer <IdeWithStatus >() {
461498 override fun customizeCellRenderer (
0 commit comments