diff --git a/CHANGELOG.md b/CHANGELOG.md index 7472dd9b..dcf36caa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Fixed - installed EAP, RC, NIGHTLY and PREVIEW IDEs are no longer displayed if there is a higher released version available for download. +- project path is prefilled with the `folder` URI parameter when the IDE&Project dialog opens for URI handling. ## 2.19.0 - 2025-02-21 diff --git a/gradle.properties b/gradle.properties index c7842bd4..cebd25a7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,10 +4,10 @@ pluginGroup=com.coder.gateway # Zip file name. pluginName=coder-gateway # SemVer format -> https://semver.org -pluginVersion=2.20.0 +pluginVersion=2.20.1 # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html # for insight into build numbers and IntelliJ Platform versions. -pluginSinceBuild=233.6745 +pluginSinceBuild=243.26053 # This should be kept up to date with the latest EAP. If the API is incompatible # with the latest stable, use the eap branch temporarily instead. pluginUntilBuild=251.* @@ -26,11 +26,11 @@ pluginUntilBuild=251.* # that exists, ideally the most recent one, for example # 233.15325-EAP-CANDIDATE-SNAPSHOT). platformType=GW -platformVersion=241.19416-EAP-CANDIDATE-SNAPSHOT -instrumentationCompiler=243.15521-EAP-CANDIDATE-SNAPSHOT +platformVersion=243.26053-EAP-CANDIDATE-SNAPSHOT +instrumentationCompiler=243.26053-EAP-CANDIDATE-SNAPSHOT # Gateway does not have open sources. platformDownloadSources=true -verifyVersions=2023.3,2024.1,2024.2,2024.3 +verifyVersions=2024.3,2025.1 # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 platformPlugins= diff --git a/src/main/kotlin/com/coder/gateway/util/Dialogs.kt b/src/main/kotlin/com/coder/gateway/util/Dialogs.kt index 0e360363..f35f1e86 100644 --- a/src/main/kotlin/com/coder/gateway/util/Dialogs.kt +++ b/src/main/kotlin/com/coder/gateway/util/Dialogs.kt @@ -38,7 +38,10 @@ private class CoderWorkspaceStepDialog( init { init() - title = CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.choose.text", CoderCLIManager.getWorkspaceParts(state.workspace, state.agent)) + title = CoderGatewayBundle.message( + "gateway.connector.view.coder.remoteproject.choose.text", + CoderCLIManager.getWorkspaceParts(state.workspace, state.agent) + ) } override fun show() { @@ -75,12 +78,13 @@ fun askIDE( cli: CoderCLIManager, client: CoderRestClient, workspaces: List, + remoteProjectPath: String? = null ): WorkspaceProjectIDE? { var data: WorkspaceProjectIDE? = null ApplicationManager.getApplication().invokeAndWait { val dialog = CoderWorkspaceStepDialog( - CoderWorkspacesStepSelection(agent, workspace, cli, client, workspaces), + CoderWorkspacesStepSelection(agent, workspace, cli, client, workspaces, remoteProjectPath), ) data = dialog.showAndGetData() } diff --git a/src/main/kotlin/com/coder/gateway/util/LinkHandler.kt b/src/main/kotlin/com/coder/gateway/util/LinkHandler.kt index c32a136e..f802109c 100644 --- a/src/main/kotlin/com/coder/gateway/util/LinkHandler.kt +++ b/src/main/kotlin/com/coder/gateway/util/LinkHandler.kt @@ -32,7 +32,8 @@ open class LinkHandler( parameters: Map, indicator: ((t: String) -> Unit)? = null, ): WorkspaceProjectIDE { - val deploymentURL = parameters.url() ?: dialogUi.ask("Deployment URL", "Enter the full URL of your Coder deployment") + val deploymentURL = + parameters.url() ?: dialogUi.ask("Deployment URL", "Enter the full URL of your Coder deployment") if (deploymentURL.isNullOrBlank()) { throw MissingArgumentException("Query parameter \"$URL\" is missing") } @@ -50,7 +51,8 @@ open class LinkHandler( } // TODO: Show a dropdown and ask for the workspace if missing. - val workspaceName = parameters.workspace() ?: throw MissingArgumentException("Query parameter \"$WORKSPACE\" is missing") + val workspaceName = + parameters.workspace() ?: throw MissingArgumentException("Query parameter \"$WORKSPACE\" is missing") // The owner was added to support getting into another user's workspace // but may not exist if the Coder Gateway module is out of date. If no @@ -65,9 +67,9 @@ open class LinkHandler( indicator, ) - var workspace : Workspace - var workspaces : List = emptyList() - var workspacesAndAgents : Set> = emptySet() + var workspace: Workspace + var workspaces: List = emptyList() + var workspacesAndAgents: Set> = emptySet() if (cli.features.wildcardSSH) { workspace = client.workspaceByOwnerAndName(owner, workspaceName) } else { @@ -83,19 +85,28 @@ open class LinkHandler( WorkspaceStatus.PENDING, WorkspaceStatus.STARTING -> // TODO: Wait for the workspace to turn on. throw IllegalArgumentException( - "The workspace \"$workspaceName\" is ${workspace.latestBuild.status.toString().lowercase()}; please wait then try again", + "The workspace \"$workspaceName\" is ${ + workspace.latestBuild.status.toString().lowercase() + }; please wait then try again", ) + WorkspaceStatus.STOPPING, WorkspaceStatus.STOPPED, WorkspaceStatus.CANCELING, WorkspaceStatus.CANCELED, - -> + -> // TODO: Turn on the workspace. throw IllegalArgumentException( - "The workspace \"$workspaceName\" is ${workspace.latestBuild.status.toString().lowercase()}; please start the workspace and try again", + "The workspace \"$workspaceName\" is ${ + workspace.latestBuild.status.toString().lowercase() + }; please start the workspace and try again", ) + WorkspaceStatus.FAILED, WorkspaceStatus.DELETING, WorkspaceStatus.DELETED -> throw IllegalArgumentException( - "The workspace \"$workspaceName\" is ${workspace.latestBuild.status.toString().lowercase()}; unable to connect", + "The workspace \"$workspaceName\" is ${ + workspace.latestBuild.status.toString().lowercase() + }; unable to connect", ) + WorkspaceStatus.RUNNING -> Unit // All is well } @@ -106,10 +117,16 @@ open class LinkHandler( if (status.pending()) { // TODO: Wait for the agent to be ready. throw IllegalArgumentException( - "The agent \"${agent.name}\" has a status of \"${status.toString().lowercase()}\"; please wait then try again", + "The agent \"${agent.name}\" has a status of \"${ + status.toString().lowercase() + }\"; please wait then try again", ) } else if (!status.ready()) { - throw IllegalArgumentException("The agent \"${agent.name}\" has a status of \"${status.toString().lowercase()}\"; unable to connect") + throw IllegalArgumentException( + "The agent \"${agent.name}\" has a status of \"${ + status.toString().lowercase() + }\"; unable to connect" + ) } // We only need to log in if we are using token-based auth. @@ -123,12 +140,13 @@ open class LinkHandler( val openDialog = parameters.ideProductCode().isNullOrBlank() || - parameters.ideBuildNumber().isNullOrBlank() || - (parameters.idePathOnHost().isNullOrBlank() && parameters.ideDownloadLink().isNullOrBlank()) || - parameters.folder().isNullOrBlank() + parameters.ideBuildNumber().isNullOrBlank() || + (parameters.idePathOnHost().isNullOrBlank() && parameters.ideDownloadLink().isNullOrBlank()) || + parameters.folder().isNullOrBlank() return if (openDialog) { - askIDE(agent, workspace, cli, client, workspaces) ?: throw MissingArgumentException("IDE selection aborted; unable to connect") + askIDE(agent, workspace, cli, client, workspaces, parameters.folder()) + ?: throw MissingArgumentException("IDE selection aborted; unable to connect") } else { // Check that both the domain and the redirected domain are // allowlisted. If not, check with the user whether to proceed. @@ -259,7 +277,7 @@ private fun isAllowlisted(url: URL): Triple { val allowlisted = domainAllowlist.any { url.host == it || url.host.endsWith(".$it") } && - domainAllowlist.any { finalUrl.host == it || finalUrl.host.endsWith(".$it") } + domainAllowlist.any { finalUrl.host == it || finalUrl.host.endsWith(".$it") } val https = url.protocol == "https" && finalUrl.protocol == "https" return Triple(allowlisted, https, linkWithRedirect) } diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspaceProjectIDEStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspaceProjectIDEStepView.kt index ce28903a..81f02b2a 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspaceProjectIDEStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspaceProjectIDEStepView.kt @@ -95,7 +95,7 @@ private fun displayIdeWithStatus(ideWithStatus: IdeWithStatus): String = * to select an IDE and project to run on the workspace. */ class CoderWorkspaceProjectIDEStepView( - private val showTitle: Boolean = true, + private val showTitle: Boolean = true ) : CoderWizardStep( CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.next.text"), ) { @@ -200,7 +200,9 @@ class CoderWorkspaceProjectIDEStepView( val name = CoderCLIManager.getWorkspaceParts(data.workspace, data.agent) logger.info("Initializing workspace step for $name") - val homeDirectory = data.agent.expandedDirectory ?: data.agent.directory + val homeDirectory = data.remoteProjectPath.takeIf { !it.isNullOrBlank() } + ?: data.agent.expandedDirectory + ?: data.agent.directory tfProject.text = if (homeDirectory.isNullOrBlank()) "/home" else homeDirectory titleLabel.text = CoderGatewayBundle.message("gateway.connector.view.coder.remoteproject.choose.text", name) titleLabel.isVisible = showTitle @@ -429,7 +431,7 @@ class CoderWorkspaceProjectIDEStepView( if (remainingInstalledIdes.size < installedIdes.size) { logger.info( "Skipping the following list of installed IDEs because there is already a released version " + - "available for download: ${(installedIdes - remainingInstalledIdes).joinToString { "${it.product.productCode} ${it.presentableVersion}" }}" + "available for download: ${(installedIdes - remainingInstalledIdes).joinToString { "${it.product.productCode} ${it.presentableVersion}" }}" ) } return remainingInstalledIdes.map { it.toIdeWithStatus() }.sorted() + availableIdes.map { it.toIdeWithStatus() } diff --git a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt index 53a67c37..1928a4c4 100644 --- a/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt +++ b/src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt @@ -105,6 +105,7 @@ data class CoderWorkspacesStepSelection( // Pass along the latest workspaces so we can configure the CLI a bit // faster, otherwise this step would have to fetch the workspaces again. val workspaces: List, + val remoteProjectPath: String? = null ) /** @@ -147,7 +148,8 @@ class CoderWorkspacesStepView : setEmptyState(CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text.disconnected")) setSelectionMode(ListSelectionModel.SINGLE_SELECTION) selectionModel.addListSelectionListener { - nextButton.isEnabled = selectedObject?.status?.ready() == true && selectedObject?.agent?.operatingSystem == OS.LINUX + nextButton.isEnabled = + selectedObject?.status?.ready() == true && selectedObject?.agent?.operatingSystem == OS.LINUX if (selectedObject?.status?.ready() == true && selectedObject?.agent?.operatingSystem != OS.LINUX) { notificationBanner.apply { component.isVisible = true @@ -343,22 +345,26 @@ class CoderWorkspacesStepView : val maxWait = Duration.ofMinutes(10) while (isActive) { // Wait for the workspace to fully stop. delay(timeout.toMillis()) - val found = tableOfWorkspaces.items.firstOrNull { it.workspace.id == workspace.id } + val found = + tableOfWorkspaces.items.firstOrNull { it.workspace.id == workspace.id } when (val status = found?.workspace?.latestBuild?.status) { WorkspaceStatus.PENDING, WorkspaceStatus.STOPPING, WorkspaceStatus.RUNNING -> { logger.info("Still waiting for ${workspace.name} to stop before updating") } + WorkspaceStatus.STARTING, WorkspaceStatus.FAILED, WorkspaceStatus.CANCELING, WorkspaceStatus.CANCELED, WorkspaceStatus.DELETING, WorkspaceStatus.DELETED, - -> { + -> { logger.warn("Canceled ${workspace.name} update due to status change to $status") break } + null -> { logger.warn("Canceled ${workspace.name} update because it no longer exists") break } + WorkspaceStatus.STOPPED -> { logger.info("${workspace.name} has stopped; updating now") c.updateWorkspace(workspace) @@ -560,7 +566,10 @@ class CoderWorkspacesStepView : deploymentURL.host, ) tableOfWorkspaces.setEmptyState( - CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text.connecting", deploymentURL.host), + CoderGatewayBundle.message( + "gateway.connector.view.coder.workspaces.connect.text.connecting", + deploymentURL.host + ), ) tableOfWorkspaces.listTableModel.items = emptyList() @@ -600,7 +609,10 @@ class CoderWorkspacesStepView : client = authedClient tableOfWorkspaces.setEmptyState( - CoderGatewayBundle.message("gateway.connector.view.coder.workspaces.connect.text.connected", deploymentURL.host), + CoderGatewayBundle.message( + "gateway.connector.view.coder.workspaces.connect.text.connected", + deploymentURL.host + ), ) tfUrlComment?.text = CoderGatewayBundle.message( @@ -788,7 +800,8 @@ class WorkspacesTableModel : WorkspaceVersionColumnInfo("Version"), WorkspaceStatusColumnInfo("Status"), ) { - private class WorkspaceIconColumnInfo(columnName: String) : ColumnInfo(columnName) { + private class WorkspaceIconColumnInfo(columnName: String) : + ColumnInfo(columnName) { override fun valueOf(item: WorkspaceAgentListModel?): String? = item?.workspace?.templateName override fun getRenderer(item: WorkspaceAgentListModel?): TableCellRenderer { @@ -820,7 +833,8 @@ class WorkspacesTableModel : } } - private class WorkspaceNameColumnInfo(columnName: String) : ColumnInfo(columnName) { + private class WorkspaceNameColumnInfo(columnName: String) : + ColumnInfo(columnName) { override fun valueOf(item: WorkspaceAgentListModel?): String? = item?.name override fun getComparator(): Comparator = Comparator { a, b -> @@ -850,7 +864,8 @@ class WorkspacesTableModel : } } - private class WorkspaceOwnerColumnInfo(columnName: String) : ColumnInfo(columnName) { + private class WorkspaceOwnerColumnInfo(columnName: String) : + ColumnInfo(columnName) { override fun valueOf(item: WorkspaceAgentListModel?): String? = item?.workspace?.ownerName override fun getComparator(): Comparator = Comparator { a, b -> @@ -880,7 +895,8 @@ class WorkspacesTableModel : } } - private class WorkspaceTemplateNameColumnInfo(columnName: String) : ColumnInfo(columnName) { + private class WorkspaceTemplateNameColumnInfo(columnName: String) : + ColumnInfo(columnName) { override fun valueOf(item: WorkspaceAgentListModel?): String? = item?.workspace?.templateName override fun getComparator(): java.util.Comparator = Comparator { a, b -> @@ -909,7 +925,8 @@ class WorkspacesTableModel : } } - private class WorkspaceVersionColumnInfo(columnName: String) : ColumnInfo(columnName) { + private class WorkspaceVersionColumnInfo(columnName: String) : + ColumnInfo(columnName) { override fun valueOf(workspace: WorkspaceAgentListModel?): String? = if (workspace == null) { "Unknown" } else if (workspace.workspace.outdated) { @@ -940,7 +957,8 @@ class WorkspacesTableModel : } } - private class WorkspaceStatusColumnInfo(columnName: String) : ColumnInfo(columnName) { + private class WorkspaceStatusColumnInfo(columnName: String) : + ColumnInfo(columnName) { override fun valueOf(item: WorkspaceAgentListModel?): String? = item?.status?.label override fun getComparator(): java.util.Comparator = Comparator { a, b ->