diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/direnv/DirenvService.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/direnv/DirenvService.kt index 3d2f1881..2e8d0807 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/direnv/DirenvService.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/direnv/DirenvService.kt @@ -33,6 +33,7 @@ import com.intellij.openapi.components.* import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.util.Key +import com.intellij.openapi.util.UserDataHolder import com.intellij.openapi.vfs.toNioPathOrNull import com.intellij.platform.util.progress.withProgressText import com.intellij.util.io.awaitExit @@ -140,20 +141,14 @@ class DirenvService(val project: Project): SerializablePersistentStateComponent< private const val GROUP_DISPLAY_ID = "zigbrains-direnv" fun getInstance(project: Project): IDirenvService = project.service() - val STATE_KEY = Key.create("DIRENV_STATE") - } -} + private val STATE_KEY = Key.create("DIRENV_STATE") -enum class DirenvState { - Auto, - Enabled, - Disabled; + fun getStateFor(data: UserDataHolder, project: Project?): DirenvState { + return data.getUserData(STATE_KEY) ?: project?.let { getInstance(project).isEnabled } ?: DirenvState.Disabled + } - fun isEnabled(project: Project?): Boolean { - return when(this) { - Enabled -> true - Disabled -> false - Auto -> project?.service()?.hasDotEnv() == true + fun setStateFor(data: UserDataHolder, state: DirenvState) { + data.putUserData(STATE_KEY, state) } } } diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/direnv/DirenvState.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/direnv/DirenvState.kt new file mode 100644 index 00000000..f4c96608 --- /dev/null +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/direnv/DirenvState.kt @@ -0,0 +1,40 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * ZigBrains is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * ZigBrains is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with ZigBrains. If not, see . + */ + +package com.falsepattern.zigbrains.direnv + +import com.intellij.openapi.components.service +import com.intellij.openapi.project.Project + +enum class DirenvState { + Auto, + Enabled, + Disabled; + + fun isEnabled(project: Project?): Boolean { + return when(this) { + Enabled -> true + Disabled -> false + Auto -> project?.service()?.hasDotEnv() == true + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/direnv/ui/DirenvEditor.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/direnv/ui/DirenvEditor.kt index ff189ab8..e0231243 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/direnv/ui/DirenvEditor.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/direnv/ui/DirenvEditor.kt @@ -39,10 +39,12 @@ abstract class DirenvEditor(private val sharedState: ZigProjectConfigurationP row(ZigBrainsBundle.message("settings.direnv.enable.label")) { comboBox(DirenvState.entries).component.let { cb = it - it.addItemListener { e -> - if (e.stateChange != ItemEvent.SELECTED) - return@addItemListener - sharedState + if (sharedState != null) { + it.addItemListener { e -> + if (e.stateChange != ItemEvent.SELECTED) + return@addItemListener + DirenvService.setStateFor(sharedState, DirenvState.Auto) + } } } } @@ -72,7 +74,7 @@ abstract class DirenvEditor(private val sharedState: ZigProjectConfigurationP class ForProject(sharedState: ZigProjectConfigurationProvider.IUserDataBridge) : DirenvEditor(sharedState) { override fun isEnabled(context: Project): DirenvState { - return context.service().isEnabledRaw + return DirenvService.getInstance(context).isEnabled } override fun setEnabled(context: Project, value: DirenvState) { @@ -85,7 +87,7 @@ abstract class DirenvEditor(private val sharedState: ZigProjectConfigurationP if (project?.isDefault != false) { return null } - sharedState.putUserData(DirenvService.STATE_KEY, DirenvState.Auto) + DirenvService.setStateFor(sharedState, DirenvState.Auto) return ForProject(sharedState) } diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/base/ZigToolchainProvider.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/base/ZigToolchainProvider.kt index 7ecada79..c18a3923 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/base/ZigToolchainProvider.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/base/ZigToolchainProvider.kt @@ -24,24 +24,18 @@ package com.falsepattern.zigbrains.project.toolchain.base import com.falsepattern.zigbrains.direnv.DirenvState import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService -import com.falsepattern.zigbrains.shared.zigCoroutineScope import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project +import com.intellij.openapi.util.Key import com.intellij.openapi.util.UserDataHolder import com.intellij.ui.SimpleColoredComponent -import com.intellij.util.text.SemVer -import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.async import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.flatMap import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.flowOn -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull import java.util.UUID private val EXTENSION_POINT_NAME = ExtensionPointName.create("com.falsepattern.zigbrains.toolchainProvider") @@ -53,7 +47,7 @@ internal interface ZigToolchainProvider { fun serialize(toolchain: ZigToolchain): Map fun matchesSuggestion(toolchain: ZigToolchain, suggestion: ZigToolchain): Boolean fun createConfigurable(uuid: UUID, toolchain: ZigToolchain): ZigToolchainConfigurable<*> - suspend fun suggestToolchains(project: Project?, direnv: DirenvState): Flow + suspend fun suggestToolchains(project: Project?, data: UserDataHolder): Flow fun render(toolchain: ZigToolchain, component: SimpleColoredComponent, isSuggestion: Boolean, isSelected: Boolean) } @@ -75,11 +69,11 @@ fun ZigToolchain.createNamedConfigurable(uuid: UUID): ZigToolchainConfigurable<* } @OptIn(ExperimentalCoroutinesApi::class) -fun suggestZigToolchains(project: Project? = null, direnv: DirenvState = DirenvState.Disabled): Flow { +fun suggestZigToolchains(project: Project? = null, data: UserDataHolder = emptyData): Flow { val existing = ZigToolchainListService.getInstance().toolchains.map { (_, tc) -> tc }.toList() return EXTENSION_POINT_NAME.extensionList.asFlow().flatMapConcat { ext -> val compatibleExisting = existing.filter { ext.isCompatible(it) } - val suggestions = ext.suggestToolchains(project, direnv) + val suggestions = ext.suggestToolchains(project, data) suggestions.filter { suggestion -> compatibleExisting.none { existing -> ext.matchesSuggestion(existing, suggestion) } } @@ -89,4 +83,15 @@ fun suggestZigToolchains(project: Project? = null, direnv: DirenvState = DirenvS fun ZigToolchain.render(component: SimpleColoredComponent, isSuggestion: Boolean, isSelected: Boolean) { val provider = EXTENSION_POINT_NAME.extensionList.find { it.isCompatible(this) } ?: throw IllegalStateException() return provider.render(this, component, isSuggestion, isSelected) +} + +private val emptyData = object: UserDataHolder { + override fun getUserData(key: Key): T? { + return null + } + + override fun putUserData(key: Key, value: T?) { + + } + } \ No newline at end of file diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/local/LocalZigToolchain.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/local/LocalZigToolchain.kt index 1f529990..f5dc9045 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/local/LocalZigToolchain.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/local/LocalZigToolchain.kt @@ -40,7 +40,7 @@ data class LocalZigToolchain(val location: Path, val std: Path? = null, override } override suspend fun patchCommandLine(commandLine: GeneralCommandLine, project: Project?): GeneralCommandLine { - if (project != null && (commandLine.getUserData(DirenvService.STATE_KEY) ?: DirenvService.getInstance(project).isEnabled).isEnabled(project)) { + if (project != null && DirenvService.getStateFor(commandLine, project).isEnabled(project)) { commandLine.withEnvironment(DirenvService.getInstance(project).import().env) } return commandLine diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/local/LocalZigToolchainProvider.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/local/LocalZigToolchainProvider.kt index d18c0e82..49b223d7 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/local/LocalZigToolchainProvider.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/local/LocalZigToolchainProvider.kt @@ -28,7 +28,6 @@ import com.falsepattern.zigbrains.direnv.Env import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainConfigurable import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainProvider -import com.falsepattern.zigbrains.shared.zigCoroutineScope import com.intellij.openapi.project.Project import com.intellij.openapi.util.UserDataHolder import com.intellij.openapi.util.io.FileUtil @@ -36,19 +35,15 @@ import com.intellij.openapi.util.io.toNioPathOrNull import com.intellij.openapi.util.text.StringUtil import com.intellij.ui.SimpleColoredComponent import com.intellij.ui.SimpleTextAttributes -import com.intellij.util.EnvironmentUtil import com.intellij.util.system.OS import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.async import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.flattenConcat import kotlinx.coroutines.flow.flowOf -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull -import java.io.File import java.nio.file.Files import java.nio.file.Path import java.util.UUID @@ -97,16 +92,14 @@ class LocalZigToolchainProvider: ZigToolchainProvider { } @OptIn(ExperimentalCoroutinesApi::class) - override suspend fun suggestToolchains(project: Project?, direnv: DirenvState): Flow { - val env = if (project != null && direnv.isEnabled(project)) { + override suspend fun suggestToolchains(project: Project?, data: UserDataHolder): Flow { + val env = if (project != null && DirenvService.getStateFor(data, project).isEnabled(project)) { DirenvService.getInstance(project).import() } else { Env.empty } val pathToolchains = env.findAllExecutablesOnPATH("zig").mapNotNull { it.parent } val wellKnown = getWellKnown().asFlow().flatMapConcat { dir -> - if (!dir.isDirectory()) - return@flatMapConcat emptyFlow() runCatching { Files.newDirectoryStream(dir).use { stream -> stream.toList().filterNotNull().asFlow() diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ui/ZigToolchainEditor.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ui/ZigToolchainEditor.kt index 09433f57..c30d8295 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ui/ZigToolchainEditor.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ui/ZigToolchainEditor.kt @@ -43,6 +43,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.util.Key +import com.intellij.openapi.util.UserDataHolder import com.intellij.ui.dsl.builder.AlignX import com.intellij.ui.dsl.builder.Panel import com.intellij.util.concurrency.annotations.RequiresEdt @@ -60,8 +61,7 @@ class ZigToolchainEditor(private var project: Project?, private val sharedState: private val model: TCModel private var editButton: JButton? = null init { - val direnv = sharedState.getUserData(DirenvService.STATE_KEY) ?: project?.let { DirenvService.getInstance(it).isEnabled } ?: DirenvState.Disabled - model = TCModel(getModelList(project, direnv)) + model = TCModel(getModelList(project, sharedState)) toolchainBox = TCComboBox(model) toolchainBox.addItemListener(::itemStateChanged) ZigToolchainListService.getInstance().addChangeListener(this) @@ -97,8 +97,7 @@ class ZigToolchainEditor(private var project: Project?, private val sharedState: override suspend fun toolchainListChanged() { withContext(Dispatchers.EDT + toolchainBox.asContextElement()) { - val direnv = sharedState.getUserData(DirenvService.STATE_KEY) ?: project?.let { DirenvService.getInstance(it).isEnabled } ?: DirenvState.Disabled - val list = getModelList(project, direnv) + val list = getModelList(project, sharedState) model.updateContents(list) val onReload = selectOnNextReload selectOnNextReload = null @@ -129,9 +128,7 @@ class ZigToolchainEditor(private var project: Project?, private val sharedState: } override fun onUserDataChanged(key: Key<*>) { - if (key == DirenvService.STATE_KEY) { - zigCoroutineScope.launch { toolchainListChanged() } - } + zigCoroutineScope.launch { toolchainListChanged() } } @@ -199,13 +196,13 @@ class ZigToolchainEditor(private var project: Project?, private val sharedState: } -private fun getModelList(project: Project?, direnv: DirenvState): List { +private fun getModelList(project: Project?, data: UserDataHolder): List { val modelList = ArrayList() modelList.add(TCListElem.None) modelList.addAll(ZigToolchainListService.getInstance().toolchains.map { it.asActual() }.sortedBy { it.toolchain.name }) modelList.add(Separator("", true)) modelList.addAll(TCListElem.fetchGroup) modelList.add(Separator(ZigBrainsBundle.message("settings.toolchain.model.detected.separator"), true)) - modelList.add(suggestZigToolchains(project, direnv).asPending()) + modelList.add(suggestZigToolchains(project, data).asPending()) return modelList } \ No newline at end of file