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 + } + } +}