direnv decoupling
This commit is contained in:
parent
f7ea73ae45
commit
68b60e2c77
7 changed files with 79 additions and 47 deletions
|
@ -33,6 +33,7 @@ import com.intellij.openapi.components.*
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.project.guessProjectDir
|
import com.intellij.openapi.project.guessProjectDir
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
|
import com.intellij.openapi.util.UserDataHolder
|
||||||
import com.intellij.openapi.vfs.toNioPathOrNull
|
import com.intellij.openapi.vfs.toNioPathOrNull
|
||||||
import com.intellij.platform.util.progress.withProgressText
|
import com.intellij.platform.util.progress.withProgressText
|
||||||
import com.intellij.util.io.awaitExit
|
import com.intellij.util.io.awaitExit
|
||||||
|
@ -140,20 +141,14 @@ class DirenvService(val project: Project): SerializablePersistentStateComponent<
|
||||||
private const val GROUP_DISPLAY_ID = "zigbrains-direnv"
|
private const val GROUP_DISPLAY_ID = "zigbrains-direnv"
|
||||||
fun getInstance(project: Project): IDirenvService = project.service<DirenvService>()
|
fun getInstance(project: Project): IDirenvService = project.service<DirenvService>()
|
||||||
|
|
||||||
val STATE_KEY = Key.create<DirenvState>("DIRENV_STATE")
|
private val STATE_KEY = Key.create<DirenvState>("DIRENV_STATE")
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class DirenvState {
|
fun getStateFor(data: UserDataHolder, project: Project?): DirenvState {
|
||||||
Auto,
|
return data.getUserData(STATE_KEY) ?: project?.let { getInstance(project).isEnabled } ?: DirenvState.Disabled
|
||||||
Enabled,
|
}
|
||||||
Disabled;
|
|
||||||
|
|
||||||
fun isEnabled(project: Project?): Boolean {
|
fun setStateFor(data: UserDataHolder, state: DirenvState) {
|
||||||
return when(this) {
|
data.putUserData(STATE_KEY, state)
|
||||||
Enabled -> true
|
|
||||||
Disabled -> false
|
|
||||||
Auto -> project?.service<DirenvService>()?.hasDotEnv() == true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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<DirenvService>()?.hasDotEnv() == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,10 +39,12 @@ abstract class DirenvEditor<T>(private val sharedState: ZigProjectConfigurationP
|
||||||
row(ZigBrainsBundle.message("settings.direnv.enable.label")) {
|
row(ZigBrainsBundle.message("settings.direnv.enable.label")) {
|
||||||
comboBox(DirenvState.entries).component.let {
|
comboBox(DirenvState.entries).component.let {
|
||||||
cb = it
|
cb = it
|
||||||
it.addItemListener { e ->
|
if (sharedState != null) {
|
||||||
if (e.stateChange != ItemEvent.SELECTED)
|
it.addItemListener { e ->
|
||||||
return@addItemListener
|
if (e.stateChange != ItemEvent.SELECTED)
|
||||||
sharedState
|
return@addItemListener
|
||||||
|
DirenvService.setStateFor(sharedState, DirenvState.Auto)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,7 +74,7 @@ abstract class DirenvEditor<T>(private val sharedState: ZigProjectConfigurationP
|
||||||
|
|
||||||
class ForProject(sharedState: ZigProjectConfigurationProvider.IUserDataBridge) : DirenvEditor<Project>(sharedState) {
|
class ForProject(sharedState: ZigProjectConfigurationProvider.IUserDataBridge) : DirenvEditor<Project>(sharedState) {
|
||||||
override fun isEnabled(context: Project): DirenvState {
|
override fun isEnabled(context: Project): DirenvState {
|
||||||
return context.service<DirenvService>().isEnabledRaw
|
return DirenvService.getInstance(context).isEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setEnabled(context: Project, value: DirenvState) {
|
override fun setEnabled(context: Project, value: DirenvState) {
|
||||||
|
@ -85,7 +87,7 @@ abstract class DirenvEditor<T>(private val sharedState: ZigProjectConfigurationP
|
||||||
if (project?.isDefault != false) {
|
if (project?.isDefault != false) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
sharedState.putUserData(DirenvService.STATE_KEY, DirenvState.Auto)
|
DirenvService.setStateFor(sharedState, DirenvState.Auto)
|
||||||
return ForProject(sharedState)
|
return ForProject(sharedState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,24 +24,18 @@ package com.falsepattern.zigbrains.project.toolchain.base
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.direnv.DirenvState
|
import com.falsepattern.zigbrains.direnv.DirenvState
|
||||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
|
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
|
||||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
|
||||||
import com.intellij.openapi.extensions.ExtensionPointName
|
import com.intellij.openapi.extensions.ExtensionPointName
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.openapi.util.UserDataHolder
|
import com.intellij.openapi.util.UserDataHolder
|
||||||
import com.intellij.ui.SimpleColoredComponent
|
import com.intellij.ui.SimpleColoredComponent
|
||||||
import com.intellij.util.text.SemVer
|
|
||||||
import kotlinx.coroutines.Deferred
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.asFlow
|
import kotlinx.coroutines.flow.asFlow
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.flatMap
|
|
||||||
import kotlinx.coroutines.flow.flatMapConcat
|
import kotlinx.coroutines.flow.flatMapConcat
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.mapNotNull
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigToolchainProvider>("com.falsepattern.zigbrains.toolchainProvider")
|
private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigToolchainProvider>("com.falsepattern.zigbrains.toolchainProvider")
|
||||||
|
@ -53,7 +47,7 @@ internal interface ZigToolchainProvider {
|
||||||
fun serialize(toolchain: ZigToolchain): Map<String, String>
|
fun serialize(toolchain: ZigToolchain): Map<String, String>
|
||||||
fun matchesSuggestion(toolchain: ZigToolchain, suggestion: ZigToolchain): Boolean
|
fun matchesSuggestion(toolchain: ZigToolchain, suggestion: ZigToolchain): Boolean
|
||||||
fun createConfigurable(uuid: UUID, toolchain: ZigToolchain): ZigToolchainConfigurable<*>
|
fun createConfigurable(uuid: UUID, toolchain: ZigToolchain): ZigToolchainConfigurable<*>
|
||||||
suspend fun suggestToolchains(project: Project?, direnv: DirenvState): Flow<ZigToolchain>
|
suspend fun suggestToolchains(project: Project?, data: UserDataHolder): Flow<ZigToolchain>
|
||||||
fun render(toolchain: ZigToolchain, component: SimpleColoredComponent, isSuggestion: Boolean, isSelected: Boolean)
|
fun render(toolchain: ZigToolchain, component: SimpleColoredComponent, isSuggestion: Boolean, isSelected: Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,11 +69,11 @@ fun ZigToolchain.createNamedConfigurable(uuid: UUID): ZigToolchainConfigurable<*
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
fun suggestZigToolchains(project: Project? = null, direnv: DirenvState = DirenvState.Disabled): Flow<ZigToolchain> {
|
fun suggestZigToolchains(project: Project? = null, data: UserDataHolder = emptyData): Flow<ZigToolchain> {
|
||||||
val existing = ZigToolchainListService.getInstance().toolchains.map { (_, tc) -> tc }.toList()
|
val existing = ZigToolchainListService.getInstance().toolchains.map { (_, tc) -> tc }.toList()
|
||||||
return EXTENSION_POINT_NAME.extensionList.asFlow().flatMapConcat { ext ->
|
return EXTENSION_POINT_NAME.extensionList.asFlow().flatMapConcat { ext ->
|
||||||
val compatibleExisting = existing.filter { ext.isCompatible(it) }
|
val compatibleExisting = existing.filter { ext.isCompatible(it) }
|
||||||
val suggestions = ext.suggestToolchains(project, direnv)
|
val suggestions = ext.suggestToolchains(project, data)
|
||||||
suggestions.filter { suggestion ->
|
suggestions.filter { suggestion ->
|
||||||
compatibleExisting.none { existing -> ext.matchesSuggestion(existing, suggestion) }
|
compatibleExisting.none { existing -> ext.matchesSuggestion(existing, suggestion) }
|
||||||
}
|
}
|
||||||
|
@ -90,3 +84,14 @@ fun ZigToolchain.render(component: SimpleColoredComponent, isSuggestion: Boolean
|
||||||
val provider = EXTENSION_POINT_NAME.extensionList.find { it.isCompatible(this) } ?: throw IllegalStateException()
|
val provider = EXTENSION_POINT_NAME.extensionList.find { it.isCompatible(this) } ?: throw IllegalStateException()
|
||||||
return provider.render(this, component, isSuggestion, isSelected)
|
return provider.render(this, component, isSuggestion, isSelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val emptyData = object: UserDataHolder {
|
||||||
|
override fun <T : Any?> getUserData(key: Key<T?>): T? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : Any?> putUserData(key: Key<T?>, value: T?) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -40,7 +40,7 @@ data class LocalZigToolchain(val location: Path, val std: Path? = null, override
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun patchCommandLine(commandLine: GeneralCommandLine, project: Project?): GeneralCommandLine {
|
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)
|
commandLine.withEnvironment(DirenvService.getInstance(project).import().env)
|
||||||
}
|
}
|
||||||
return commandLine
|
return commandLine
|
||||||
|
|
|
@ -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.ZigToolchain
|
||||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainConfigurable
|
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainConfigurable
|
||||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainProvider
|
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainProvider
|
||||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.util.UserDataHolder
|
import com.intellij.openapi.util.UserDataHolder
|
||||||
import com.intellij.openapi.util.io.FileUtil
|
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.openapi.util.text.StringUtil
|
||||||
import com.intellij.ui.SimpleColoredComponent
|
import com.intellij.ui.SimpleColoredComponent
|
||||||
import com.intellij.ui.SimpleTextAttributes
|
import com.intellij.ui.SimpleTextAttributes
|
||||||
import com.intellij.util.EnvironmentUtil
|
|
||||||
import com.intellij.util.system.OS
|
import com.intellij.util.system.OS
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.asFlow
|
import kotlinx.coroutines.flow.asFlow
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import kotlinx.coroutines.flow.flatMapConcat
|
import kotlinx.coroutines.flow.flatMapConcat
|
||||||
import kotlinx.coroutines.flow.flattenConcat
|
import kotlinx.coroutines.flow.flattenConcat
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.mapNotNull
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
import java.io.File
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
@ -97,16 +92,14 @@ class LocalZigToolchainProvider: ZigToolchainProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
override suspend fun suggestToolchains(project: Project?, direnv: DirenvState): Flow<ZigToolchain> {
|
override suspend fun suggestToolchains(project: Project?, data: UserDataHolder): Flow<ZigToolchain> {
|
||||||
val env = if (project != null && direnv.isEnabled(project)) {
|
val env = if (project != null && DirenvService.getStateFor(data, project).isEnabled(project)) {
|
||||||
DirenvService.getInstance(project).import()
|
DirenvService.getInstance(project).import()
|
||||||
} else {
|
} else {
|
||||||
Env.empty
|
Env.empty
|
||||||
}
|
}
|
||||||
val pathToolchains = env.findAllExecutablesOnPATH("zig").mapNotNull { it.parent }
|
val pathToolchains = env.findAllExecutablesOnPATH("zig").mapNotNull { it.parent }
|
||||||
val wellKnown = getWellKnown().asFlow().flatMapConcat { dir ->
|
val wellKnown = getWellKnown().asFlow().flatMapConcat { dir ->
|
||||||
if (!dir.isDirectory())
|
|
||||||
return@flatMapConcat emptyFlow<Path>()
|
|
||||||
runCatching {
|
runCatching {
|
||||||
Files.newDirectoryStream(dir).use { stream ->
|
Files.newDirectoryStream(dir).use { stream ->
|
||||||
stream.toList().filterNotNull().asFlow()
|
stream.toList().filterNotNull().asFlow()
|
||||||
|
|
|
@ -43,6 +43,7 @@ import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.project.ProjectManager
|
import com.intellij.openapi.project.ProjectManager
|
||||||
import com.intellij.openapi.ui.DialogWrapper
|
import com.intellij.openapi.ui.DialogWrapper
|
||||||
import com.intellij.openapi.util.Key
|
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.AlignX
|
||||||
import com.intellij.ui.dsl.builder.Panel
|
import com.intellij.ui.dsl.builder.Panel
|
||||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
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 val model: TCModel
|
||||||
private var editButton: JButton? = null
|
private var editButton: JButton? = null
|
||||||
init {
|
init {
|
||||||
val direnv = sharedState.getUserData(DirenvService.STATE_KEY) ?: project?.let { DirenvService.getInstance(it).isEnabled } ?: DirenvState.Disabled
|
model = TCModel(getModelList(project, sharedState))
|
||||||
model = TCModel(getModelList(project, direnv))
|
|
||||||
toolchainBox = TCComboBox(model)
|
toolchainBox = TCComboBox(model)
|
||||||
toolchainBox.addItemListener(::itemStateChanged)
|
toolchainBox.addItemListener(::itemStateChanged)
|
||||||
ZigToolchainListService.getInstance().addChangeListener(this)
|
ZigToolchainListService.getInstance().addChangeListener(this)
|
||||||
|
@ -97,8 +97,7 @@ class ZigToolchainEditor(private var project: Project?, private val sharedState:
|
||||||
|
|
||||||
override suspend fun toolchainListChanged() {
|
override suspend fun toolchainListChanged() {
|
||||||
withContext(Dispatchers.EDT + toolchainBox.asContextElement()) {
|
withContext(Dispatchers.EDT + toolchainBox.asContextElement()) {
|
||||||
val direnv = sharedState.getUserData(DirenvService.STATE_KEY) ?: project?.let { DirenvService.getInstance(it).isEnabled } ?: DirenvState.Disabled
|
val list = getModelList(project, sharedState)
|
||||||
val list = getModelList(project, direnv)
|
|
||||||
model.updateContents(list)
|
model.updateContents(list)
|
||||||
val onReload = selectOnNextReload
|
val onReload = selectOnNextReload
|
||||||
selectOnNextReload = null
|
selectOnNextReload = null
|
||||||
|
@ -129,9 +128,7 @@ class ZigToolchainEditor(private var project: Project?, private val sharedState:
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUserDataChanged(key: Key<*>) {
|
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<TCListElemIn> {
|
private fun getModelList(project: Project?, data: UserDataHolder): List<TCListElemIn> {
|
||||||
val modelList = ArrayList<TCListElemIn>()
|
val modelList = ArrayList<TCListElemIn>()
|
||||||
modelList.add(TCListElem.None)
|
modelList.add(TCListElem.None)
|
||||||
modelList.addAll(ZigToolchainListService.getInstance().toolchains.map { it.asActual() }.sortedBy { it.toolchain.name })
|
modelList.addAll(ZigToolchainListService.getInstance().toolchains.map { it.asActual() }.sortedBy { it.toolchain.name })
|
||||||
modelList.add(Separator("", true))
|
modelList.add(Separator("", true))
|
||||||
modelList.addAll(TCListElem.fetchGroup)
|
modelList.addAll(TCListElem.fetchGroup)
|
||||||
modelList.add(Separator(ZigBrainsBundle.message("settings.toolchain.model.detected.separator"), true))
|
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
|
return modelList
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue