diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1bb90c7a8..fa88ba840 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,9 @@
* [#3640](https://github.com/KronicDeth/intellij-elixir/pull/3640) - [@joshuataylor](https://github.com/joshuataylor)
* Bump intellij platform gradle to 2.0.1 and IdeaVIM to 2.16.0
+* [#3643](https://github.com/KronicDeth/intellij-elixir/pull/3643) - [@joshuataylor](https://github.com/joshuataylor)
+ * Fix RubyMine freezing for umbrella projects but showing the new project wizard as a temporary workaround.
+
## v19.0.0
### Breaking changes
diff --git a/build.gradle b/build.gradle
index 3ee362509..68d129619 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,5 +1,6 @@
import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
+import org.jetbrains.intellij.platform.gradle.tasks.RunIdeTask
plugins {
id "org.jetbrains.intellij.platform" version "2.0.1"
@@ -125,6 +126,22 @@ intellijPlatform {
}
apply plugin: "kotlin"
+tasks.withType(RunIdeTask) {
+ // Set JVM arguments
+ jvmArguments.addAll(["-Didea.debug.mode=true", "-Didea.is.internal=true", "-Dlog4j2.debug=true", "-Dlogger.org=TRACE", "-XX:+AllowEnhancedClassRedefinition"])
+
+ // Set system properties to debug log
+ systemProperty "idea.log.debug.categories", "org.elixir_lang"
+
+ // Set the maximum heap size
+ maxHeapSize = "7g"
+
+ // Optionally set the working directory if specified in the project properties
+ if (project.hasProperty("runIdeWorkingDirectory") && !project.property("runIdeWorkingDirectory").isEmpty()) {
+ workingDir = file(project.property("runIdeWorkingDirectory"))
+ }
+}
+
//noinspection GroovyAssignabilityCheck,GrUnresolvedAccess
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
//noinspection GrUnresolvedAccess
diff --git a/resources/META-INF/changelog.html b/resources/META-INF/changelog.html
index e6ff77d9e..6850baa6a 100644
--- a/resources/META-INF/changelog.html
+++ b/resources/META-INF/changelog.html
@@ -5,10 +5,14 @@
v19.0.1
Enhancements
diff --git a/src/org/elixir_lang/Elixir.kt b/src/org/elixir_lang/Elixir.kt
index c01442195..7416688b6 100644
--- a/src/org/elixir_lang/Elixir.kt
+++ b/src/org/elixir_lang/Elixir.kt
@@ -14,15 +14,16 @@ object Elixir {
environment: Map,
workingDirectory: String?,
elixirSdk: Sdk,
- erlArgumentList: kotlin.collections.List = emptyList()
+ erlArgumentList: kotlin.collections.List = emptyList(),
): GeneralCommandLine {
val erlangSdk = elixirSdkToEnsuredErlangSdk(elixirSdk)
- val commandLine = org.elixir_lang.Erl.commandLine(
- pty = false,
- environment = environment,
- workingDirectory = workingDirectory,
- erlangSdk = erlangSdk
- )
+ val commandLine =
+ org.elixir_lang.Erl.commandLine(
+ pty = false,
+ environment = environment,
+ workingDirectory = workingDirectory,
+ erlangSdk = erlangSdk,
+ )
// MUST be before `addElixir` because it ends with `-extra` which turns off argument parsing for `erl`
commandLine.addParameters(erlArgumentList)
addElixir(commandLine, elixirSdk, erlangSdk)
@@ -35,13 +36,17 @@ object Elixir {
?: throw MissingErlangSdk(elixirSdk)
fun elixirSdkHasErlangSdk(elixirSdk: Sdk): Boolean = elixirSdkToErlangSdk(elixirSdk) != null
- fun elixirSdkToErlangSdk(elixirSdk: Sdk): Sdk? =
- elixirSdk.sdkAdditionalData?.let { it as SdkAdditionalData }?.erlangSdk
+
+ fun elixirSdkToErlangSdk(elixirSdk: Sdk): Sdk? = elixirSdk.sdkAdditionalData?.let { it as SdkAdditionalData }?.getErlangSdk()
/**
* Adds `-pa ebinDirectory` for those in the `elixirSdk` that aren't in the `erlangSdk`
*/
- fun prependNewCodePaths(commandLine: GeneralCommandLine, elixirSdk: Sdk, erlangSdk: Sdk) {
+ fun prependNewCodePaths(
+ commandLine: GeneralCommandLine,
+ elixirSdk: Sdk,
+ erlangSdk: Sdk,
+ ) {
val elixirEbinDirectories = elixirSdk.ebinDirectories()
val erlangEbinDirectories = erlangSdk.ebinDirectories()
prependNewCodePaths(commandLine, elixirEbinDirectories, erlangEbinDirectories)
@@ -50,7 +55,11 @@ object Elixir {
/**
* Keep in-suync with [org.elixir_lang.jps.Builder.addElixir]
*/
- private fun addElixir(commandLine: GeneralCommandLine, elixirSdk: Sdk, erlangSdk: Sdk) {
+ private fun addElixir(
+ commandLine: GeneralCommandLine,
+ elixirSdk: Sdk,
+ erlangSdk: Sdk,
+ ) {
prependNewCodePaths(commandLine, elixirSdk, erlangSdk)
commandLine.addParameters("-noshell", "-s", "elixir", "start_cli")
commandLine.addParameters("-elixir", "ansi_enabled", "true")
@@ -60,7 +69,7 @@ object Elixir {
private fun prependNewCodePaths(
commandLine: GeneralCommandLine,
elixirEbinDirectories: kotlin.collections.List,
- erlangEbinDirectories: kotlin.collections.List
+ erlangEbinDirectories: kotlin.collections.List,
) {
val newEbinDirectories = elixirEbinDirectories - erlangEbinDirectories
prependCodePaths(commandLine, newEbinDirectories)
diff --git a/src/org/elixir_lang/facet/configurable/Provider.kt b/src/org/elixir_lang/facet/configurable/Provider.kt
index a76211739..72ca33ff5 100644
--- a/src/org/elixir_lang/facet/configurable/Provider.kt
+++ b/src/org/elixir_lang/facet/configurable/Provider.kt
@@ -5,6 +5,6 @@ import com.intellij.openapi.options.ConfigurableProvider
import org.elixir_lang.sdk.ProcessOutput
class Provider(private val project: com.intellij.openapi.project.Project): ConfigurableProvider() {
- override fun canCreateConfigurable(): Boolean = ProcessOutput.isSmallIde()
+ override fun canCreateConfigurable(): Boolean = ProcessOutput.isSmallIde
override fun createConfigurable(): Configurable = Project(project)
}
diff --git a/src/org/elixir_lang/facet/sdks/Provider.kt b/src/org/elixir_lang/facet/sdks/Provider.kt
index 3392861be..47176e260 100644
--- a/src/org/elixir_lang/facet/sdks/Provider.kt
+++ b/src/org/elixir_lang/facet/sdks/Provider.kt
@@ -4,5 +4,5 @@ import com.intellij.openapi.options.ConfigurableProvider
import org.elixir_lang.sdk.ProcessOutput
abstract class Provider : ConfigurableProvider() {
- override fun canCreateConfigurable(): Boolean = ProcessOutput.isSmallIde()
+ override fun canCreateConfigurable(): Boolean = ProcessOutput.isSmallIde
}
diff --git a/src/org/elixir_lang/mix/Project.kt b/src/org/elixir_lang/mix/Project.kt
index 72329dd9e..d23d9ce3b 100644
--- a/src/org/elixir_lang/mix/Project.kt
+++ b/src/org/elixir_lang/mix/Project.kt
@@ -1,6 +1,7 @@
package org.elixir_lang.mix
import com.intellij.openapi.application.runWriteAction
+import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.module.ModifiableModuleModel
import com.intellij.openapi.module.Module
import com.intellij.openapi.progress.ProgressIndicator
@@ -20,6 +21,8 @@ import java.io.EOFException
import java.io.File
object Project {
+ private val LOG = Logger.getInstance(Project::class.java)
+
fun addSourceDirToContent(
content: ContentEntry,
root: VirtualFile,
@@ -41,6 +44,7 @@ object Project {
VfsUtilCore.visitChildrenRecursively(root, object : VirtualFileVisitor() {
override fun visitFile(file: VirtualFile): Boolean {
+ LOG.debug("visiting $file")
indicator.checkCanceled()
if (file.isDirectory) {
diff --git a/src/org/elixir_lang/mix/project/DirectoryConfigurator.kt b/src/org/elixir_lang/mix/project/DirectoryConfigurator.kt
index da5be185e..147900ba3 100644
--- a/src/org/elixir_lang/mix/project/DirectoryConfigurator.kt
+++ b/src/org/elixir_lang/mix/project/DirectoryConfigurator.kt
@@ -5,6 +5,9 @@ import com.intellij.facet.FacetManager
import com.intellij.facet.FacetType
import com.intellij.facet.impl.FacetUtil.addFacet
import com.intellij.ide.impl.OpenProjectTask
+import com.intellij.notification.NotificationGroupManager
+import com.intellij.notification.NotificationType
+import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.progress.ProgressIndicator
@@ -13,14 +16,19 @@ import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ex.ProjectManagerEx
import com.intellij.openapi.roots.ModuleRootModificationUtil
+import com.intellij.openapi.roots.ProjectRootManager
+import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable
+import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel
import com.intellij.openapi.util.Ref
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.PlatformProjectOpenProcessor.Companion.runDirectoryProjectConfigurators
import com.intellij.projectImport.ProjectAttachProcessor
import com.intellij.util.PlatformUtils
+import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
import org.elixir_lang.DepsWatcher
import org.elixir_lang.Facet
import org.elixir_lang.mix.Project.addFolders
@@ -28,11 +36,16 @@ import org.elixir_lang.mix.Watcher
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.exists
+import org.elixir_lang.sdk.elixir.Type
/**
* Used in Small IDEs like Rubymine that don't support [OpenProcessor].
*/
class DirectoryConfigurator : com.intellij.platform.DirectoryProjectConfigurator {
+ companion object {
+ private val LOG = Logger.getInstance(DirectoryConfigurator::class.java)
+ }
+
override fun configureProject(
project: Project,
baseDir: VirtualFile,
@@ -40,6 +53,7 @@ class DirectoryConfigurator : com.intellij.platform.DirectoryProjectConfigurator
isProjectCreatedWithWizard: Boolean
) {
var foundOtpApps: List = emptyList()
+ LOG.debug("configuring $baseDir for project $project, created with wizard: $isProjectCreatedWithWizard")
ProgressManager.getInstance().run(object : Task.Modal(project, "Scanning Mix Projects", true) {
override fun run(indicator: ProgressIndicator) {
@@ -47,13 +61,37 @@ class DirectoryConfigurator : com.intellij.platform.DirectoryProjectConfigurator
}
})
+ // If this is an umbrella app, RubyMine currently freezes.
+ // Instead, let's just show a notification that the user needs to use the Wizard.
+ if (!isProjectCreatedWithWizard && foundOtpApps.isNotEmpty() && foundOtpApps.size > 1) {
+ LOG.info("not configuring project $project because it is an umbrella app")
+ NotificationGroupManager.getInstance()
+ .getNotificationGroup("Elixir")
+ .createNotification(
+ "Umbrella App Detected",
+ "Elixir Umbrella app detected, please use the Project Wizard to properly configure it when using an IDE like RubyMine.",
+ NotificationType.WARNING
+ )
+ .notify(project)
+
+ return
+ }
+
for (otpApp in foundOtpApps) {
+ LOG.debug("configuring descendant otp app: ${otpApp.name}")
if (otpApp.root == baseDir) {
+ LOG.debug("configuring root otp app: ${otpApp.name}")
configureRootOtpApp(project, otpApp)
} else {
runBlocking {
- launch(coroutineContext) {
- configureDescendantOtpApp(project, otpApp)
+ try {
+ withTimeout(2000L) {
+ LOG.debug("Not otp app root: ${otpApp.name}, configuring descendant otp app.")
+ configureDescendantOtpApp(project, otpApp)
+ }
+ } catch (e: TimeoutCancellationException) {
+ // Handle the timeout exception, e.g., log a warning or notify the user
+ LOG.error("Timeout while configuring descendant OTP app: ${otpApp.name}", e)
}
}
}
@@ -82,8 +120,10 @@ class DirectoryConfigurator : com.intellij.platform.DirectoryProjectConfigurator
private suspend fun configureDescendantOtpApp(rootProject: Project, otpApp: OtpApp) {
if (!PlatformUtils.isGoIde() && ProjectAttachProcessor.canAttachToProject()) {
newProject(otpApp)?.let { otpAppProject ->
+ LOG.debug("attaching $otpAppProject to $rootProject")
attachToProject(rootProject, Paths.get(otpApp.root.path))
+ LOG.debug("scanning libraries for newly attached project for OTP app ${otpApp.name}")
ProgressManager.getInstance().run(object : Task.Modal(
otpAppProject,
"Scanning mix.exs to connect libraries for newly attached project for OTP app ${otpApp.name}",
@@ -92,8 +132,10 @@ class DirectoryConfigurator : com.intellij.platform.DirectoryProjectConfigurator
override fun run(progressIndicator: ProgressIndicator) {
for (module in ModuleManager.getInstance(otpAppProject).modules) {
if (progressIndicator.isCanceled) {
+ LOG.debug("canceled scanning libraries for newly attached project for OTP app ${otpApp.name}")
break
}
+ LOG.debug("scanning libraries for newly attached project for OTP app ${otpApp.name} for module ${module.name}")
Watcher(otpAppProject).syncLibraries(module, progressIndicator)
}
@@ -108,8 +150,10 @@ class DirectoryConfigurator : com.intellij.platform.DirectoryProjectConfigurator
*/
private suspend fun newProject(otpApp: OtpApp): Project? {
val projectDir = Paths.get(FileUtil.toSystemDependentName(otpApp.root.path), Project.DIRECTORY_STORE_FOLDER)
+ LOG.debug("Checking if $projectDir exists")
return if (projectDir.exists()) {
+ LOG.debug("$projectDir already exists")
null
} else {
val path = otpApp.root.path.let { Paths.get(it) }
@@ -119,6 +163,8 @@ class DirectoryConfigurator : com.intellij.platform.DirectoryProjectConfigurator
projectName = otpApp.name
}
+ LOG.debug("Creating new project at $path with isNewProject: ${openProjectTask.isNewProject} and useDefaultProjectAsTemplate: ${openProjectTask.useDefaultProjectAsTemplate} and projectName: ${openProjectTask.projectName}")
+
ProjectManagerEx
.getInstanceEx()
.newProject(
@@ -126,9 +172,14 @@ class DirectoryConfigurator : com.intellij.platform.DirectoryProjectConfigurator
openProjectTask
)
?.let { project ->
+ LOG.debug("runDirectoryProjectConfigurators for project: $project at $path")
runDirectoryProjectConfigurators(path, project, false)
+ LOG.debug("runDirectoryProjectConfigurators complete for project: $project at $path")
+
+ LOG.debug("Saving settings for project: $project at $path")
StoreUtil.saveSettings(project, true)
+ LOG.debug("Saved settings for project: $project at $path")
project
}
diff --git a/src/org/elixir_lang/notification/setup_sdk/Action.kt b/src/org/elixir_lang/notification/setup_sdk/Action.kt
index 8ceaabf9e..32d214648 100644
--- a/src/org/elixir_lang/notification/setup_sdk/Action.kt
+++ b/src/org/elixir_lang/notification/setup_sdk/Action.kt
@@ -6,8 +6,6 @@ import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleType
import com.intellij.openapi.project.Project
-import org.elixir_lang.notification.setup_sdk.Provider.Companion.showFacetSettings
-import org.elixir_lang.notification.setup_sdk.Provider.Companion.showModuleSettings
/**
* Created by zyuyou on 15/7/8.
diff --git a/src/org/elixir_lang/notification/setup_sdk/Provider.kt b/src/org/elixir_lang/notification/setup_sdk/Provider.kt
index a4a3675eb..4505b18c2 100644
--- a/src/org/elixir_lang/notification/setup_sdk/Provider.kt
+++ b/src/org/elixir_lang/notification/setup_sdk/Provider.kt
@@ -1,6 +1,7 @@
package org.elixir_lang.notification.setup_sdk
import com.intellij.ide.actions.ShowSettingsUtilImpl
+import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleType
@@ -14,6 +15,8 @@ import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.ui.EditorNotificationPanel
import com.intellij.ui.EditorNotificationProvider
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
import org.elixir_lang.ElixirFileType
import org.elixir_lang.ElixirLanguage
import org.elixir_lang.sdk.ProcessOutput
@@ -21,120 +24,125 @@ import org.elixir_lang.sdk.elixir.Type
import java.util.function.Function
import javax.swing.JComponent
-/**
- * https://github.com/ignatov/intellij-erlang/blob/master/src/org/intellij/erlang/inspection/SetupSDKNotificationProvider.java
- */
class Provider : EditorNotificationProvider {
override fun collectNotificationData(
project: Project,
- file: VirtualFile
+ file: VirtualFile,
): Function =
- Function { createNotificationPanel(file, project) }
+ Function {
+ kotlinx.coroutines.runBlocking {
+ createNotificationPanel(file, project)
+ }
+ }
- private fun createNotificationPanel(
+ private suspend fun createNotificationPanel(
virtualFile: VirtualFile,
- project: Project
+ project: Project,
): EditorNotificationPanel? =
- if (virtualFile.fileType is ElixirFileType) {
- PsiManager
- .getInstance(project)
- .findFile(virtualFile)
- ?.let { psiFile ->
- if (psiFile.language === ElixirLanguage &&
- Type.mostSpecificSdk(psiFile) == null
- ) {
- createPanel(project, psiFile)
- } else {
- null
- }
+ withContext(Dispatchers.Default) {
+ ReadAction.compute {
+ if (virtualFile.fileType is ElixirFileType) {
+ PsiManager
+ .getInstance(project)
+ .findFile(virtualFile)
+ ?.let { psiFile ->
+ if (psiFile.language === ElixirLanguage &&
+ Type.mostSpecificSdk(psiFile) == null
+ ) {
+ createPanel(project, psiFile)
+ } else {
+ null
+ }
+ }
+ } else {
+ null
}
- } else {
- null
- }
-
- companion object {
- fun showFacetSettings(project: Project) {
- if (ProcessOutput.isSmallIde()) {
- showSmallIDEFacetSettings(project)
}
- // TODO Elixir Facet in non-Elixir module in IntelliJ
}
- fun showModuleSettings(project: Project, module: Module) {
- ProjectSettingsService.getInstance(project).openModuleSettings(module)
- }
+}
- private fun showProjectSettings(project: Project) {
- SdkPopupFactory
- .newBuilder()
- .withProject(project)
- .withSdkType(Type.instance)
- .updateProjectSdkFromSelection()
- .buildPopup()
- .showInFocusCenter()
- }
+fun showFacetSettings(project: Project) {
+ if (ProcessOutput.isSmallIde) {
+ showSmallIDEFacetSettings(project)
+ }
+ // TODO Elixir Facet in non-Elixir module in IntelliJ
+}
- private fun showSmallIDEFacetSettings(project: Project) {
- ShowSettingsUtilImpl.showSettingsDialog(project, "language", "Elixir")
- }
+fun showModuleSettings(
+ project: Project,
+ module: Module,
+) {
+ ProjectSettingsService.getInstance(project).openModuleSettings(module)
+}
- private fun createSmallIDEFacetPanel(project: Project): EditorNotificationPanel {
- return EditorNotificationPanel().apply {
- text = "Elixir Facet SDK is not defined"
- @Suppress("DialogTitleCapitalization")
- createActionLabel("Setup Elixir Facet SDK") {
- showSmallIDEFacetSettings(project)
- }
- }
- }
+private fun showProjectSettings(project: Project) {
+ SdkPopupFactory.newBuilder()
+ .withProject(project)
+ .withSdkType(Type.instance)
+ .updateProjectSdkFromSelection()
+ .buildPopup()
+ .showInFocusCenter()
+}
- private fun createFacetPanel(project: Project): EditorNotificationPanel? {
- return if (ProcessOutput.isSmallIde()) {
- createSmallIDEFacetPanel(project)
- } else {
- // TODO Elixir Facet in non-Elixir module in IntelliJ
- null
- }
- }
+private fun showSmallIDEFacetSettings(project: Project) {
+ ShowSettingsUtilImpl.showSettingsDialog(project, "language", "Elixir")
+}
- private fun createModulePanel(project: Project, module: Module): EditorNotificationPanel {
- return EditorNotificationPanel().apply {
- text = "Elixir Module SDK is not defined"
- @Suppress("DialogTitleCapitalization")
- createActionLabel("Setup Elixir Module SDK") {
- showModuleSettings(project, module)
- }
- }
+private fun createSmallIDEFacetPanel(project: Project): EditorNotificationPanel =
+ EditorNotificationPanel().apply {
+ text = "Elixir Facet SDK is not defined"
+ @Suppress("DialogTitleCapitalization")
+ createActionLabel("Setup Elixir Facet SDK") {
+ showSmallIDEFacetSettings(project)
}
+ }
- private fun createPanel(project: Project, psiFile: PsiFile): EditorNotificationPanel? {
- val module = ModuleUtilCore.findModuleForPsiElement(psiFile)
+private fun createFacetPanel(project: Project): EditorNotificationPanel? =
+ if (ProcessOutput.isSmallIde) {
+ createSmallIDEFacetPanel(project)
+ } else {
+ // TODO Elixir Facet in non-Elixir module in IntelliJ
+ null
+ }
- return if (module != null) {
- // CANNOT use ModuleType.is(module, ElixirModuleType.getInstance()) as ElixirModuleType depends on
- // JavaModuleBuilder and so only available in IntelliJ
- if (ModuleType.get(module).id == "ELIXIR_MODULE") {
- createModulePanel(project, module)
- } else {
- createFacetPanel(project)
- }
- } else {
- if (ProcessOutput.isSmallIde()) {
- createSmallIDEFacetPanel(project)
- } else {
- createProjectPanel(project)
- }
- }
+private fun createModulePanel(
+ project: Project,
+ module: Module,
+): EditorNotificationPanel =
+ EditorNotificationPanel().apply {
+ text = "Elixir Module SDK is not defined"
+ @Suppress("DialogTitleCapitalization")
+ createActionLabel("Setup Elixir Module SDK") {
+ showModuleSettings(project, module)
}
+ }
- private fun createProjectPanel(project: Project): EditorNotificationPanel {
- return EditorNotificationPanel().apply {
- text = "Project SDK is not defined"
- createActionLabel(ProjectBundle.message("project.sdk.setup")) {
- showProjectSettings(project)
- }
- }
+private fun createPanel(
+ project: Project,
+ psiFile: PsiFile,
+): EditorNotificationPanel? {
+ val module = ModuleUtilCore.findModuleForPsiElement(psiFile)
+ return when {
+ module != null -> {
+ // CANNOT use ModuleType.is(module, ElixirModuleType.getInstance()) as ElixirModuleType depends on
+ // JavaModuleBuilder and so only available in IntelliJ
+ if (ModuleType.get(module).id == "ELIXIR_MODULE") {
+ createModulePanel(project, module)
+ } else {
+ createFacetPanel(project)
+ }
}
+ ProcessOutput.isSmallIde -> createSmallIDEFacetPanel(project)
+ else -> createProjectPanel(project)
}
}
+
+private fun createProjectPanel(project: Project): EditorNotificationPanel =
+ EditorNotificationPanel().apply {
+ text = "Project SDK is not defined"
+ createActionLabel(ProjectBundle.message("project.sdk.setup")) {
+ showProjectSettings(project)
+ }
+ }
\ No newline at end of file
diff --git a/src/org/elixir_lang/sdk/ProcessOutput.java b/src/org/elixir_lang/sdk/ProcessOutput.java
deleted file mode 100644
index 689c07ca4..000000000
--- a/src/org/elixir_lang/sdk/ProcessOutput.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package org.elixir_lang.sdk;
-
-import com.google.common.base.Charsets;
-import com.intellij.execution.ExecutionException;
-import com.intellij.execution.configurations.GeneralCommandLine;
-import com.intellij.execution.process.CapturingProcessHandler;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.util.Function;
-import com.intellij.util.PlatformUtils;
-import com.intellij.util.containers.ContainerUtil;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.util.List;
-
-/**
- * Created by zyuyou on 2015/5/27.
- *
- */
-public class ProcessOutput {
- /*
- * CONSTANTS
- */
-
- private static final Logger LOGGER = Logger.getInstance(ProcessOutput.class);
- public static final int STANDARD_TIMEOUT = 10 * 1000;
-
- @Nullable
- public static T transformStdoutLine(@NotNull com.intellij.execution.process.ProcessOutput output, @NotNull Function lineTransformer) {
- List lines;
-
- if (output.getExitCode() != 0 || output.isTimeout() || output.isCancelled()) {
- lines = ContainerUtil.emptyList();
- } else {
- lines = output.getStdoutLines();
- }
-
- T transformed = null;
-
- for (String line : lines) {
- transformed = lineTransformer.fun(line);
-
- if (transformed != null) {
- break;
- }
- }
-
- return transformed;
- }
-
- @Nullable
- public static T transformStdoutLine(@NotNull Function lineTransformer,
- int timeout,
- @NotNull String workDir,
- @NotNull String exePath,
- @NotNull String... arguments) {
- T transformed = null;
-
- try {
- com.intellij.execution.process.ProcessOutput output = getProcessOutput(timeout, workDir, exePath, arguments);
-
- transformed = transformStdoutLine(output, lineTransformer);
- } catch (ExecutionException executionException) {
- LOGGER.warn(executionException);
- }
-
- return transformed;
- }
-
- @NotNull
- public static com.intellij.execution.process.ProcessOutput getProcessOutput(int timeout,
- @Nullable String workDir,
- @NotNull String exePath,
- @NotNull String... arguments) throws ExecutionException{
- if(workDir == null || !new File(workDir).isDirectory() || !new File(exePath).canExecute()){
- return new com.intellij.execution.process.ProcessOutput();
- }
-
- GeneralCommandLine cmd = new GeneralCommandLine().withCharset(Charsets.UTF_8);
- cmd.withWorkDirectory(workDir);
- cmd.setExePath(exePath);
- cmd.addParameters(arguments);
-
- return execute(cmd, timeout);
- }
-
- @NotNull
- public static com.intellij.execution.process.ProcessOutput execute(@NotNull GeneralCommandLine cmd) throws ExecutionException {
- return execute(cmd, STANDARD_TIMEOUT);
- }
-
- @NotNull
- public static com.intellij.execution.process.ProcessOutput execute(@NotNull GeneralCommandLine cmd, int timeout) throws ExecutionException {
- CapturingProcessHandler processHandler = new CapturingProcessHandler(cmd);
- return timeout < 0 ? processHandler.runProcess() : processHandler.runProcess(timeout);
- }
-
- public static boolean isSmallIde(){
- return !(PlatformUtils.isIntelliJ() || PlatformUtils.getPlatformPrefix().equals("AndroidStudio"));
- }
-}
diff --git a/src/org/elixir_lang/sdk/ProcessOutput.kt b/src/org/elixir_lang/sdk/ProcessOutput.kt
new file mode 100644
index 000000000..6f969182a
--- /dev/null
+++ b/src/org/elixir_lang/sdk/ProcessOutput.kt
@@ -0,0 +1,102 @@
+package org.elixir_lang.sdk
+
+import com.google.common.base.Charsets
+import com.intellij.execution.ExecutionException
+import com.intellij.execution.configurations.GeneralCommandLine
+import com.intellij.execution.process.CapturingProcessHandler
+import com.intellij.openapi.diagnostic.Logger
+import com.intellij.util.Function
+import com.intellij.util.PlatformUtils
+import com.intellij.util.containers.ContainerUtil
+import java.io.File
+
+/**
+ * Created by zyuyou on 2015/5/27.
+ *
+ */
+object ProcessOutput {
+ /*
+ * CONSTANTS
+ */
+ private val LOGGER = Logger.getInstance(
+ ProcessOutput::class.java
+ )
+ const val STANDARD_TIMEOUT: Int = 10 * 1000
+
+ fun transformStdoutLine(
+ output: com.intellij.execution.process.ProcessOutput,
+ lineTransformer: Function
+ ): T? {
+ val lines = if (output.exitCode != 0 || output.isTimeout || output.isCancelled) {
+ ContainerUtil.emptyList()
+ } else {
+ output.stdoutLines
+ }
+
+ var transformed: T? = null
+
+ for (line in lines) {
+ transformed = lineTransformer.`fun`(line)
+
+ if (transformed != null) {
+ break
+ }
+ }
+
+ return transformed
+ }
+
+ fun transformStdoutLine(
+ lineTransformer: Function,
+ timeout: Int,
+ workDir: String,
+ exePath: String,
+ vararg arguments: String?
+ ): T? {
+ var transformed: T? = null
+
+ try {
+ val output = getProcessOutput(timeout, workDir, exePath, *arguments)
+
+ transformed = transformStdoutLine(output, lineTransformer)
+ } catch (executionException: ExecutionException) {
+ LOGGER.warn(executionException)
+ }
+
+ return transformed
+ }
+
+ @JvmStatic
+ @Throws(ExecutionException::class)
+ fun getProcessOutput(
+ timeout: Int,
+ workDir: String?,
+ exePath: String,
+ vararg arguments: String?
+ ): com.intellij.execution.process.ProcessOutput {
+ if (workDir == null || !File(workDir).isDirectory || !File(exePath).canExecute()) {
+ return com.intellij.execution.process.ProcessOutput()
+ }
+
+ val cmd = GeneralCommandLine().withCharset(Charsets.UTF_8)
+ cmd.withWorkDirectory(workDir)
+ cmd.exePath = exePath
+ cmd.addParameters(*arguments)
+
+ return execute(cmd, timeout)
+ }
+
+ @JvmOverloads
+ @Throws(ExecutionException::class)
+ fun execute(
+ cmd: GeneralCommandLine,
+ timeout: Int = STANDARD_TIMEOUT
+ ): com.intellij.execution.process.ProcessOutput {
+ val processHandler = CapturingProcessHandler(cmd)
+ return if (timeout < 0) processHandler.runProcess() else processHandler.runProcess(timeout)
+ }
+
+ @JvmStatic
+ val isSmallIde: Boolean
+ get() = !(PlatformUtils.isIntelliJ() || PlatformUtils.getPlatformPrefix() == "AndroidStudio")
+}
diff --git a/src/org/elixir_lang/sdk/Type.java b/src/org/elixir_lang/sdk/Type.java
deleted file mode 100644
index 22b3f1a03..000000000
--- a/src/org/elixir_lang/sdk/Type.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package org.elixir_lang.sdk;
-
-import com.intellij.openapi.projectRoots.SdkModificator;
-import com.intellij.openapi.roots.JavadocOrderRootType;
-import com.intellij.openapi.roots.OrderRootType;
-import com.intellij.openapi.vfs.LocalFileSystem;
-import com.intellij.openapi.vfs.VirtualFile;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.nio.file.Path;
-import java.util.function.Consumer;
-
-import static org.elixir_lang.jps.HomePath.eachEbinPath;
-import static org.elixir_lang.sdk.ProcessOutput.isSmallIde;
-
-public class Type {
- private Type() {
- }
-
- public static void addCodePaths(@NotNull SdkModificator sdkModificator) {
- eachEbinPath(
- sdkModificator.getHomePath(),
- ebin -> ebinPathChainVirtualFile(
- ebin, virtualFile -> sdkModificator.addRoot(virtualFile, OrderRootType.CLASSES)
- )
- );
- }
-
- public static void ebinPathChainVirtualFile(@NotNull Path ebinPath, Consumer virtualFileConsumer) {
- VirtualFile virtualFile = LocalFileSystem
- .getInstance()
- .findFileByIoFile(ebinPath.toFile());
-
- if (virtualFile != null) {
- virtualFileConsumer.accept(virtualFile);
- }
- }
-
- @Nullable
- public static OrderRootType documentationRootType() {
- OrderRootType rootType;
-
- if (isSmallIde()) {
- rootType = null;
- } else {
- rootType = JavadocOrderRootType.getInstance();
- }
-
- return rootType;
- }
-}
diff --git a/src/org/elixir_lang/sdk/Type.kt b/src/org/elixir_lang/sdk/Type.kt
new file mode 100644
index 000000000..e2dd03f53
--- /dev/null
+++ b/src/org/elixir_lang/sdk/Type.kt
@@ -0,0 +1,46 @@
+package org.elixir_lang.sdk
+
+import com.intellij.openapi.projectRoots.SdkModificator
+import com.intellij.openapi.roots.JavadocOrderRootType
+import com.intellij.openapi.roots.OrderRootType
+import com.intellij.openapi.vfs.LocalFileSystem
+import com.intellij.openapi.vfs.VirtualFile
+import org.elixir_lang.jps.HomePath
+import java.nio.file.Path
+import java.util.function.Consumer
+
+object Type {
+ fun addCodePaths(sdkModificator: SdkModificator) {
+ HomePath.eachEbinPath(
+ sdkModificator.homePath
+ ) { ebin: Path ->
+ ebinPathChainVirtualFile(
+ ebin
+ ) { virtualFile: VirtualFile? ->
+ sdkModificator.addRoot(
+ virtualFile!!, OrderRootType.CLASSES
+ )
+ }
+ }
+ }
+
+ fun ebinPathChainVirtualFile(ebinPath: Path, virtualFileConsumer: Consumer) {
+ val virtualFile = LocalFileSystem
+ .getInstance()
+ .findFileByIoFile(ebinPath.toFile())
+
+ if (virtualFile != null) {
+ virtualFileConsumer.accept(virtualFile)
+ }
+ }
+
+ fun documentationRootType(): OrderRootType? {
+ val rootType = if (ProcessOutput.isSmallIde) {
+ null
+ } else {
+ JavadocOrderRootType.getInstance()
+ }
+
+ return rootType
+ }
+}
diff --git a/src/org/elixir_lang/sdk/elixir/Release.java b/src/org/elixir_lang/sdk/elixir/Release.java
deleted file mode 100644
index a6c75c88c..000000000
--- a/src/org/elixir_lang/sdk/elixir/Release.java
+++ /dev/null
@@ -1,185 +0,0 @@
-package org.elixir_lang.sdk.elixir;
-
-import org.jetbrains.annotations.Contract;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public final class Release implements Comparable {
- /*
- * CONSTANTS
- */
-
- public static final Release V_1_0_4 = new Release("1", "0", "4", null, null);
- public static final Release LATEST = new Release("1", "6", "0", "dev", null);
-
- private static final Pattern VERSION_PATTERN = Pattern.compile(
- // @version_regex from Version in elixir itself
- "(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?(?:\\-([\\d\\w\\.\\-]+))?(?:\\+([\\d\\w\\-]+))?"
- );
-
- /*
- *
- * Static Methods
- *
- */
-
- /*
- * Public Static Methods
- */
-
- @Nullable
- public static Release fromString(@Nullable String versionString){
- Matcher m = versionString != null ? VERSION_PATTERN.matcher(versionString) : null;
- return m != null && m.matches() ? new Release(m.group(1), m.group(2), m.group(3), m.group(4), m.group(5)) : null;
- }
-
- /*
- * Private Static Methods
- */
-
- @Contract(pure = true)
- private static int compareMaybeFormattedDecimals(@Nullable String mine, @Nullable String others) {
- int comparison;
-
- if (mine == null && others == null) {
- comparison = 0;
- } else if (mine == null) {
- comparison = -1;
- } else if (others == null) {
- comparison = 1;
- } else {
- try {
- int myInt = Integer.parseInt(mine);
-
- try {
- int othersInt = Integer.parseInt(others);
-
- if (myInt > othersInt) {
- comparison = 1;
- } else if (myInt < othersInt) {
- comparison = -1;
- } else {
- comparison = 0;
- }
- } catch (NumberFormatException numberFormatException) {
- comparison = mine.compareTo(others);
- }
- } catch (NumberFormatException numberFormatException) {
- comparison = mine.compareTo(others);
- }
- }
-
- return comparison;
- }
-
- /**
- * @see Version.to_compare
- */
- @Contract(pure = true)
- private static int comparePre(@Nullable String mine, @Nullable String others) {
- int comparison;
-
- if (mine == null && others == null) {
- comparison = 0;
- } else if (mine == null) {
- // https://github.com/elixir-lang/elixir/blob/27c350da06ee4df5a4710507abe443ffba5b07dd/lib/elixir/lib/version.ex#L203
- comparison = 1;
- } else if (others == null) {
- // https://github.com/elixir-lang/elixir/blob/27c350da06ee4df5a4710507abe443ffba5b07dd/lib/elixir/lib/version.ex#L204
- comparison = -1;
- } else {
- comparison = mine.compareTo(others);
- }
-
- return comparison;
- }
-
- /*
- * Fields
- */
-
- @Nullable
- private final String build;
- @NotNull
- public final String major;
- @Nullable
- public final String minor;
- @Nullable
- private final String patch;
- @Nullable
- private final String pre;
-
- /*
- * Constructors
- */
-
- public Release(@NotNull String major,
- @Nullable String minor,
- @Nullable String patch,
- @Nullable String pre,
- @Nullable String build) {
- this.major = major;
- this.minor = minor;
- this.patch = patch;
- this.pre = pre;
- this.build = build;
-
- if (minor == null && patch != null) {
- throw new IllegalArgumentException("patch MUST be null if minor is null");
- }
- }
-
- /*
- * Instance Methods
- */
-
- @Override
- public int compareTo(@NotNull Release other) {
- int comparison = compareMaybeFormattedDecimals(major, other.major);
-
- if (comparison == 0) {
- comparison = compareMaybeFormattedDecimals(minor, other.minor);
-
- if (comparison == 0) {
- comparison = compareMaybeFormattedDecimals(patch, other.patch);
-
- if (comparison == 0) {
- comparison = comparePre(pre, other.pre);
- }
- }
- }
-
- return comparison;
- }
-
- @Override
- public String toString() {
- return "Elixir " + version();
- }
-
- @NotNull
- public String version(){
- StringBuilder version = new StringBuilder(major);
-
- if (minor != null) {
- version.append('.').append(minor);
- }
-
- if (patch != null) {
- version.append('.').append(patch);
- }
-
- if (pre != null) {
- version.append('-').append(pre);
- }
-
- if (build != null) {
- version.append('+').append(build);
- }
-
- return version.toString();
- }
-}
diff --git a/src/org/elixir_lang/sdk/elixir/Release.kt b/src/org/elixir_lang/sdk/elixir/Release.kt
new file mode 100644
index 000000000..37fede8b1
--- /dev/null
+++ b/src/org/elixir_lang/sdk/elixir/Release.kt
@@ -0,0 +1,88 @@
+package org.elixir_lang.sdk.elixir
+
+import org.jetbrains.annotations.Contract
+import java.util.regex.Pattern
+
+class Release private constructor(
+ @JvmField val major: String,
+ @JvmField val minor: String?,
+ private val patch: String?,
+ private val pre: String?,
+ private val build: String?
+) : Comparable {
+
+ companion object {
+ @JvmField
+ val V_1_0_4 = Release("1", "0", "4", null, null)
+ @JvmField
+ val LATEST = Release("1", "6", "0", "dev", null)
+
+ private val VERSION_PATTERN = Pattern.compile(
+ "(\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))?(?:\\-([\\d\\w\\.\\-]+))?(?:\\+([\\d\\w\\-]+))?"
+ )
+
+ @JvmStatic
+ fun fromString(versionString: String?): Release? {
+ val m = versionString?.let { VERSION_PATTERN.matcher(it) }
+ return if (m != null && m.matches()) {
+ Release(m.group(1), m.group(2), m.group(3), m.group(4), m.group(5))
+ } else null
+ }
+
+ @Contract(pure = true)
+ private fun compareMaybeFormattedDecimals(mine: String?, others: String?): Int {
+ return when {
+ mine == null && others == null -> 0
+ mine == null -> -1
+ others == null -> 1
+ else -> try {
+ val myInt = mine.toInt()
+ val othersInt = others.toInt()
+ myInt.compareTo(othersInt)
+ } catch (numberFormatException: NumberFormatException) {
+ mine.compareTo(others)
+ }
+ }
+ }
+
+ @Contract(pure = true)
+ private fun comparePre(mine: String?, others: String?): Int {
+ return when {
+ mine == null && others == null -> 0
+ mine == null -> 1
+ others == null -> -1
+ else -> mine.compareTo(others)
+ }
+ }
+ }
+
+ init {
+ require(!(minor == null && patch != null)) { "patch MUST be null if minor is null" }
+ }
+
+ override fun compareTo(other: Release): Int {
+ var comparison = compareMaybeFormattedDecimals(major, other.major)
+ if (comparison == 0) {
+ comparison = compareMaybeFormattedDecimals(minor, other.minor)
+ if (comparison == 0) {
+ comparison = compareMaybeFormattedDecimals(patch, other.patch)
+ if (comparison == 0) {
+ comparison = comparePre(pre, other.pre)
+ }
+ }
+ }
+ return comparison
+ }
+
+ override fun toString(): String = "Elixir ${version()}"
+
+ fun version(): String {
+ return buildString {
+ append(major)
+ minor?.let { append('.').append(it) }
+ patch?.let { append('.').append(it) }
+ pre?.let { append('-').append(it) }
+ build?.let { append('+').append(it) }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/elixir_lang/sdk/elixir/Type.kt b/src/org/elixir_lang/sdk/elixir/Type.kt
index 8f1404245..da14369f5 100644
--- a/src/org/elixir_lang/sdk/elixir/Type.kt
+++ b/src/org/elixir_lang/sdk/elixir/Type.kt
@@ -28,26 +28,23 @@ import com.intellij.serviceContainer.AlreadyDisposedException
import com.intellij.util.system.CpuArch
import gnu.trove.THashSet
import org.apache.commons.io.FilenameUtils
-import org.apache.commons.lang.ArrayUtils
import org.elixir_lang.Facet
import org.elixir_lang.Icons
import org.elixir_lang.jps.HomePath
import org.elixir_lang.jps.model.SerializerExtension
import org.elixir_lang.jps.sdk_type.Elixir
import org.elixir_lang.sdk.ProcessOutput
+import org.elixir_lang.sdk.Type.ebinPathChainVirtualFile
import org.elixir_lang.sdk.erlang_dependent.AdditionalDataConfigurable
import org.elixir_lang.sdk.erlang_dependent.SdkAdditionalData
-import org.elixir_lang.sdk.erlang_dependent.SdkModificatorRootTypeConsumer
import org.jdom.Element
import org.jetbrains.annotations.Contract
-import org.jetbrains.annotations.NotNull
import org.jetbrains.annotations.TestOnly
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths
import java.util.*
import javax.swing.Icon
-import java.util.concurrent.Callable
class Type : org.elixir_lang.sdk.erlang_dependent.Type(SerializerExtension.ELIXIR_SDK_TYPE_ID) {
/**
@@ -114,32 +111,48 @@ class Type : org.elixir_lang.sdk.erlang_dependent.Type(SerializerExtension.ELIXI
val homePathByVersion: MutableMap = TreeMap(Comparator.reverseOrder())
if (SystemInfo.isMac) {
HomePath.mergeASDF(homePathByVersion, "elixir")
- HomePath.mergeHomebrew(homePathByVersion, "elixir", java.util.function.Function.identity())
- HomePath.mergeNixStore(homePathByVersion, NIX_PATTERN, java.util.function.Function.identity())
+ HomePath.mergeHomebrew(
+ homePathByVersion,
+ "elixir",
+ java.util.function.Function
+ .identity(),
+ )
+ HomePath.mergeNixStore(
+ homePathByVersion,
+ NIX_PATTERN,
+ java.util.function.Function
+ .identity(),
+ )
} else {
val sdkPath: String
if (SystemInfo.isWindows) {
- sdkPath = if (CpuArch.CURRENT.width == 32) {
- WINDOWS_32BIT_DEFAULT_HOME_PATH
- } else {
- WINDOWS_64BIT_DEFAULT_HOME_PATH
- }
+ sdkPath =
+ if (CpuArch.CURRENT.width == 32) {
+ WINDOWS_32BIT_DEFAULT_HOME_PATH
+ } else {
+ WINDOWS_64BIT_DEFAULT_HOME_PATH
+ }
homePathByVersion[HomePath.UNKNOWN_VERSION] = sdkPath
} else if (SystemInfo.isLinux) {
putIfDirectory(homePathByVersion, HomePath.UNKNOWN_VERSION, LINUX_DEFAULT_HOME_PATH)
putIfDirectory(homePathByVersion, HomePath.UNKNOWN_VERSION, LINUX_MINT_HOME_PATH)
- HomePath.mergeNixStore(homePathByVersion, NIX_PATTERN, java.util.function.Function.identity())
+ HomePath.mergeNixStore(
+ homePathByVersion,
+ NIX_PATTERN,
+ java.util.function.Function
+ .identity(),
+ )
}
}
return homePathByVersion
}
- private fun invalidSdkHomeException(virtualFile: VirtualFile): Exception {
- return Exception(invalidSdkHomeMessage(virtualFile))
- }
+ private fun invalidSdkHomeException(virtualFile: VirtualFile): Exception =
+ Exception(invalidSdkHomeMessage(virtualFile))
- private fun invalidSdkHomeMessage(virtualFile: VirtualFile): String = if (virtualFile.isDirectory) {
- """A valid home for $presentableName has the following structure:
+ private fun invalidSdkHomeMessage(virtualFile: VirtualFile): String =
+ if (virtualFile.isDirectory) {
+ """A valid home for $presentableName has the following structure:
ELIXIR_SDK_HOME
* bin
@@ -155,16 +168,19 @@ ELIXIR_SDK_HOME
** logger
** mix
"""
- } else {
- "A directory must be select for the home for $presentableName"
- }
+ } else {
+ "A directory must be select for the home for $presentableName"
+ }
override fun isValidSdkHome(path: String): Boolean {
val elixir = Elixir.getScriptInterpreterExecutable(path)
val elixirc = Elixir.getByteCodeCompilerExecutable(path)
val iex = Elixir.getIExExecutable(path)
val mix = Elixir.mixFile(path)
- return elixir.canExecute() && elixirc.canExecute() && iex.canExecute() && mix.canRead() &&
+ return elixir.canExecute() &&
+ elixirc.canExecute() &&
+ iex.canExecute() &&
+ mix.canRead() &&
HomePath.hasEbinPath(path)
}
@@ -181,12 +197,12 @@ ELIXIR_SDK_HOME
return suggestedHomePath
}
- override fun suggestHomePaths(): Collection {
- return homePathByVersion().values
- }
+ override fun suggestHomePaths(): Collection = homePathByVersion().values
- override fun suggestSdkName(currentSdkName: String?, sdkHome: String): String =
- Release.fromString(File(sdkHome).name)?.toString() ?: "Elixir at $sdkHome"
+ override fun suggestSdkName(
+ currentSdkName: String?,
+ sdkHome: String,
+ ): String = Release.fromString(File(sdkHome).name)?.toString() ?: "Elixir at $sdkHome"
private fun validateSdkHomePath(virtualFile: VirtualFile) {
val selectedPath = virtualFile.path
@@ -201,14 +217,13 @@ ELIXIR_SDK_HOME
override fun createAdditionalDataConfigurable(
sdkModel: SdkModel,
- sdkModificator: SdkModificator
- ): com.intellij.openapi.projectRoots.AdditionalDataConfigurable {
- return AdditionalDataConfigurable(sdkModel, sdkModificator)
- }
+ sdkModificator: SdkModificator,
+ ): com.intellij.openapi.projectRoots.AdditionalDataConfigurable =
+ AdditionalDataConfigurable(sdkModel, sdkModificator)
override fun saveAdditionalData(
additionalData: com.intellij.openapi.projectRoots.SdkAdditionalData,
- additional: Element
+ additional: Element,
) {
if (additionalData is SdkAdditionalData) {
try {
@@ -222,7 +237,7 @@ ELIXIR_SDK_HOME
@Suppress("SameParameterValue")
override fun loadAdditionalData(
elixirSdk: Sdk,
- additional: Element
+ additional: Element,
): com.intellij.openapi.projectRoots.SdkAdditionalData {
val sdkAdditionalData = SdkAdditionalData(elixirSdk)
try {
@@ -248,7 +263,7 @@ ELIXIR_SDK_HOME
private fun addDocumentationPath(
sdkModificator: SdkModificator,
releaseVersion: String?,
- appName: String
+ appName: String,
) {
val hexdocUrlBuilder = StringBuilder("https://hexdoc.pm/").append(appName)
if (releaseVersion != null) {
@@ -256,7 +271,9 @@ ELIXIR_SDK_HOME
}
val hexdocUrlVirtualFile = VirtualFileManager.getInstance().findFileByUrl(hexdocUrlBuilder.toString())
if (hexdocUrlVirtualFile != null) {
- val documentationRootType = org.elixir_lang.sdk.Type.documentationRootType()
+ val documentationRootType =
+ org.elixir_lang.sdk.Type
+ .documentationRootType()
if (documentationRootType != null) {
sdkModificator.addRoot(hexdocUrlVirtualFile, documentationRootType)
}
@@ -266,7 +283,7 @@ ELIXIR_SDK_HOME
private fun addDocumentationPath(
sdkModificator: SdkModificator,
releaseVersion: String?,
- ebinPath: Path
+ ebinPath: Path,
) {
val appName = ebinPath.parent.fileName.toString()
addDocumentationPath(sdkModificator, releaseVersion, appName)
@@ -275,24 +292,30 @@ ELIXIR_SDK_HOME
private fun addDocumentationPaths(sdkModificator: SdkModificator) {
val releaseVersion = releaseVersion(sdkModificator)
HomePath.eachEbinPath(
- sdkModificator.homePath
+ sdkModificator.homePath,
) { ebinPath: Path -> addDocumentationPath(sdkModificator, releaseVersion, ebinPath) }
}
private fun addSourcePaths(sdkModificator: SdkModificator) {
HomePath.eachEbinPath(
- sdkModificator.homePath
+ sdkModificator.homePath,
) { ebinPath: Path -> addSourcePath(sdkModificator, ebinPath) }
}
- private fun addSourcePath(sdkModificator: SdkModificator, libFile: File) {
+ private fun addSourcePath(
+ sdkModificator: SdkModificator,
+ libFile: File,
+ ) {
val sourcePath = VfsUtil.findFileByIoFile(libFile, true)
if (sourcePath != null) {
sdkModificator.addRoot(sourcePath, OrderRootType.SOURCES)
}
}
- private fun addSourcePath(sdkModificator: SdkModificator, ebinPath: Path) {
+ private fun addSourcePath(
+ sdkModificator: SdkModificator,
+ ebinPath: Path,
+ ) {
val parentPath = ebinPath.parent
val libPath = Paths.get(parentPath.toString(), "lib")
val libFile = libPath.toFile()
@@ -303,14 +326,18 @@ ELIXIR_SDK_HOME
private fun configureSdkPaths(sdk: Sdk) {
val sdkModificator = sdk.sdkModificator
- org.elixir_lang.sdk.Type.addCodePaths(sdkModificator)
+ org.elixir_lang.sdk.Type
+ .addCodePaths(sdkModificator)
addDocumentationPaths(sdkModificator)
addSourcePaths(sdkModificator)
configureInternalErlangSdk(sdk, sdkModificator)
ApplicationManager.getApplication().runWriteAction { sdkModificator.commitChanges() }
}
- private fun configureInternalErlangSdk(elixirSdk: Sdk, elixirSdkModificator: SdkModificator): Sdk? {
+ private fun configureInternalErlangSdk(
+ elixirSdk: Sdk,
+ elixirSdkModificator: SdkModificator,
+ ): Sdk? {
val erlangSdk = defaultErlangSdk()
if (erlangSdk != null) {
val sdkAdditionData: com.intellij.openapi.projectRoots.SdkAdditionalData =
@@ -325,17 +352,17 @@ ELIXIR_SDK_HOME
fun addNewCodePathsFromInternErlangSdk(
elixirSdk: Sdk,
internalErlangSdk: Sdk,
- elixirSdkModificator: SdkModificator
+ elixirSdkModificator: SdkModificator,
) {
codePathsFromInternalErlangSdk(
elixirSdk,
internalErlangSdk,
elixirSdkModificator,
- SdkModificatorRootTypeConsumer { sdkModificator: SdkModificator, configuredRoots: Array, expandedInternalRoot: VirtualFile, type: OrderRootType ->
- if (!ArrayUtils.contains(configuredRoots, expandedInternalRoot)) {
+ { sdkModificator, configuredRoots, expandedInternalRoot, type ->
+ if (expandedInternalRoot !in configuredRoots) {
sdkModificator.addRoot(expandedInternalRoot, type)
}
- }
+ },
)
}
@@ -343,18 +370,15 @@ ELIXIR_SDK_HOME
fun removeCodePathsFromInternalErlangSdk(
elixirSdk: Sdk,
internalErlangSdk: Sdk,
- elixirSdkModificator: SdkModificator
+ elixirSdkModificator: SdkModificator,
) {
codePathsFromInternalErlangSdk(
elixirSdk,
internalErlangSdk,
elixirSdkModificator,
- SdkModificatorRootTypeConsumer { sdkModificator: SdkModificator, _: Array, expandedInternalRoot: VirtualFile, type: OrderRootType ->
- sdkModificator.removeRoot(
- expandedInternalRoot,
- type
- )
- }
+ { sdkModificator, _, expandedInternalRoot, type ->
+ sdkModificator.removeRoot(expandedInternalRoot, type)
+ },
)
}
@@ -362,7 +386,7 @@ ELIXIR_SDK_HOME
elixirSdk: Sdk,
internalErlangSdk: Sdk,
elixirSdkModificator: SdkModificator,
- sdkModificatorRootTypeConsumer: SdkModificatorRootTypeConsumer
+ sdkModificatorRootTypeConsumer: (SdkModificator, Array, VirtualFile, OrderRootType) -> Unit,
) {
val internalSdkType = internalErlangSdk.sdkType as SdkType
val elixirSdkType = elixirSdk.sdkType as SdkType
@@ -372,8 +396,12 @@ ELIXIR_SDK_HOME
val configuredRoots = elixirSdkModificator.getRoots(type)
for (internalRoot in internalRoots) {
for (expandedInternalRoot in expandInternalErlangSdkRoot(internalRoot, type)) {
- sdkModificatorRootTypeConsumer
- .consume(elixirSdkModificator, configuredRoots, expandedInternalRoot, type)
+ sdkModificatorRootTypeConsumer(
+ elixirSdkModificator,
+ configuredRoots,
+ expandedInternalRoot,
+ type
+ )
}
}
}
@@ -382,7 +410,7 @@ ELIXIR_SDK_HOME
private fun expandInternalErlangSdkRoot(
internalRoot: VirtualFile,
- type: OrderRootType
+ type: OrderRootType,
): Iterable {
val expandedInternalRootList: List
if (type === OrderRootType.CLASSES) {
@@ -398,9 +426,11 @@ ELIXIR_SDK_HOME
expandedInternalRootList = ArrayList()
val parentPath = Paths.get(path).parent.toString()
HomePath.eachEbinPath(parentPath) { ebinPath: Path? ->
- org.elixir_lang.sdk.Type.ebinPathChainVirtualFile(
- ebinPath!!
- ) { e: VirtualFile -> expandedInternalRootList.add(e) }
+ ebinPathChainVirtualFile(
+ ebinPath!!,
+ ) { virtualFile: VirtualFile? ->
+ virtualFile?.let { expandedInternalRootList.add(it) }
+ }
}
} else {
expandedInternalRootList = listOf(internalRoot)
@@ -413,7 +443,10 @@ ELIXIR_SDK_HOME
@JvmStatic
@TestOnly
- fun createMockSdk(sdkHome: String, release: Release): Sdk {
+ fun createMockSdk(
+ sdkHome: String,
+ release: Release,
+ ): Sdk {
val sdk: Sdk = ProjectJdkImpl(release.toString(), instance)
val sdkModificator = sdk.sdkModificator
sdkModificator.homePath = sdkHome
@@ -434,7 +467,7 @@ ELIXIR_SDK_HOME
private fun createDefaultErlangSdk(
projectJdkTable: ProjectJdkTable,
erlangSdkType: SdkType,
- homePath: String
+ homePath: String,
): Sdk? {
val sdkName = erlangSdkType.suggestSdkName("Default " + erlangSdkType.name, homePath)
val projectJdkImpl = ProjectJdkImpl(sdkName, erlangSdkType)
@@ -446,7 +479,7 @@ ELIXIR_SDK_HOME
{
ApplicationManager.getApplication().runWriteAction { projectJdkTable.addJdk(projectJdkImpl) }
},
- ModalityState.NON_MODAL
+ ModalityState.NON_MODAL,
)
projectJdkImpl
} else {
@@ -456,20 +489,21 @@ ELIXIR_SDK_HOME
private fun createDefaultErlangSdk(
projectJdkTable: ProjectJdkTable,
- erlangSdkType: SdkType
+ erlangSdkType: SdkType,
): Sdk? =
defaultErlangSdkHomePath()?.let { homePath ->
createDefaultErlangSdk(projectJdkTable, erlangSdkType, homePath)
}
@JvmStatic
- fun erlangSdkType(): SdkType = if (ProcessOutput.isSmallIde()) {
- /* intellij-erlang's "Erlang SDK" does not work in small IDEs because it uses JavadocRoot for documentation,
- which isn't available in Small IDEs. */
- null
- } else {
- EP_NAME.extensionList.find { sdkType -> sdkType.name == "Erlang SDK" }
- } ?: findInstance(org.elixir_lang.sdk.erlang.Type::class.java)
+ fun erlangSdkType(): SdkType =
+ if (ProcessOutput.isSmallIde) {
+ /* intellij-erlang's "Erlang SDK" does not work in small IDEs because it uses JavadocRoot for documentation,
+ which isn't available in Small IDEs. */
+ null
+ } else {
+ EP_NAME.extensionList.find { sdkType -> sdkType.name == "Erlang SDK" }
+ } ?: findInstance(org.elixir_lang.sdk.erlang.Type::class.java)
private fun defaultErlangSdk(): Sdk? {
val projectJdkTable = ProjectJdkTable.getInstance()
@@ -489,19 +523,20 @@ ELIXIR_SDK_HOME
@JvmStatic
@Contract("null -> null")
- fun getRelease(sdk: Sdk?): Release? = if (sdk != null && sdk.sdkType === instance) {
- Release.fromString(sdk.versionString)
- ?: sdk.homePath?.let { Release.fromString(File(it).name) }
- } else {
- null
- }
+ fun getRelease(sdk: Sdk?): Release? =
+ if (sdk != null && sdk.sdkType === instance) {
+ Release.fromString(sdk.versionString)
+ ?: sdk.homePath?.let { Release.fromString(File(it).name) }
+ } else {
+ null
+ }
private fun getVersionString(version: Release?): String? = version?.toString()
private fun putIfDirectory(
homePathByVersion: MutableMap,
@Suppress("SameParameterValue") version: Version,
- homePath: String
+ homePath: String,
) {
val homeFile = File(homePath)
if (homeFile.isDirectory) {
@@ -513,11 +548,12 @@ ELIXIR_SDK_HOME
private fun projectSdk(project: Project): Sdk? = sdk(ProjectRootManager.getInstance(project).projectSdk)
- private fun sdk(sdk: Sdk?): Sdk? = if (sdk != null && sdk.sdkType === instance) {
- sdk
- } else {
- null
- }
+ private fun sdk(sdk: Sdk?): Sdk? =
+ if (sdk != null && sdk.sdkType === instance) {
+ sdk
+ } else {
+ null
+ }
@JvmStatic
fun mostSpecificSdk(module: Module): Sdk? =
@@ -529,17 +565,20 @@ ELIXIR_SDK_HOME
val project = psiElement.project
return if (!project.isDisposed) {
- /* ModuleUtilCore.findModuleForPsiElement can fail with NullPointerException if the
- ProjectFileIndex.SERVICE.getInstance(Project) returns {@code null}, so check that the
- ProjectFileIndex is available first */
if (ProjectFileIndex.SERVICE.getInstance(project) != null) {
- val module = try {
- ReadAction.compute {
- ModuleUtilCore.findModuleForPsiElement(psiElement)
- }
- } catch (_: AlreadyDisposedException) {
- null
- }
+ // Use a background thread to perform the ReadAction
+ val module =
+ ApplicationManager
+ .getApplication()
+ .executeOnPooledThread {
+ try {
+ ReadAction.compute {
+ ModuleUtilCore.findModuleForPsiElement(psiElement)
+ }
+ } catch (_: AlreadyDisposedException) {
+ null
+ }
+ }.get() // Wait for the result
if (module != null) {
mostSpecificSdk(module)
diff --git a/src/org/elixir_lang/sdk/erlang/Release.java b/src/org/elixir_lang/sdk/erlang/Release.java
deleted file mode 100644
index af291e7f0..000000000
--- a/src/org/elixir_lang/sdk/erlang/Release.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.elixir_lang.sdk.erlang;
-
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class Release {
- private static final Pattern VERSION_PATTERN = Pattern.compile("Erlang/OTP (\\S+) \\[erts-(\\S+)\\]");
-
- @NotNull
- final String otpRelease;
- @NotNull
- private final String ertsVersion;
-
- Release(@NotNull String otpRelease, @NotNull String ertsVersion) {
- this.otpRelease = otpRelease;
- this.ertsVersion = ertsVersion;
- }
-
- @Nullable
- static Release fromString(@Nullable String versionString) {
- Release release = null;
-
- if (versionString != null) {
- Matcher matcher = VERSION_PATTERN.matcher(versionString);
-
- if (matcher.matches()) {
- release = new Release(matcher.group(1), matcher.group(2));
- }
- }
-
- return release;
- }
-
- @NotNull
- @Override
- public String toString() {
- return "Erlang/OTP " + otpRelease + " [erts-" + ertsVersion + "]";
- }
-}
diff --git a/src/org/elixir_lang/sdk/erlang/Release.kt b/src/org/elixir_lang/sdk/erlang/Release.kt
new file mode 100644
index 000000000..b3161a5d0
--- /dev/null
+++ b/src/org/elixir_lang/sdk/erlang/Release.kt
@@ -0,0 +1,27 @@
+package org.elixir_lang.sdk.erlang
+
+import java.util.regex.Pattern
+
+class Release internal constructor(val otpRelease: String, private val ertsVersion: String) {
+ override fun toString(): String {
+ return "Erlang/OTP $otpRelease [erts-$ertsVersion]"
+ }
+
+ companion object {
+ private val VERSION_PATTERN: Pattern = Pattern.compile("Erlang/OTP (\\S+) \\[erts-(\\S+)\\]")
+
+ fun fromString(versionString: String?): Release? {
+ var release: Release? = null
+
+ if (versionString != null) {
+ val matcher = VERSION_PATTERN.matcher(versionString)
+
+ if (matcher.matches()) {
+ release = Release(matcher.group(1), matcher.group(2))
+ }
+ }
+
+ return release
+ }
+ }
+}
diff --git a/src/org/elixir_lang/sdk/erlang/Type.java b/src/org/elixir_lang/sdk/erlang/Type.java
deleted file mode 100644
index b98e212e2..000000000
--- a/src/org/elixir_lang/sdk/erlang/Type.java
+++ /dev/null
@@ -1,285 +0,0 @@
-package org.elixir_lang.sdk.erlang;
-
-import com.intellij.execution.ExecutionException;
-import com.intellij.execution.process.ProcessOutput;
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.projectRoots.Sdk;
-import com.intellij.openapi.projectRoots.SdkModel;
-import com.intellij.openapi.projectRoots.SdkModificator;
-import com.intellij.openapi.projectRoots.SdkType;
-import com.intellij.openapi.roots.OrderRootType;
-import com.intellij.openapi.util.SystemInfo;
-import com.intellij.openapi.util.Version;
-import com.intellij.util.containers.ContainerUtil;
-import org.elixir_lang.jps.sdk_type.Erlang;
-import org.elixir_lang.jps.HomePath;
-import org.elixir_lang.sdk.erlang_dependent.AdditionalDataConfigurable;
-import org.jdom.Element;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import java.io.File;
-import java.util.*;
-import java.util.function.Function;
-import java.util.regex.Pattern;
-
-import static org.elixir_lang.jps.HomePath.*;
-import static org.elixir_lang.sdk.Type.addCodePaths;
-import static org.elixir_lang.sdk.Type.documentationRootType;
-
-/**
- * An Erlang SdkType for use when `intellij-erlang` is not installed
- */
-public class Type extends SdkType {
- private static final String OTP_RELEASE_PREFIX_LINE = Type.class.getCanonicalName() + " OTP_RELEASE:";
- private static final String ERTS_VERSION_PREFIX_LINE = Type.class.getCanonicalName() + " ERTS_VERSION:";
- private static final String PRINT_VERSION_INFO_EXPRESSION =
- "io:format(\"~n~s~n~s~n~s~n~s~n\",[" +
- "\"" + OTP_RELEASE_PREFIX_LINE + "\"," +
- "erlang:system_info(otp_release)," +
- "\"" + ERTS_VERSION_PREFIX_LINE + "\"," +
- "erlang:system_info(version)" +
- "]),erlang:halt().";
- private static final String WINDOWS_DEFAULT_HOME_PATH = "C:\\Program Files\\erl9.0";
- private static final Pattern NIX_PATTERN = nixPattern("erlang");
- private static final String LINUX_MINT_HOME_PATH = HomePath.LINUX_MINT_HOME_PATH + "/erlang";
- private static final String LINUX_DEFAULT_HOME_PATH = HomePath.LINUX_DEFAULT_HOME_PATH + "/erlang";
- private static final Function VERSION_PATH_TO_HOME_PATH =
- versionPath -> new File(versionPath, "lib/erlang");
- private static final Logger LOGGER = Logger.getInstance(Type.class);
- private final Map releaseBySdkHome = ContainerUtil.createWeakMap();
-
-
- public Type() {
- // Don't use "Erlang SDK" as we want it to be different from the name in intellij-erlang
- super("Erlang SDK for Elixir SDK");
- }
-
- @NotNull
- private static String getDefaultSdkName(@NotNull String sdkHome, @Nullable Release version) {
- StringBuilder defaultSdkNameBuilder = new StringBuilder("Erlang for Elixir ");
-
- if (version != null) {
- defaultSdkNameBuilder.append(version.otpRelease);
- } else {
- defaultSdkNameBuilder.append(" at ").append(sdkHome);
- }
-
- return defaultSdkNameBuilder.toString();
- }
-
- @Nullable
- private static String getVersionCacheKey(@Nullable String sdkHome) {
- String versionCacheKey = null;
-
- if (sdkHome != null) {
- versionCacheKey = new File(sdkHome).getAbsolutePath();
- }
-
- return versionCacheKey;
- }
-
- @Nullable
- private static Release parseSdkVersion(@NotNull List printVersionInfoOutput) {
- String otpRelease = null;
- String ertsVersion = null;
-
- ListIterator iterator = printVersionInfoOutput.listIterator();
-
- while (iterator.hasNext()) {
- String line = iterator.next();
-
- if (OTP_RELEASE_PREFIX_LINE.equals(line) && iterator.hasNext()) {
- otpRelease = iterator.next();
- } else if (ERTS_VERSION_PREFIX_LINE.equals(line) && iterator.hasNext()) {
- ertsVersion = iterator.next();
- }
- }
-
- Release release;
-
- if (otpRelease != null && ertsVersion != null) {
- release = new Release(otpRelease, ertsVersion);
- } else {
- release = null;
- }
-
- return release;
- }
-
- /**
- * Map of home paths to versions in descending version order so that newer versions are favored.
- *
- * @return Map
- */
- public static Map homePathByVersion() {
- Map homePathByVersion = HomePath.homePathByVersion();
-
- if (SystemInfo.isMac) {
- mergeASDF(homePathByVersion, "erlang");
- mergeHomebrew(homePathByVersion, "erlang", VERSION_PATH_TO_HOME_PATH);
- mergeNixStore(homePathByVersion, NIX_PATTERN, VERSION_PATH_TO_HOME_PATH);
- } else {
- if (SystemInfo.isWindows) {
- putIfDirectory(homePathByVersion, UNKNOWN_VERSION, WINDOWS_DEFAULT_HOME_PATH);
- } else if (SystemInfo.isLinux) {
- putIfDirectory(homePathByVersion, UNKNOWN_VERSION, LINUX_DEFAULT_HOME_PATH);
- putIfDirectory(homePathByVersion, UNKNOWN_VERSION, LINUX_MINT_HOME_PATH);
-
- mergeTravisCIKerl(homePathByVersion, Function.identity());
- mergeNixStore(homePathByVersion, NIX_PATTERN, VERSION_PATH_TO_HOME_PATH);
- }
- }
-
- return homePathByVersion;
- }
-
- private static void putIfDirectory(@NotNull Map homePathByVersion,
- @NotNull Version version,
- @NotNull String homePath) {
- File homeFile = new File(homePath);
-
- if (homeFile.isDirectory()) {
- homePathByVersion.put(version, homePath);
- }
- }
-
- @Override
- public boolean isRootTypeApplicable(@NotNull OrderRootType type) {
- return type == OrderRootType.CLASSES ||
- type == OrderRootType.SOURCES ||
- type == documentationRootType();
- }
-
- @Override
- public void setupSdkPaths(@NotNull Sdk sdk) {
- SdkModificator sdkModificator = sdk.getSdkModificator();
- addCodePaths(sdkModificator);
- ApplicationManager.getApplication().runWriteAction(sdkModificator::commitChanges);
- }
-
- /**
- * Returns a recommended starting path for a file chooser (where SDKs of this type are usually may be found),
- * or {@code null} if not applicable/no SDKs found.
- *
- * E.g. for Python SDK on Unix the method may return either {@code "/usr/bin"} or {@code "/usr/bin/python"}
- * (if there is only one Python interpreter installed on a host).
- */
- @Nullable
- @Override
- public String suggestHomePath() {
- Iterator iterator = suggestHomePaths().iterator();
- String suggestedHomePath = null;
-
- if (iterator.hasNext()) {
- suggestedHomePath = iterator.next();
- }
-
- return suggestedHomePath;
- }
-
- @NotNull
- @Override
- public Collection suggestHomePaths() {
- return homePathByVersion().values();
- }
-
- @Override
- public boolean isValidSdkHome(String path) {
- File erl = Erlang.getByteCodeInterpreterExecutable(path);
-
- return erl.canExecute();
- }
-
- @Override
- public String suggestSdkName(String currentSdkName, String sdkHome) {
- return getDefaultSdkName(sdkHome, detectSdkVersion(sdkHome));
- }
-
- @Nullable
- @Override
- public String getVersionString(@NotNull String sdkHome) {
- Release release = detectSdkVersion(sdkHome);
- String versionString;
-
- if (release != null) {
- versionString = release.otpRelease;
- } else {
- versionString = null;
- }
-
- return versionString;
- }
-
- @Nullable
- @Override
- public AdditionalDataConfigurable createAdditionalDataConfigurable(@NotNull SdkModel sdkModel,
- @NotNull SdkModificator sdkModificator) {
- return null;
- }
-
- @NotNull
- @Override
- public String getPresentableName() {
- return getName();
- }
-
- @Override
- public void saveAdditionalData(@NotNull com.intellij.openapi.projectRoots.SdkAdditionalData additionalData,
- @NotNull Element additional) {
- // Intentionally left blank
- }
-
- @Nullable
- private Release detectSdkVersion(@NotNull String sdkHome) {
- Release release = null;
-
- Release cachedRelease = releaseBySdkHome.get(getVersionCacheKey(sdkHome));
-
- if (cachedRelease != null) {
- release = cachedRelease;
- } else {
- File erl = Erlang.getByteCodeInterpreterExecutable(sdkHome);
-
- if (!erl.canExecute()) {
- StringBuilder messageBuilder = new StringBuilder("Can't detect Erlang version: ").append(erl.getPath());
-
- if (erl.exists()) {
- messageBuilder.append(" is not executable.");
- } else {
- messageBuilder.append(" is missing.");
- }
-
- LOGGER.warn(messageBuilder.toString());
- } else {
- try {
- ProcessOutput output = org.elixir_lang.sdk.ProcessOutput.getProcessOutput(
- 10 * 1000,
- sdkHome,
- erl.getAbsolutePath(),
- "-noshell",
- "-eval",
- PRINT_VERSION_INFO_EXPRESSION
- );
-
- if (!(output.getExitCode() != 0 || output.isCancelled() || output.isTimeout())) {
- release = parseSdkVersion(output.getStdoutLines());
- }
-
- if (release != null) {
- releaseBySdkHome.put(getVersionCacheKey(sdkHome), release);
- } else {
- LOGGER.warn("Failed to detect Erlang version.\n" +
- "StdOut: " + output.getStdout() + "\n" +
- "StdErr: " + output.getStderr());
- }
- } catch (ExecutionException e) {
- LOGGER.warn(e);
- }
- }
- }
-
- return release;
- }
-}
diff --git a/src/org/elixir_lang/sdk/erlang/Type.kt b/src/org/elixir_lang/sdk/erlang/Type.kt
new file mode 100644
index 000000000..f056ee33e
--- /dev/null
+++ b/src/org/elixir_lang/sdk/erlang/Type.kt
@@ -0,0 +1,198 @@
+package org.elixir_lang.sdk.erlang
+
+import com.intellij.execution.ExecutionException
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.diagnostic.Logger
+import com.intellij.openapi.projectRoots.Sdk
+import com.intellij.openapi.projectRoots.SdkModel
+import com.intellij.openapi.projectRoots.SdkModificator
+import com.intellij.openapi.projectRoots.SdkType
+import com.intellij.openapi.roots.OrderRootType
+import com.intellij.openapi.util.SystemInfo
+import com.intellij.openapi.util.Version
+import com.intellij.util.containers.ContainerUtil
+import org.elixir_lang.jps.HomePath
+import org.elixir_lang.jps.sdk_type.Erlang
+import org.elixir_lang.sdk.erlang_dependent.AdditionalDataConfigurable
+import org.jdom.Element
+import java.io.File
+
+class Type : SdkType("Erlang SDK for Elixir SDK") {
+ private val releaseBySdkHome: MutableMap = ContainerUtil.createWeakMap()
+
+ companion object {
+ private const val OTP_RELEASE_PREFIX_LINE = "org.elixir_lang.sdk.erlang.Type OTP_RELEASE:"
+ private const val ERTS_VERSION_PREFIX_LINE = "org.elixir_lang.sdk.erlang.Type ERTS_VERSION:"
+ private const val PRINT_VERSION_INFO_EXPRESSION =
+ "io:format(\"~n~s~n~s~n~s~n~s~n\",[" +
+ "\"$OTP_RELEASE_PREFIX_LINE\"," +
+ "erlang:system_info(otp_release)," +
+ "\"$ERTS_VERSION_PREFIX_LINE\"," +
+ "erlang:system_info(version)" +
+ "]),erlang:halt()."
+ private const val WINDOWS_DEFAULT_HOME_PATH = "C:\\Program Files\\erl9.0"
+ private val NIX_PATTERN = HomePath.nixPattern("erlang")
+ private const val LINUX_MINT_HOME_PATH = "${HomePath.LINUX_MINT_HOME_PATH}/erlang"
+ private const val LINUX_DEFAULT_HOME_PATH = "${HomePath.LINUX_DEFAULT_HOME_PATH}/erlang"
+ private val VERSION_PATH_TO_HOME_PATH: (File) -> File = { versionPath -> File(versionPath, "lib/erlang") }
+ private val LOGGER = Logger.getInstance(Type::class.java)
+
+ @JvmStatic
+ fun getDefaultSdkName(
+ sdkHome: String,
+ version: Release?,
+ ): String =
+ buildString {
+ append("Erlang for Elixir ")
+ if (version != null) {
+ append(version.otpRelease)
+ } else {
+ append(" at ").append(sdkHome)
+ }
+ }
+
+ private fun getVersionCacheKey(sdkHome: String?): String? = sdkHome?.let { File(it).absolutePath }
+
+ private fun parseSdkVersion(printVersionInfoOutput: List): Release? {
+ var otpRelease: String? = null
+ var ertsVersion: String? = null
+
+ val iterator = printVersionInfoOutput.listIterator()
+ while (iterator.hasNext()) {
+ when (val line = iterator.next()) {
+ OTP_RELEASE_PREFIX_LINE -> if (iterator.hasNext()) otpRelease = iterator.next()
+ ERTS_VERSION_PREFIX_LINE -> if (iterator.hasNext()) ertsVersion = iterator.next()
+ }
+ }
+
+ return if (otpRelease != null && ertsVersion != null) Release(otpRelease, ertsVersion) else null
+ }
+
+ @JvmStatic
+ fun homePathByVersion(): Map {
+ val homePathByVersion = HomePath.homePathByVersion().toMutableMap()
+
+ when {
+ SystemInfo.isMac -> {
+ HomePath.mergeASDF(homePathByVersion, "erlang")
+ HomePath.mergeHomebrew(homePathByVersion, "erlang", VERSION_PATH_TO_HOME_PATH)
+ HomePath.mergeNixStore(homePathByVersion, NIX_PATTERN, VERSION_PATH_TO_HOME_PATH)
+ }
+
+ SystemInfo.isWindows -> {
+ putIfDirectory(homePathByVersion, HomePath.UNKNOWN_VERSION, WINDOWS_DEFAULT_HOME_PATH)
+ }
+
+ SystemInfo.isLinux -> {
+ putIfDirectory(homePathByVersion, HomePath.UNKNOWN_VERSION, LINUX_DEFAULT_HOME_PATH)
+ putIfDirectory(homePathByVersion, HomePath.UNKNOWN_VERSION, LINUX_MINT_HOME_PATH)
+ HomePath.mergeTravisCIKerl(homePathByVersion) { it }
+ HomePath.mergeNixStore(homePathByVersion, NIX_PATTERN, VERSION_PATH_TO_HOME_PATH)
+ }
+ }
+
+ return homePathByVersion
+ }
+
+ private fun putIfDirectory(
+ homePathByVersion: MutableMap,
+ version: Version,
+ homePath: String,
+ ) {
+ val homeFile = File(homePath)
+ if (homeFile.isDirectory) {
+ homePathByVersion[version] = homePath
+ }
+ }
+ }
+
+ override fun isRootTypeApplicable(type: OrderRootType): Boolean =
+ type == OrderRootType.CLASSES ||
+ type == OrderRootType.SOURCES ||
+ type ==
+ org.elixir_lang.sdk.Type
+ .documentationRootType()
+
+ override fun setupSdkPaths(sdk: Sdk) {
+ val sdkModificator = sdk.sdkModificator
+ org.elixir_lang.sdk.Type
+ .addCodePaths(sdkModificator)
+ ApplicationManager.getApplication().runWriteAction { sdkModificator.commitChanges() }
+ }
+
+ override fun suggestHomePath(): String? = suggestHomePaths().firstOrNull()
+
+ override fun suggestHomePaths(): Collection = homePathByVersion().values
+
+ override fun isValidSdkHome(path: String): Boolean = Erlang.getByteCodeInterpreterExecutable(path).canExecute()
+
+ override fun suggestSdkName(
+ currentSdkName: String?,
+ sdkHome: String,
+ ): String = getDefaultSdkName(sdkHome, detectSdkVersion(sdkHome))
+
+ override fun getVersionString(sdkHome: String): String? = detectSdkVersion(sdkHome)?.otpRelease
+
+ override fun createAdditionalDataConfigurable(
+ sdkModel: SdkModel,
+ sdkModificator: SdkModificator,
+ ): AdditionalDataConfigurable? = null
+
+ override fun getPresentableName(): String = name
+
+ override fun saveAdditionalData(
+ additionalData: com.intellij.openapi.projectRoots.SdkAdditionalData,
+ additional: Element,
+ ) {
+ // Intentionally left blank
+ }
+
+ private fun detectSdkVersion(sdkHome: String): Release? {
+ val cachedRelease = getVersionCacheKey(sdkHome)?.let { releaseBySdkHome[it] }
+ if (cachedRelease != null) return cachedRelease
+
+ val erl = Erlang.getByteCodeInterpreterExecutable(sdkHome)
+ if (!erl.canExecute()) {
+ val message =
+ buildString {
+ append("Can't detect Erlang version: ${erl.path}")
+ if (erl.exists()) append(" is not executable.") else append(" is missing.")
+ }
+ LOGGER.warn(message)
+ return null
+ }
+
+ var release: Release? = null
+
+ ApplicationManager
+ .getApplication()
+ .executeOnPooledThread {
+ try {
+ val output =
+ org.elixir_lang.sdk.ProcessOutput.getProcessOutput(
+ 10 * 1000,
+ sdkHome,
+ erl.absolutePath,
+ "-noshell",
+ "-eval",
+ PRINT_VERSION_INFO_EXPRESSION,
+ )
+
+ if (output.exitCode == 0 && !output.isCancelled && !output.isTimeout) {
+ release =
+ parseSdkVersion(output.stdoutLines)?.also { detectedRelease ->
+ getVersionCacheKey(sdkHome)?.let { key ->
+ releaseBySdkHome[key] = detectedRelease
+ }
+ }
+ } else {
+ LOGGER.warn("Failed to detect Erlang version.\nStdOut: ${output.stdout}\nStdErr: ${output.stderr}")
+ }
+ } catch (e: ExecutionException) {
+ LOGGER.warn(e)
+ }
+ }.get() // Wait for the task to complete
+
+ return release
+ }
+}
diff --git a/src/org/elixir_lang/sdk/erlang_dependent/AdditionalDataConfigurable.java b/src/org/elixir_lang/sdk/erlang_dependent/AdditionalDataConfigurable.java
deleted file mode 100644
index 46bc2bbff..000000000
--- a/src/org/elixir_lang/sdk/erlang_dependent/AdditionalDataConfigurable.java
+++ /dev/null
@@ -1,243 +0,0 @@
-package org.elixir_lang.sdk.erlang_dependent;
-
-import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.options.ConfigurationException;
-import com.intellij.openapi.projectRoots.Sdk;
-import com.intellij.openapi.projectRoots.SdkModel;
-import com.intellij.openapi.projectRoots.SdkModificator;
-import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl;
-import com.intellij.openapi.ui.ComboBox;
-import com.intellij.openapi.util.Comparing;
-import com.intellij.ui.SimpleListCellRenderer;
-import com.intellij.util.ui.JBUI;
-import org.elixir_lang.sdk.elixir.Type;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.event.ItemEvent;
-import java.util.Objects;
-
-import static org.elixir_lang.sdk.elixir.Type.addNewCodePathsFromInternErlangSdk;
-import static org.elixir_lang.sdk.elixir.Type.removeCodePathsFromInternalErlangSdk;
-import static org.elixir_lang.sdk.erlang_dependent.Type.staticIsValidDependency;
-
-public class AdditionalDataConfigurable implements com.intellij.openapi.projectRoots.AdditionalDataConfigurable {
- private final JLabel internalErlangSdkLabel = new JLabel("Internal Erlang SDK:");
- private final DefaultComboBoxModel internalErlangSdksComboBoxModel = new DefaultComboBoxModel<>();
- private final ComboBox internalErlangSdksComboBox = new ComboBox(internalErlangSdksComboBoxModel);
- private final SdkModel sdkModel;
- private final SdkModel.Listener sdkModelListener;
- private final SdkModificator sdkModificator;
- private Sdk elixirSdk;
- private boolean modified;
- private boolean freeze = false;
-
- public AdditionalDataConfigurable(@NotNull SdkModel sdkModel, SdkModificator sdkModificator) {
- this.sdkModel = sdkModel;
- this.sdkModificator = sdkModificator;
-
- sdkModelListener = new SdkModel.Listener() {
- public void sdkAdded(Sdk sdk) {
- if (staticIsValidDependency(sdk)) {
- addErlangSdk(sdk);
- }
- }
-
- public void beforeSdkRemove(Sdk sdk) {
- if (staticIsValidDependency(sdk)) {
- removeErlangSdk(sdk);
- }
- }
-
- public void sdkChanged(Sdk sdk, String previousName) {
- if (staticIsValidDependency(sdk)) {
- updateErlangSdkList(sdk, previousName);
- }
- }
-
- public void sdkHomeSelected(final Sdk sdk, final String newSdkHome) {
- if (staticIsValidDependency(sdk)) {
- internalErlangSdkUpdate(sdk);
- }
- }
- };
- this.sdkModel.addListener(sdkModelListener);
- }
-
- private void updateJdkList() {
- internalErlangSdksComboBoxModel.removeAllElements();
-
- for (Sdk sdk : sdkModel.getSdks()) {
- if (staticIsValidDependency(sdk)) {
- internalErlangSdksComboBoxModel.addElement(sdk);
- }
- }
- }
-
- public void setSdk(Sdk sdk) {
- elixirSdk = sdk;
- }
-
- public JComponent createComponent() {
- JPanel wholePanel = new JPanel(new GridBagLayout());
-
- wholePanel.add(
- internalErlangSdkLabel,
- new GridBagConstraints(
- 0,
- GridBagConstraints.RELATIVE,
- 1,
- 1,
- 0,
- 1,
- GridBagConstraints.WEST,
- GridBagConstraints.NONE,
- JBUI.emptyInsets(),
- 0,
- 0
- )
- );
- wholePanel.add(
- internalErlangSdksComboBox,
- new GridBagConstraints(
- 1,
- GridBagConstraints.RELATIVE,
- 1,
- 1,
- 1,
- 1,
- GridBagConstraints.EAST,
- GridBagConstraints.HORIZONTAL,
- JBUI.insets(0, 30, 0, 0),
- 0,
- 0
- )
- );
- internalErlangSdksComboBox.setRenderer(
- new SimpleListCellRenderer() {
- @Override
- public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) {
- if (value instanceof Sdk) {
- setText(((Sdk) value).getName());
- }
- }
- }
- );
- internalErlangSdksComboBox.addItemListener(itemEvent -> {
- if (!freeze) {
- final int stateChange = itemEvent.getStateChange();
- final Sdk internalErlangSdk = (Sdk) itemEvent.getItem();
-
- if (stateChange == ItemEvent.DESELECTED) {
- removeCodePathsFromInternalErlangSdk(elixirSdk, internalErlangSdk, sdkModificator);
- } else if (stateChange == ItemEvent.SELECTED) {
- addNewCodePathsFromInternErlangSdk(elixirSdk, internalErlangSdk, sdkModificator);
- modified = true;
- }
- }
- });
-
- modified = true;
-
- return wholePanel;
- }
-
- private void internalErlangSdkUpdate(@NotNull final Sdk sdk) {
- SdkAdditionalData sdkAdditionalData = (SdkAdditionalData) sdk.getSdkAdditionalData();
- Sdk erlangSdk;
-
- if (sdkAdditionalData != null) {
- erlangSdk = sdkAdditionalData.getErlangSdk();
- } else {
- erlangSdk = null;
- }
-
- if (erlangSdk == null || internalErlangSdksComboBoxModel.getIndexOf(erlangSdk) == -1) {
- internalErlangSdksComboBoxModel.addElement(erlangSdk);
- } else {
- internalErlangSdksComboBoxModel.setSelectedItem(erlangSdk);
- }
- }
-
- public boolean isModified() {
- return modified;
- }
-
- public void apply() throws ConfigurationException {
- writeInternalErlangSdk((Sdk) internalErlangSdksComboBox.getSelectedItem());
- modified = false;
- }
-
- private void writeInternalErlangSdk(@Nullable Sdk erlangSdk) {
- writeInternalErlangSdk(elixirSdk.getSdkModificator(), erlangSdk);
- }
-
- private void writeInternalErlangSdk(@NotNull SdkModificator sdkModificator, @Nullable Sdk erlangSdk) {
- SdkAdditionalData sdkAdditionData = new SdkAdditionalData(
- erlangSdk,
- elixirSdk
- );
- sdkModificator.setSdkAdditionalData(sdkAdditionData);
- ApplicationManager.getApplication().runWriteAction(sdkModificator::commitChanges);
- ((ProjectJdkImpl) elixirSdk).resetVersionString();
- }
-
- public void reset() {
- freeze = true;
- updateJdkList();
- freeze = false;
-
- if (elixirSdk != null && elixirSdk.getSdkAdditionalData() instanceof SdkAdditionalData) {
- final SdkAdditionalData sdkAdditionalData = (SdkAdditionalData) elixirSdk.getSdkAdditionalData();
- final Sdk erlangSdk = sdkAdditionalData.getErlangSdk();
-
- if (erlangSdk != null) {
- for (int i = 0; i < internalErlangSdksComboBoxModel.getSize(); i++) {
- if (Comparing.strEqual(
- internalErlangSdksComboBoxModel.getElementAt(i).getName(),
- erlangSdk.getName()
- )) {
- internalErlangSdksComboBox.setSelectedIndex(i);
- break;
- }
- }
- }
-
- modified = false;
- }
- }
-
- public void disposeUIResources() {
- sdkModel.removeListener(sdkModelListener);
- }
-
- private void addErlangSdk(final Sdk sdk) {
- internalErlangSdksComboBoxModel.addElement(sdk);
- }
-
- private void removeErlangSdk(final Sdk sdk) {
- if (internalErlangSdksComboBoxModel.getSelectedItem().equals(sdk)) {
- modified = true;
- }
-
- internalErlangSdksComboBoxModel.removeElement(sdk);
- }
-
- private void updateErlangSdkList(Sdk sdk, String previousName) {
- final Sdk[] sdks = sdkModel.getSdks();
-
- for (Sdk currentSdk : sdks) {
- if (currentSdk.getSdkType() instanceof Type) {
- final SdkAdditionalData sdkAdditionalData = (SdkAdditionalData) currentSdk.getSdkAdditionalData();
- final Sdk erlangSdk = sdkAdditionalData.getErlangSdk();
-
- if (erlangSdk != null && Objects.equals(erlangSdk.getName(), previousName)) {
- sdkAdditionalData.setErlangSdk(sdk);
- }
- }
- }
- updateJdkList();
- }
-}
diff --git a/src/org/elixir_lang/sdk/erlang_dependent/AdditionalDataConfigurable.kt b/src/org/elixir_lang/sdk/erlang_dependent/AdditionalDataConfigurable.kt
new file mode 100644
index 000000000..4262bf903
--- /dev/null
+++ b/src/org/elixir_lang/sdk/erlang_dependent/AdditionalDataConfigurable.kt
@@ -0,0 +1,256 @@
+package org.elixir_lang.sdk.erlang_dependent
+
+import com.intellij.openapi.application.ApplicationManager
+import com.intellij.openapi.options.ConfigurationException
+import com.intellij.openapi.projectRoots.AdditionalDataConfigurable
+import com.intellij.openapi.projectRoots.Sdk
+import com.intellij.openapi.projectRoots.SdkModel
+import com.intellij.openapi.projectRoots.SdkModificator
+import com.intellij.openapi.ui.ComboBox
+import com.intellij.openapi.util.Comparing
+import com.intellij.ui.SimpleListCellRenderer
+import com.intellij.util.ui.JBUI
+import org.elixir_lang.sdk.elixir.Type.Companion.addNewCodePathsFromInternErlangSdk
+import org.elixir_lang.sdk.elixir.Type.Companion.removeCodePathsFromInternalErlangSdk
+import java.awt.GridBagConstraints
+import java.awt.GridBagLayout
+import java.awt.event.ItemEvent
+import javax.swing.*
+
+class AdditionalDataConfigurable(
+ private val sdkModel: SdkModel,
+ private val sdkModificator: SdkModificator,
+) : AdditionalDataConfigurable {
+ private val internalErlangSdkLabel = JLabel("Internal Erlang SDK:")
+ private val internalErlangSdksComboBoxModel = DefaultComboBoxModel()
+ private val internalErlangSdksComboBox = ComboBox(internalErlangSdksComboBoxModel)
+ private val sdkModelListener: SdkModel.Listener
+ private var elixirSdk: Sdk? = null
+ private var modified = false
+ private var freeze = false
+
+ init {
+ sdkModelListener =
+ object : SdkModel.Listener {
+ override fun sdkAdded(sdk: Sdk) {
+ if (Type.staticIsValidDependency(sdk)) {
+ addErlangSdk(sdk)
+ }
+ }
+
+ override fun beforeSdkRemove(sdk: Sdk) {
+ if (Type.staticIsValidDependency(sdk)) {
+ removeErlangSdk(sdk)
+ }
+ }
+
+ override fun sdkChanged(
+ sdk: Sdk,
+ previousName: String,
+ ) {
+ if (Type.staticIsValidDependency(sdk)) {
+ updateErlangSdkList(sdk, previousName)
+ }
+ }
+
+ override fun sdkHomeSelected(
+ sdk: Sdk,
+ newSdkHome: String,
+ ) {
+ if (Type.staticIsValidDependency(sdk)) {
+ internalErlangSdkUpdate(sdk)
+ }
+ }
+ }
+ sdkModel.addListener(sdkModelListener)
+ }
+
+ private fun updateJdkList() {
+ internalErlangSdksComboBoxModel.removeAllElements()
+
+ for (sdk in sdkModel.sdks) {
+ if (Type.staticIsValidDependency(sdk)) {
+ internalErlangSdksComboBoxModel.addElement(sdk)
+ }
+ }
+ }
+
+ override fun setSdk(sdk: Sdk) {
+ elixirSdk = sdk
+ }
+
+ override fun createComponent(): JComponent {
+ val wholePanel = JPanel(GridBagLayout())
+
+ wholePanel.add(
+ internalErlangSdkLabel,
+ GridBagConstraints(
+ 0,
+ GridBagConstraints.RELATIVE,
+ 1,
+ 1,
+ 0.0,
+ 1.0,
+ GridBagConstraints.WEST,
+ GridBagConstraints.NONE,
+ JBUI.emptyInsets(),
+ 0,
+ 0,
+ ),
+ )
+ wholePanel.add(
+ internalErlangSdksComboBox,
+ GridBagConstraints(
+ 1,
+ GridBagConstraints.RELATIVE,
+ 1,
+ 1,
+ 1.0,
+ 1.0,
+ GridBagConstraints.EAST,
+ GridBagConstraints.HORIZONTAL,
+ JBUI.insetsLeft(30),
+ 0,
+ 0,
+ ),
+ )
+ internalErlangSdksComboBox.setRenderer(
+ object : SimpleListCellRenderer() {
+ override fun customize(
+ list: JList,
+ value: Sdk?,
+ index: Int,
+ selected: Boolean,
+ hasFocus: Boolean,
+ ) {
+ text = value?.name
+ }
+ },
+ )
+ internalErlangSdksComboBox.addItemListener { itemEvent: ItemEvent ->
+ if (!freeze) {
+ val stateChange = itemEvent.stateChange
+ val internalErlangSdk = itemEvent.item as Sdk
+
+ if (stateChange == ItemEvent.DESELECTED) {
+ removeCodePathsFromInternalErlangSdk(
+ elixirSdk!!,
+ internalErlangSdk,
+ sdkModificator,
+ )
+ } else if (stateChange == ItemEvent.SELECTED) {
+ addNewCodePathsFromInternErlangSdk(
+ elixirSdk!!,
+ internalErlangSdk,
+ sdkModificator,
+ )
+ modified = true
+ }
+ }
+ }
+
+ modified = true
+
+ return wholePanel
+ }
+
+ private fun internalErlangSdkUpdate(sdk: Sdk) {
+ val sdkAdditionalData = sdk.sdkAdditionalData as SdkAdditionalData?
+
+ val erlangSdk = sdkAdditionalData?.getErlangSdk()
+
+ if (erlangSdk == null || internalErlangSdksComboBoxModel.getIndexOf(erlangSdk) == -1) {
+ internalErlangSdksComboBoxModel.addElement(erlangSdk)
+ } else {
+ internalErlangSdksComboBoxModel.setSelectedItem(erlangSdk)
+ }
+ }
+
+ override fun isModified(): Boolean = modified
+
+ @Throws(ConfigurationException::class)
+ override fun apply() {
+ writeInternalErlangSdk(internalErlangSdksComboBox.selectedItem as Sdk)
+ modified = false
+ }
+
+ private fun writeInternalErlangSdk(erlangSdk: Sdk?) {
+ writeInternalErlangSdk(elixirSdk!!.sdkModificator, erlangSdk)
+ }
+
+ private fun writeInternalErlangSdk(
+ sdkModificator: SdkModificator,
+ erlangSdk: Sdk?,
+ ) {
+ val sdkAdditionData =
+ SdkAdditionalData(
+ erlangSdk,
+ elixirSdk!!,
+ )
+ sdkModificator.sdkAdditionalData = sdkAdditionData
+ ApplicationManager.getApplication().runWriteAction { sdkModificator.commitChanges() }
+
+ this.sdkModificator.commitChanges()
+ }
+
+ override fun reset() {
+ freeze = true
+ updateJdkList()
+ freeze = false
+
+ if (elixirSdk != null && elixirSdk!!.sdkAdditionalData is SdkAdditionalData) {
+ val sdkAdditionalData = elixirSdk!!.sdkAdditionalData as SdkAdditionalData?
+ val erlangSdk = sdkAdditionalData!!.getErlangSdk()
+
+ if (erlangSdk != null) {
+ for (i in 0 until internalErlangSdksComboBoxModel.size) {
+ if (Comparing.strEqual(
+ internalErlangSdksComboBoxModel.getElementAt(i)!!.name,
+ erlangSdk.name,
+ )
+ ) {
+ internalErlangSdksComboBox.selectedIndex = i
+ break
+ }
+ }
+ }
+
+ modified = false
+ }
+ }
+
+ override fun disposeUIResources() {
+ sdkModel.removeListener(sdkModelListener)
+ }
+
+ private fun addErlangSdk(sdk: Sdk) {
+ internalErlangSdksComboBoxModel.addElement(sdk)
+ }
+
+ private fun removeErlangSdk(sdk: Sdk) {
+ if (internalErlangSdksComboBoxModel.selectedItem == sdk) {
+ modified = true
+ }
+
+ internalErlangSdksComboBoxModel.removeElement(sdk)
+ }
+
+ private fun updateErlangSdkList(
+ sdk: Sdk,
+ previousName: String,
+ ) {
+ val sdks = sdkModel.sdks
+
+ for (currentSdk in sdks) {
+ if (currentSdk.sdkType is org.elixir_lang.sdk.elixir.Type) {
+ val sdkAdditionalData = currentSdk.sdkAdditionalData as SdkAdditionalData?
+ val erlangSdk = sdkAdditionalData!!.getErlangSdk()
+
+ if (erlangSdk != null && erlangSdk.name == previousName) {
+ sdkAdditionalData.setErlangSdk(sdk)
+ }
+ }
+ }
+ updateJdkList()
+ }
+}
diff --git a/src/org/elixir_lang/sdk/erlang_dependent/SdkAdditionalData.java b/src/org/elixir_lang/sdk/erlang_dependent/SdkAdditionalData.java
deleted file mode 100644
index e06ea72b6..000000000
--- a/src/org/elixir_lang/sdk/erlang_dependent/SdkAdditionalData.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package org.elixir_lang.sdk.erlang_dependent;
-
-import com.intellij.openapi.options.ConfigurationException;
-import com.intellij.openapi.projectRoots.ProjectJdkTable;
-import com.intellij.openapi.projectRoots.Sdk;
-import com.intellij.openapi.projectRoots.SdkModel;
-import com.intellij.openapi.projectRoots.ValidatableSdkAdditionalData;
-import com.intellij.openapi.util.InvalidDataException;
-import com.intellij.openapi.util.WriteExternalException;
-import org.elixir_lang.sdk.elixir.Type;
-import org.jdom.Element;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-public class SdkAdditionalData implements ValidatableSdkAdditionalData {
- @NotNull
- private final Sdk elixirSdk;
- @Nullable
- private Sdk erlangSdk;
- @Nullable
- private String erlangSdkName;
- private static final String ERLANG_SDK_NAME = "erlang-sdk-name";
-
- public SdkAdditionalData(@Nullable Sdk erlangSdk, @NotNull Sdk elixirSdk) {
- this.erlangSdk = erlangSdk;
- this.elixirSdk = elixirSdk;
- }
-
- // readExternal
- public SdkAdditionalData(@NotNull Sdk elixirSdk) {
- this.elixirSdk = elixirSdk;
- }
-
-
- /**
- * Checks if the ERLANG_SDK_NAME properties are configured correctly, and throws an exception
- * if they are not.
- *
- * @param sdkModel the model containing all configured SDKs.
- * @throws ConfigurationException if the ERLANG_SDK_NAME is not configured correctly.
- * @since 5.0.1
- */
- @Override
- public void checkValid(SdkModel sdkModel) throws ConfigurationException {
- if (getErlangSdk() == null) {
- throw new ConfigurationException("Please configure the Erlang ERLANG_SDK_NAME");
- }
- }
-
- @Override
- public Object clone() throws CloneNotSupportedException {
- return new SdkAdditionalData(erlangSdk, elixirSdk);
- }
-
- public void readExternal(Element element) throws InvalidDataException {
- erlangSdkName = element.getAttributeValue(ERLANG_SDK_NAME);
- }
-
- public void writeExternal(Element element) throws WriteExternalException {
- final Sdk sdk = getErlangSdk();
-
- if (sdk != null) {
- element.setAttribute(ERLANG_SDK_NAME, sdk.getName());
- }
- }
-
- @Nullable
- public Sdk getErlangSdk() {
- final ProjectJdkTable jdkTable = ProjectJdkTable.getInstance();
-
- if (erlangSdk == null) {
- if (erlangSdkName != null) {
- erlangSdk = jdkTable.findJdk(erlangSdkName);
- erlangSdkName = null;
- } else {
- for (Sdk jdk : jdkTable.getAllJdks()) {
- if (Type.staticIsValidDependency(jdk)) {
- erlangSdk = jdk;
- break;
- }
- }
- }
- }
-
- return erlangSdk;
- }
-
- public void setErlangSdk(@Nullable Sdk erlangSdk) {
- this.erlangSdk = erlangSdk;
- }
-}
diff --git a/src/org/elixir_lang/sdk/erlang_dependent/SdkAdditionalData.kt b/src/org/elixir_lang/sdk/erlang_dependent/SdkAdditionalData.kt
new file mode 100644
index 000000000..90b4354ff
--- /dev/null
+++ b/src/org/elixir_lang/sdk/erlang_dependent/SdkAdditionalData.kt
@@ -0,0 +1,88 @@
+package org.elixir_lang.sdk.erlang_dependent
+
+import com.intellij.openapi.options.ConfigurationException
+import com.intellij.openapi.projectRoots.ProjectJdkTable
+import com.intellij.openapi.projectRoots.Sdk
+import com.intellij.openapi.projectRoots.SdkModel
+import com.intellij.openapi.projectRoots.ValidatableSdkAdditionalData
+import com.intellij.openapi.util.InvalidDataException
+import com.intellij.openapi.util.WriteExternalException
+import org.jdom.Element
+
+class SdkAdditionalData :
+ ValidatableSdkAdditionalData,
+ Cloneable {
+ private val elixirSdk: Sdk
+ private var erlangSdk: Sdk? = null
+ private var erlangSdkName: String? = null
+
+ constructor(erlangSdk: Sdk?, elixirSdk: Sdk) {
+ this.erlangSdk = erlangSdk
+ this.elixirSdk = elixirSdk
+ }
+
+ // readExternal
+ constructor(elixirSdk: Sdk) {
+ this.elixirSdk = elixirSdk
+ }
+
+ /**
+ * Checks if the ERLANG_SDK_NAME properties are configured correctly, and throws an exception
+ * if they are not.
+ *
+ * @param sdkModel the model containing all configured SDKs.
+ * @throws ConfigurationException if the ERLANG_SDK_NAME is not configured correctly.
+ * @since 5.0.1
+ */
+ @Throws(ConfigurationException::class)
+ override fun checkValid(sdkModel: SdkModel) {
+ if (getErlangSdk() == null) {
+ throw ConfigurationException("Please configure the Erlang ERLANG_SDK_NAME")
+ }
+ }
+
+ @Throws(CloneNotSupportedException::class)
+ public override fun clone(): Any = SdkAdditionalData(erlangSdk, elixirSdk)
+
+ @Throws(InvalidDataException::class)
+ fun readExternal(element: Element) {
+ erlangSdkName = element.getAttributeValue(ERLANG_SDK_NAME)
+ }
+
+ @Throws(WriteExternalException::class)
+ fun writeExternal(element: Element) {
+ val sdk = getErlangSdk()
+
+ if (sdk != null) {
+ element.setAttribute(ERLANG_SDK_NAME, sdk.name)
+ }
+ }
+
+ fun getErlangSdk(): Sdk? {
+ val jdkTable = ProjectJdkTable.getInstance()
+
+ if (erlangSdk == null) {
+ if (erlangSdkName != null) {
+ erlangSdk = jdkTable.findJdk(erlangSdkName!!)
+ erlangSdkName = null
+ } else {
+ for (jdk in jdkTable.allJdks) {
+ if (Type.staticIsValidDependency(jdk)) {
+ erlangSdk = jdk
+ break
+ }
+ }
+ }
+ }
+
+ return erlangSdk
+ }
+
+ fun setErlangSdk(erlangSdk: Sdk?) {
+ this.erlangSdk = erlangSdk
+ }
+
+ companion object {
+ private const val ERLANG_SDK_NAME = "erlang-sdk-name"
+ }
+}
diff --git a/src/org/elixir_lang/sdk/erlang_dependent/SdkModificatorRootTypeConsumer.java b/src/org/elixir_lang/sdk/erlang_dependent/SdkModificatorRootTypeConsumer.java
deleted file mode 100644
index d1e9199f5..000000000
--- a/src/org/elixir_lang/sdk/erlang_dependent/SdkModificatorRootTypeConsumer.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.elixir_lang.sdk.erlang_dependent;
-
-import com.intellij.openapi.projectRoots.SdkModificator;
-import com.intellij.openapi.roots.OrderRootType;
-import com.intellij.openapi.vfs.VirtualFile;
-import org.jetbrains.annotations.NotNull;
-
-public interface SdkModificatorRootTypeConsumer {
- void consume(@NotNull SdkModificator sdkModificator,
- @NotNull VirtualFile[] configuredRoots,
- @NotNull VirtualFile expandedInternalRoot,
- @NotNull OrderRootType type);
-}
diff --git a/src/org/elixir_lang/sdk/erlang_dependent/SdkModificatorRootTypeConsumer.kt b/src/org/elixir_lang/sdk/erlang_dependent/SdkModificatorRootTypeConsumer.kt
new file mode 100644
index 000000000..3c3364e33
--- /dev/null
+++ b/src/org/elixir_lang/sdk/erlang_dependent/SdkModificatorRootTypeConsumer.kt
@@ -0,0 +1,14 @@
+package org.elixir_lang.sdk.erlang_dependent
+
+import com.intellij.openapi.projectRoots.SdkModificator
+import com.intellij.openapi.roots.OrderRootType
+import com.intellij.openapi.vfs.VirtualFile
+
+interface SdkModificatorRootTypeConsumer {
+ fun consume(
+ sdkModificator: SdkModificator,
+ configuredRoots: Array?,
+ expandedInternalRoot: VirtualFile,
+ type: OrderRootType
+ )
+}
diff --git a/src/org/elixir_lang/sdk/erlang_dependent/Type.java b/src/org/elixir_lang/sdk/erlang_dependent/Type.java
deleted file mode 100644
index 7db1b9fbf..000000000
--- a/src/org/elixir_lang/sdk/erlang_dependent/Type.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package org.elixir_lang.sdk.erlang_dependent;
-
-import com.intellij.openapi.projectRoots.*;
-import com.intellij.openapi.projectRoots.SdkAdditionalData;
-import com.intellij.openapi.projectRoots.impl.DependentSdkType;
-import com.intellij.openapi.roots.JavadocOrderRootType;
-import com.intellij.openapi.roots.OrderRootType;
-import org.elixir_lang.sdk.ProcessOutput;
-import org.jdom.Element;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-import static org.elixir_lang.sdk.elixir.Type.erlangSdkType;
-
-/**
- * An SDK that depends on an Erlang SDK, either
- * * org.intellij.erlang.sdk.ErlangSdkType when intellij-erlang IS installed
- * * org.elixir_lang.sdk.erlang.Type when intellij-erlang IS NOT installed
- */
-public abstract class Type extends DependentSdkType {
- private static final String ERLANG_SDK_TYPE_CANONICAL_NAME = "org.intellij.erlang.sdk.ErlangSdkType";
- private static final String ERLANG_SDK_FOR_ELIXIR_SDK_TYPE_CANONICAL_NAME = org.elixir_lang.sdk.erlang.Type.class.getCanonicalName();
-
- protected Type(@NotNull String name) {
- super(name);
- }
-
- public static boolean staticIsValidDependency(Sdk sdk) {
- String sdkTypeCanonicalName = sdk.getSdkType().getClass().getCanonicalName();
- boolean isValidDependency;
-
- if (ProcessOutput.isSmallIde()) {
- isValidDependency = sdkTypeCanonicalName.equals(ERLANG_SDK_FOR_ELIXIR_SDK_TYPE_CANONICAL_NAME);
- } else {
- isValidDependency = sdkTypeCanonicalName.equals(ERLANG_SDK_TYPE_CANONICAL_NAME) ||
- sdkTypeCanonicalName.equals(ERLANG_SDK_FOR_ELIXIR_SDK_TYPE_CANONICAL_NAME);
- }
-
- return isValidDependency;
- }
-
- @Override
- protected boolean isValidDependency(Sdk sdk) {
- return staticIsValidDependency(sdk);
- }
-
- @Override
- public String getUnsatisfiedDependencyMessage() {
- return "You need to configure an " + getDependencyType().getName() + ". Click OK to be taken through the " +
- getDependencyType().getName() + " configuration. Click Cancel to stop configuring this SDK AND the "+
- getDependencyType().getName() + ".";
- }
-
- @Override
- public SdkType getDependencyType() {
- return erlangSdkType();
- }
-
- @Nullable
- @Override
- public com.intellij.openapi.projectRoots.AdditionalDataConfigurable createAdditionalDataConfigurable(
- @NotNull SdkModel sdkModel,
- @NotNull SdkModificator sdkModificator
- ) {
- return null;
- }
-
- @Override
- public boolean isRootTypeApplicable(@NotNull OrderRootType type) {
- return type == OrderRootType.CLASSES ||
- type == OrderRootType.SOURCES ||
- type == JavadocOrderRootType.getInstance();
- }
-
- @Override
- public void saveAdditionalData(@NotNull SdkAdditionalData additionalData, @NotNull Element additional) {
- // intentionally left blank
- }
-}
diff --git a/src/org/elixir_lang/sdk/erlang_dependent/Type.kt b/src/org/elixir_lang/sdk/erlang_dependent/Type.kt
new file mode 100644
index 000000000..cfc827ebc
--- /dev/null
+++ b/src/org/elixir_lang/sdk/erlang_dependent/Type.kt
@@ -0,0 +1,66 @@
+package org.elixir_lang.sdk.erlang_dependent
+
+import com.intellij.openapi.projectRoots.*
+import com.intellij.openapi.projectRoots.AdditionalDataConfigurable
+import com.intellij.openapi.projectRoots.SdkAdditionalData
+import com.intellij.openapi.projectRoots.impl.DependentSdkType
+import com.intellij.openapi.roots.JavadocOrderRootType
+import com.intellij.openapi.roots.OrderRootType
+import org.elixir_lang.sdk.ProcessOutput.isSmallIde
+import org.elixir_lang.sdk.elixir.Type.Companion.erlangSdkType
+import org.elixir_lang.sdk.erlang.Type
+import org.jdom.Element
+
+/**
+ * An SDK that depends on an Erlang SDK, either
+ * * org.intellij.erlang.sdk.ErlangSdkType when intellij-erlang IS installed
+ * * org.elixir_lang.sdk.erlang.Type when intellij-erlang IS NOT installed
+ */
+abstract class Type protected constructor(name: String) : DependentSdkType(name) {
+ override fun isValidDependency(sdk: Sdk): Boolean {
+ return staticIsValidDependency(sdk)
+ }
+
+ override fun getUnsatisfiedDependencyMessage(): String {
+ return "You need to configure an " + dependencyType.name + ". Click OK to be taken through the " +
+ dependencyType.name + " configuration. Click Cancel to stop configuring this SDK AND the " +
+ dependencyType.name + "."
+ }
+
+ override fun getDependencyType(): SdkType {
+ return erlangSdkType()
+ }
+
+ override fun createAdditionalDataConfigurable(
+ sdkModel: SdkModel,
+ sdkModificator: SdkModificator
+ ): AdditionalDataConfigurable? {
+ return null
+ }
+
+ override fun isRootTypeApplicable(type: OrderRootType): Boolean {
+ return type === OrderRootType.CLASSES || type === OrderRootType.SOURCES || type === JavadocOrderRootType.getInstance()
+ }
+
+ override fun saveAdditionalData(additionalData: SdkAdditionalData, additional: Element) {
+ // intentionally left blank
+ }
+
+ companion object {
+ private const val ERLANG_SDK_TYPE_CANONICAL_NAME = "org.intellij.erlang.sdk.ErlangSdkType"
+ private val ERLANG_SDK_FOR_ELIXIR_SDK_TYPE_CANONICAL_NAME: String = Type::class.java.canonicalName
+
+ fun staticIsValidDependency(sdk: Sdk): Boolean {
+ val sdkTypeCanonicalName = sdk.sdkType.javaClass.canonicalName
+
+ val isValidDependency = if (isSmallIde) {
+ sdkTypeCanonicalName == ERLANG_SDK_FOR_ELIXIR_SDK_TYPE_CANONICAL_NAME
+ } else {
+ sdkTypeCanonicalName == ERLANG_SDK_TYPE_CANONICAL_NAME ||
+ sdkTypeCanonicalName == ERLANG_SDK_FOR_ELIXIR_SDK_TYPE_CANONICAL_NAME
+ }
+
+ return isValidDependency
+ }
+ }
+}