tristate direnv config
This commit is contained in:
parent
3ceb61f2dd
commit
f7ea73ae45
17 changed files with 326 additions and 118 deletions
|
@ -22,8 +22,6 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains
|
package com.falsepattern.zigbrains
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
|
|
||||||
import com.falsepattern.zigbrains.project.toolchain.base.suggestZigToolchain
|
|
||||||
import com.intellij.ide.BrowserUtil
|
import com.intellij.ide.BrowserUtil
|
||||||
import com.intellij.ide.plugins.PluginManager
|
import com.intellij.ide.plugins.PluginManager
|
||||||
import com.intellij.notification.Notification
|
import com.intellij.notification.Notification
|
||||||
|
@ -35,10 +33,8 @@ import com.intellij.openapi.options.Configurable
|
||||||
import com.intellij.openapi.options.ShowSettingsUtil
|
import com.intellij.openapi.options.ShowSettingsUtil
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.startup.ProjectActivity
|
import com.intellij.openapi.startup.ProjectActivity
|
||||||
import com.intellij.openapi.util.UserDataHolderBase
|
|
||||||
import java.lang.reflect.Constructor
|
import java.lang.reflect.Constructor
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import kotlin.io.path.pathString
|
|
||||||
|
|
||||||
class ZBStartup: ProjectActivity {
|
class ZBStartup: ProjectActivity {
|
||||||
var firstInit = true
|
var firstInit = true
|
||||||
|
|
|
@ -29,24 +29,47 @@ import com.intellij.ide.impl.isTrusted
|
||||||
import com.intellij.notification.Notification
|
import com.intellij.notification.Notification
|
||||||
import com.intellij.notification.NotificationType
|
import com.intellij.notification.NotificationType
|
||||||
import com.intellij.notification.Notifications
|
import com.intellij.notification.Notifications
|
||||||
import com.intellij.openapi.components.Service
|
import com.intellij.openapi.components.*
|
||||||
import com.intellij.openapi.components.service
|
|
||||||
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.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
|
||||||
|
import com.intellij.util.xmlb.annotations.Attribute
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.isRegularFile
|
||||||
|
|
||||||
@Service(Service.Level.PROJECT)
|
@Service(Service.Level.PROJECT)
|
||||||
class DirenvService(val project: Project) {
|
@State(
|
||||||
val mutex = Mutex()
|
name = "Direnv",
|
||||||
|
storages = [Storage("zigbrains.xml")]
|
||||||
|
)
|
||||||
|
class DirenvService(val project: Project): SerializablePersistentStateComponent<DirenvService.State>(State()), IDirenvService {
|
||||||
|
private val mutex = Mutex()
|
||||||
|
|
||||||
suspend fun import(): Env {
|
override val isInstalled: Boolean by lazy {
|
||||||
|
// Using the builtin stuff here instead of Env because it should only scan for direnv on the process path
|
||||||
|
PathEnvironmentVariableUtil.findExecutableInPathOnAnyOS("direnv") != null
|
||||||
|
}
|
||||||
|
|
||||||
|
var isEnabledRaw: DirenvState
|
||||||
|
get() = state.enabled
|
||||||
|
set(value) {
|
||||||
|
updateState {
|
||||||
|
it.copy(enabled = value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val isEnabled: DirenvState
|
||||||
|
get() = isEnabledRaw
|
||||||
|
|
||||||
|
override suspend fun import(): Env {
|
||||||
if (!isInstalled || !project.isTrusted() || project.isDefault)
|
if (!isInstalled || !project.isTrusted() || project.isDefault)
|
||||||
return Env.empty
|
return Env.empty
|
||||||
val workDir = project.guessProjectDir()?.toNioPath() ?: return Env.empty
|
val workDir = project.guessProjectDir()?.toNioPath() ?: return Env.empty
|
||||||
|
@ -100,13 +123,45 @@ class DirenvService(val project: Project) {
|
||||||
return DirenvOutput(stdOut, false)
|
return DirenvOutput(stdOut, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isInstalled: Boolean by lazy {
|
fun hasDotEnv(): Boolean {
|
||||||
// Using the builtin stuff here instead of Env because it should only scan for direnv on the process path
|
if (!isInstalled)
|
||||||
PathEnvironmentVariableUtil.findExecutableInPathOnAnyOS("direnv") != null
|
return false
|
||||||
|
val projectDir = project.guessProjectDir()?.toNioPathOrNull() ?: return false
|
||||||
|
return envFiles.any { projectDir.resolve(it).isRegularFile() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class State(
|
||||||
|
@JvmField
|
||||||
|
@Attribute
|
||||||
|
var enabled: DirenvState = DirenvState.Auto
|
||||||
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val GROUP_DISPLAY_ID = "zigbrains-direnv"
|
private const val GROUP_DISPLAY_ID = "zigbrains-direnv"
|
||||||
fun getInstance(project: Project): DirenvService = project.service()
|
fun getInstance(project: Project): IDirenvService = project.service<DirenvService>()
|
||||||
|
|
||||||
|
val STATE_KEY = Key.create<DirenvState>("DIRENV_STATE")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class DirenvState {
|
||||||
|
Auto,
|
||||||
|
Enabled,
|
||||||
|
Disabled;
|
||||||
|
|
||||||
|
fun isEnabled(project: Project?): Boolean {
|
||||||
|
return when(this) {
|
||||||
|
Enabled -> true
|
||||||
|
Disabled -> false
|
||||||
|
Auto -> project?.service<DirenvService>()?.hasDotEnv() == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed interface IDirenvService {
|
||||||
|
val isInstalled: Boolean
|
||||||
|
val isEnabled: DirenvState
|
||||||
|
suspend fun import(): Env
|
||||||
|
}
|
||||||
|
|
||||||
|
private val envFiles = listOf(".envrc", ".env")
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* 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.ui
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
|
import com.falsepattern.zigbrains.direnv.DirenvService
|
||||||
|
import com.falsepattern.zigbrains.direnv.DirenvState
|
||||||
|
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
||||||
|
import com.falsepattern.zigbrains.shared.SubConfigurable
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.ui.ComboBox
|
||||||
|
import com.intellij.ui.dsl.builder.Panel
|
||||||
|
import java.awt.event.ItemEvent
|
||||||
|
|
||||||
|
abstract class DirenvEditor<T>(private val sharedState: ZigProjectConfigurationProvider.IUserDataBridge?): SubConfigurable<T> {
|
||||||
|
private var cb: ComboBox<DirenvState>? = null
|
||||||
|
override fun attach(panel: Panel): Unit = with(panel) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isModified(context: T): Boolean {
|
||||||
|
return isEnabled(context) != cb?.selectedItem as DirenvState
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apply(context: T) {
|
||||||
|
setEnabled(context, cb?.selectedItem as DirenvState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reset(context: T?) {
|
||||||
|
if (context == null) {
|
||||||
|
cb?.selectedItem = DirenvState.Auto
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cb?.selectedItem = isEnabled(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun isEnabled(context: T): DirenvState
|
||||||
|
abstract fun setEnabled(context: T, value: DirenvState)
|
||||||
|
|
||||||
|
class ForProject(sharedState: ZigProjectConfigurationProvider.IUserDataBridge) : DirenvEditor<Project>(sharedState) {
|
||||||
|
override fun isEnabled(context: Project): DirenvState {
|
||||||
|
return context.service<DirenvService>().isEnabledRaw
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setEnabled(context: Project, value: DirenvState) {
|
||||||
|
context.service<DirenvService>().isEnabledRaw = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Provider: ZigProjectConfigurationProvider {
|
||||||
|
override fun create(project: Project?, sharedState: ZigProjectConfigurationProvider.IUserDataBridge): SubConfigurable<Project>? {
|
||||||
|
if (project?.isDefault != false) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
sharedState.putUserData(DirenvService.STATE_KEY, DirenvState.Auto)
|
||||||
|
return ForProject(sharedState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val index: Int
|
||||||
|
get() = 1
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.project.execution.base
|
package com.falsepattern.zigbrains.project.execution.base
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.direnv.DirenvService
|
||||||
import com.intellij.execution.ExecutionException
|
import com.intellij.execution.ExecutionException
|
||||||
import com.intellij.execution.Executor
|
import com.intellij.execution.Executor
|
||||||
import com.intellij.execution.configurations.ConfigurationFactory
|
import com.intellij.execution.configurations.ConfigurationFactory
|
||||||
|
@ -34,6 +35,7 @@ import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.project.guessProjectDir
|
import com.intellij.openapi.project.guessProjectDir
|
||||||
import com.intellij.openapi.util.NlsActions.ActionText
|
import com.intellij.openapi.util.NlsActions.ActionText
|
||||||
import com.intellij.openapi.vfs.toNioPathOrNull
|
import com.intellij.openapi.vfs.toNioPathOrNull
|
||||||
|
import com.intellij.platform.util.progress.reportRawProgress
|
||||||
import org.jdom.Element
|
import org.jdom.Element
|
||||||
import org.jetbrains.annotations.Nls
|
import org.jetbrains.annotations.Nls
|
||||||
|
|
||||||
|
@ -62,10 +64,10 @@ abstract class ZigExecConfig<T: ZigExecConfig<T>>(project: Project, factory: Con
|
||||||
|
|
||||||
|
|
||||||
suspend fun patchCommandLine(commandLine: GeneralCommandLine): GeneralCommandLine {
|
suspend fun patchCommandLine(commandLine: GeneralCommandLine): GeneralCommandLine {
|
||||||
// TODO direnv
|
val direnv = DirenvService.getInstance(project)
|
||||||
// if (project.zigProjectSettings.state.direnv) {
|
if (direnv.isEnabled.isEnabled(project)) {
|
||||||
// commandLine.withEnvironment(DirenvCmd.importDirenv(project).env)
|
commandLine.withEnvironment(direnv.import().env)
|
||||||
// }
|
}
|
||||||
return commandLine
|
return commandLine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,8 @@ import com.intellij.execution.configurations.PtyCommandLine
|
||||||
import com.intellij.execution.process.ProcessHandler
|
import com.intellij.execution.process.ProcessHandler
|
||||||
import com.intellij.execution.runners.ExecutionEnvironment
|
import com.intellij.execution.runners.ExecutionEnvironment
|
||||||
import com.intellij.platform.ide.progress.ModalTaskOwner
|
import com.intellij.platform.ide.progress.ModalTaskOwner
|
||||||
|
import com.intellij.platform.util.progress.reportProgress
|
||||||
|
import com.intellij.platform.util.progress.reportRawProgress
|
||||||
import kotlin.io.path.pathString
|
import kotlin.io.path.pathString
|
||||||
|
|
||||||
abstract class ZigProfileState<T: ZigExecConfig<T>> (
|
abstract class ZigProfileState<T: ZigExecConfig<T>> (
|
||||||
|
|
|
@ -32,10 +32,13 @@ import com.intellij.execution.runners.ExecutionEnvironment
|
||||||
import com.intellij.execution.runners.RunContentBuilder
|
import com.intellij.execution.runners.RunContentBuilder
|
||||||
import com.intellij.execution.ui.RunContentDescriptor
|
import com.intellij.execution.ui.RunContentDescriptor
|
||||||
import com.intellij.openapi.application.ModalityState
|
import com.intellij.openapi.application.ModalityState
|
||||||
|
import com.intellij.openapi.progress.blockingContext
|
||||||
|
|
||||||
class ZigRegularRunner: ZigProgramRunner<ZigProfileState<*>>(DefaultRunExecutor.EXECUTOR_ID) {
|
class ZigRegularRunner: ZigProgramRunner<ZigProfileState<*>>(DefaultRunExecutor.EXECUTOR_ID) {
|
||||||
override suspend fun execute(state: ZigProfileState<*>, toolchain: ZigToolchain, environment: ExecutionEnvironment): RunContentDescriptor? {
|
override suspend fun execute(state: ZigProfileState<*>, toolchain: ZigToolchain, environment: ExecutionEnvironment): RunContentDescriptor? {
|
||||||
val exec = state.execute(environment.executor, this)
|
val exec = blockingContext {
|
||||||
|
state.execute(environment.executor, this)
|
||||||
|
}
|
||||||
return withEDTContext(ModalityState.any()) {
|
return withEDTContext(ModalityState.any()) {
|
||||||
val runContentBuilder = RunContentBuilder(exec, environment)
|
val runContentBuilder = RunContentBuilder(exec, environment)
|
||||||
runContentBuilder.showRunContent(null)
|
runContentBuilder.showRunContent(null)
|
||||||
|
|
|
@ -26,6 +26,7 @@ import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
import com.falsepattern.zigbrains.shared.SubConfigurable
|
import com.falsepattern.zigbrains.shared.SubConfigurable
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.util.NlsContexts
|
import com.intellij.openapi.util.NlsContexts
|
||||||
|
import com.intellij.openapi.util.UserDataHolderBase
|
||||||
|
|
||||||
class ZigConfigurable(override val context: Project) : SubConfigurable.Adapter<Project>() {
|
class ZigConfigurable(override val context: Project) : SubConfigurable.Adapter<Project>() {
|
||||||
override fun instantiate(): List<SubConfigurable<Project>> {
|
override fun instantiate(): List<SubConfigurable<Project>> {
|
||||||
|
|
|
@ -25,14 +25,51 @@ package com.falsepattern.zigbrains.project.settings
|
||||||
import com.falsepattern.zigbrains.shared.SubConfigurable
|
import com.falsepattern.zigbrains.shared.SubConfigurable
|
||||||
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.UserDataHolderBase
|
||||||
|
|
||||||
interface ZigProjectConfigurationProvider {
|
interface ZigProjectConfigurationProvider {
|
||||||
fun create(project: Project?): SubConfigurable<Project>?
|
fun create(project: Project?, sharedState: IUserDataBridge): SubConfigurable<Project>?
|
||||||
val index: Int
|
val index: Int
|
||||||
companion object {
|
companion object {
|
||||||
private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigProjectConfigurationProvider>("com.falsepattern.zigbrains.projectConfigProvider")
|
private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigProjectConfigurationProvider>("com.falsepattern.zigbrains.projectConfigProvider")
|
||||||
fun createPanels(project: Project?): List<SubConfigurable<Project>> {
|
fun createPanels(project: Project?): List<SubConfigurable<Project>> {
|
||||||
return EXTENSION_POINT_NAME.extensionList.sortedBy { it.index }.mapNotNull { it.create(project) }
|
val sharedState = UserDataBridge()
|
||||||
|
return EXTENSION_POINT_NAME.extensionList.sortedBy { it.index }.mapNotNull { it.create(project, sharedState) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
interface IUserDataBridge: UserDataHolder {
|
||||||
|
fun addUserDataChangeListener(listener: UserDataListener)
|
||||||
|
fun removeUserDataChangeListener(listener: UserDataListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserDataListener {
|
||||||
|
fun onUserDataChanged(key: Key<*>)
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserDataBridge: UserDataHolderBase(), IUserDataBridge {
|
||||||
|
private val listeners = ArrayList<UserDataListener>()
|
||||||
|
override fun <T : Any?> putUserData(key: Key<T?>, value: T?) {
|
||||||
|
super.putUserData(key, value)
|
||||||
|
synchronized(listeners) {
|
||||||
|
listeners.forEach { listener ->
|
||||||
|
listener.onUserDataChanged(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addUserDataChangeListener(listener: UserDataListener) {
|
||||||
|
synchronized(listeners) {
|
||||||
|
listeners.add(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeUserDataChangeListener(listener: UserDataListener) {
|
||||||
|
synchronized(listeners) {
|
||||||
|
listeners.remove(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.project.toolchain.base
|
package com.falsepattern.zigbrains.project.toolchain.base
|
||||||
|
|
||||||
|
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.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
import com.intellij.openapi.extensions.ExtensionPointName
|
import com.intellij.openapi.extensions.ExtensionPointName
|
||||||
|
@ -30,21 +31,29 @@ import com.intellij.openapi.util.UserDataHolder
|
||||||
import com.intellij.ui.SimpleColoredComponent
|
import com.intellij.ui.SimpleColoredComponent
|
||||||
import com.intellij.util.text.SemVer
|
import com.intellij.util.text.SemVer
|
||||||
import kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.async
|
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
|
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")
|
||||||
|
|
||||||
internal interface ZigToolchainProvider {
|
internal interface ZigToolchainProvider {
|
||||||
suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): ZigToolchain?
|
|
||||||
|
|
||||||
val serialMarker: String
|
val serialMarker: String
|
||||||
fun isCompatible(toolchain: ZigToolchain): Boolean
|
fun isCompatible(toolchain: ZigToolchain): Boolean
|
||||||
fun deserialize(data: Map<String, String>): ZigToolchain?
|
fun deserialize(data: Map<String, String>): ZigToolchain?
|
||||||
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<*>
|
||||||
fun suggestToolchains(): List<Deferred<ZigToolchain>>
|
suspend fun suggestToolchains(project: Project?, direnv: DirenvState): Flow<ZigToolchain>
|
||||||
fun render(toolchain: ZigToolchain, component: SimpleColoredComponent, isSuggestion: Boolean, isSelected: Boolean)
|
fun render(toolchain: ZigToolchain, component: SimpleColoredComponent, isSuggestion: Boolean, isSelected: Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,31 +69,21 @@ fun ZigToolchain.toRef(): ZigToolchain.Ref {
|
||||||
return ZigToolchain.Ref(provider.serialMarker, provider.serialize(this))
|
return ZigToolchain.Ref(provider.serialMarker, provider.serialize(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun Project?.suggestZigToolchain(extraData: UserDataHolder): ZigToolchain? {
|
|
||||||
return EXTENSION_POINT_NAME.extensionList.firstNotNullOfOrNull { it.suggestToolchain(this, extraData) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ZigToolchain.createNamedConfigurable(uuid: UUID): ZigToolchainConfigurable<*> {
|
fun ZigToolchain.createNamedConfigurable(uuid: UUID): ZigToolchainConfigurable<*> {
|
||||||
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.createConfigurable(uuid, this)
|
return provider.createConfigurable(uuid, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun suggestZigToolchains(): List<Deferred<ZigToolchain>> {
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
fun suggestZigToolchains(project: Project? = null, direnv: DirenvState = DirenvState.Disabled): 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.flatMap { 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()
|
val suggestions = ext.suggestToolchains(project, direnv)
|
||||||
suggestions.map { suggestion ->
|
suggestions.filter { suggestion ->
|
||||||
zigCoroutineScope.async {
|
compatibleExisting.none { existing -> ext.matchesSuggestion(existing, suggestion) }
|
||||||
val sugg = suggestion.await()
|
|
||||||
if (compatibleExisting.none { existing -> ext.matchesSuggestion(existing, sugg) }) {
|
|
||||||
sugg
|
|
||||||
} else {
|
|
||||||
throw IllegalArgumentException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}.flowOn(Dispatchers.IO)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ZigToolchain.render(component: SimpleColoredComponent, isSuggestion: Boolean, isSelected: Boolean) {
|
fun ZigToolchain.render(component: SimpleColoredComponent, isSuggestion: Boolean, isSelected: Boolean) {
|
||||||
|
|
|
@ -22,18 +22,16 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.project.toolchain.local
|
package com.falsepattern.zigbrains.project.toolchain.local
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.direnv.DirenvService
|
||||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||||
import com.intellij.execution.ExecutionException
|
import com.intellij.execution.ExecutionException
|
||||||
import com.intellij.execution.configurations.GeneralCommandLine
|
import com.intellij.execution.configurations.GeneralCommandLine
|
||||||
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.KeyWithDefaultValue
|
|
||||||
import com.intellij.openapi.util.SystemInfo
|
import com.intellij.openapi.util.SystemInfo
|
||||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||||
import com.intellij.openapi.vfs.toNioPathOrNull
|
import com.intellij.openapi.vfs.toNioPathOrNull
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
@JvmRecord
|
@JvmRecord
|
||||||
data class LocalZigToolchain(val location: Path, val std: Path? = null, override val name: String? = null): ZigToolchain {
|
data class LocalZigToolchain(val location: Path, val std: Path? = null, override val name: String? = null): ZigToolchain {
|
||||||
|
@ -42,10 +40,9 @@ 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 {
|
||||||
//TODO direnv
|
if (project != null && (commandLine.getUserData(DirenvService.STATE_KEY) ?: DirenvService.getInstance(project).isEnabled).isEnabled(project)) {
|
||||||
// if (project != null && (commandLine.getUserData(DIRENV_KEY) ?: project.zigProjectSettings.state.direnv)) {
|
commandLine.withEnvironment(DirenvService.getInstance(project).import().env)
|
||||||
// commandLine.withEnvironment(DirenvCmd.importDirenv(project).env)
|
}
|
||||||
// }
|
|
||||||
return commandLine
|
return commandLine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,8 +56,6 @@ data class LocalZigToolchain(val location: Path, val std: Path? = null, override
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val DIRENV_KEY = KeyWithDefaultValue.create<Boolean>("ZIG_LOCAL_DIRENV")
|
|
||||||
|
|
||||||
@Throws(ExecutionException::class)
|
@Throws(ExecutionException::class)
|
||||||
fun ensureLocal(toolchain: ZigToolchain): LocalZigToolchain {
|
fun ensureLocal(toolchain: ZigToolchain): LocalZigToolchain {
|
||||||
if (toolchain is LocalZigToolchain) {
|
if (toolchain is LocalZigToolchain) {
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.project.toolchain.local
|
package com.falsepattern.zigbrains.project.toolchain.local
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.direnv.DirenvService
|
||||||
|
import com.falsepattern.zigbrains.direnv.DirenvState
|
||||||
import com.falsepattern.zigbrains.direnv.Env
|
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
|
||||||
|
@ -36,8 +38,16 @@ import com.intellij.ui.SimpleColoredComponent
|
||||||
import com.intellij.ui.SimpleTextAttributes
|
import com.intellij.ui.SimpleTextAttributes
|
||||||
import com.intellij.util.EnvironmentUtil
|
import com.intellij.util.EnvironmentUtil
|
||||||
import com.intellij.util.system.OS
|
import com.intellij.util.system.OS
|
||||||
import kotlinx.coroutines.Deferred
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.async
|
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.io.File
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
@ -46,18 +56,6 @@ import kotlin.io.path.isDirectory
|
||||||
import kotlin.io.path.pathString
|
import kotlin.io.path.pathString
|
||||||
|
|
||||||
class LocalZigToolchainProvider: ZigToolchainProvider {
|
class LocalZigToolchainProvider: ZigToolchainProvider {
|
||||||
override suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): LocalZigToolchain? {
|
|
||||||
//TODO direnv
|
|
||||||
// val env = if (project != null && (extraData.getUserData(LocalZigToolchain.DIRENV_KEY) ?: project.zigProjectSettings.state.direnv)) {
|
|
||||||
// DirenvCmd.importDirenv(project)
|
|
||||||
// } else {
|
|
||||||
// emptyEnv
|
|
||||||
// }
|
|
||||||
val env = Env.empty
|
|
||||||
val zigExePath = env.findExecutableOnPATH("zig") ?: return null
|
|
||||||
return LocalZigToolchain(zigExePath.parent)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val serialMarker: String
|
override val serialMarker: String
|
||||||
get() = "local"
|
get() = "local"
|
||||||
|
|
||||||
|
@ -98,22 +96,25 @@ class LocalZigToolchainProvider: ZigToolchainProvider {
|
||||||
return LocalZigToolchainConfigurable(uuid, toolchain)
|
return LocalZigToolchainConfigurable(uuid, toolchain)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun suggestToolchains(): List<Deferred<ZigToolchain>> {
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
val res = HashSet<String>()
|
override suspend fun suggestToolchains(project: Project?, direnv: DirenvState): Flow<ZigToolchain> {
|
||||||
EnvironmentUtil.getValue("PATH")?.split(File.pathSeparatorChar)?.let { res.addAll(it.toList()) }
|
val env = if (project != null && direnv.isEnabled(project)) {
|
||||||
val wellKnown = getWellKnown()
|
DirenvService.getInstance(project).import()
|
||||||
wellKnown.forEach { dir ->
|
} else {
|
||||||
|
Env.empty
|
||||||
|
}
|
||||||
|
val pathToolchains = env.findAllExecutablesOnPATH("zig").mapNotNull { it.parent }
|
||||||
|
val wellKnown = getWellKnown().asFlow().flatMapConcat { dir ->
|
||||||
if (!dir.isDirectory())
|
if (!dir.isDirectory())
|
||||||
return@forEach
|
return@flatMapConcat emptyFlow<Path>()
|
||||||
runCatching {
|
runCatching {
|
||||||
Files.newDirectoryStream(dir).use { stream ->
|
Files.newDirectoryStream(dir).use { stream ->
|
||||||
stream.forEach { subDir ->
|
stream.toList().filterNotNull().asFlow()
|
||||||
res.add(subDir.pathString)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}.getOrElse { emptyFlow() }
|
||||||
}
|
}
|
||||||
return res.map { zigCoroutineScope.async { LocalZigToolchain.tryFromPathString(it) ?: throw IllegalArgumentException() } }
|
val joined = flowOf(pathToolchains, wellKnown).flattenConcat()
|
||||||
|
return joined.mapNotNull { LocalZigToolchain.tryFromPath(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun render(toolchain: ZigToolchain, component: SimpleColoredComponent, isSuggestion: Boolean, isSelected: Boolean) {
|
override fun render(toolchain: ZigToolchain, component: SimpleColoredComponent, isSuggestion: Boolean, isSelected: Boolean) {
|
||||||
|
@ -121,21 +122,20 @@ class LocalZigToolchainProvider: ZigToolchainProvider {
|
||||||
val name = toolchain.name
|
val name = toolchain.name
|
||||||
val path = presentDetectedPath(toolchain.location.pathString)
|
val path = presentDetectedPath(toolchain.location.pathString)
|
||||||
val primary: String
|
val primary: String
|
||||||
val secondary: String?
|
var secondary: String?
|
||||||
val tooltip: String?
|
val tooltip: String?
|
||||||
if (isSuggestion) {
|
if (isSuggestion) {
|
||||||
primary = path
|
primary = path
|
||||||
secondary = name
|
secondary = name
|
||||||
tooltip = null
|
|
||||||
} else {
|
} else {
|
||||||
primary = name ?: "Zig"
|
primary = name ?: "Zig"
|
||||||
if (isSelected) {
|
secondary = path
|
||||||
secondary = null
|
}
|
||||||
tooltip = path
|
if (isSelected) {
|
||||||
} else {
|
tooltip = secondary
|
||||||
secondary = path
|
secondary = null
|
||||||
tooltip = null
|
} else {
|
||||||
}
|
tooltip = null
|
||||||
}
|
}
|
||||||
component.append(primary)
|
component.append(primary)
|
||||||
if (secondary != null) {
|
if (secondary != null) {
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
package com.falsepattern.zigbrains.project.toolchain.ui
|
package com.falsepattern.zigbrains.project.toolchain.ui
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
|
import com.falsepattern.zigbrains.direnv.DirenvService
|
||||||
|
import com.falsepattern.zigbrains.direnv.DirenvState
|
||||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
||||||
import com.falsepattern.zigbrains.project.toolchain.ToolchainListChangeListener
|
import com.falsepattern.zigbrains.project.toolchain.ToolchainListChangeListener
|
||||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
|
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
|
||||||
|
@ -35,10 +37,12 @@ import com.falsepattern.zigbrains.shared.coroutine.launchWithEDT
|
||||||
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
import com.intellij.openapi.application.EDT
|
import com.intellij.openapi.application.EDT
|
||||||
|
import com.intellij.openapi.observable.util.whenListChanged
|
||||||
import com.intellij.openapi.options.ShowSettingsUtil
|
import com.intellij.openapi.options.ShowSettingsUtil
|
||||||
import com.intellij.openapi.project.Project
|
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.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
|
||||||
|
@ -50,16 +54,24 @@ import java.util.UUID
|
||||||
import javax.swing.JButton
|
import javax.swing.JButton
|
||||||
import kotlin.collections.addAll
|
import kotlin.collections.addAll
|
||||||
|
|
||||||
class ZigToolchainEditor(private val isForDefaultProject: Boolean = false): SubConfigurable<Project>, ToolchainListChangeListener {
|
class ZigToolchainEditor(private var project: Project?, private val sharedState: ZigProjectConfigurationProvider.IUserDataBridge): SubConfigurable<Project>, ToolchainListChangeListener, ZigProjectConfigurationProvider.UserDataListener {
|
||||||
private val toolchainBox: TCComboBox
|
private val toolchainBox: TCComboBox
|
||||||
private var selectOnNextReload: UUID? = null
|
private var selectOnNextReload: UUID? = null
|
||||||
private val model: TCModel
|
private val model: TCModel
|
||||||
private var editButton: JButton? = null
|
private var editButton: JButton? = null
|
||||||
init {
|
init {
|
||||||
model = TCModel(getModelList())
|
val direnv = sharedState.getUserData(DirenvService.STATE_KEY) ?: project?.let { DirenvService.getInstance(it).isEnabled } ?: DirenvState.Disabled
|
||||||
|
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)
|
||||||
|
sharedState.addUserDataChangeListener(this)
|
||||||
|
model.whenListChanged {
|
||||||
|
if (toolchainBox.isPopupVisible) {
|
||||||
|
toolchainBox.isPopupVisible = false
|
||||||
|
toolchainBox.isPopupVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refreshButtonState(item: Any?) {
|
private fun refreshButtonState(item: Any?) {
|
||||||
|
@ -85,7 +97,8 @@ class ZigToolchainEditor(private val isForDefaultProject: Boolean = false): SubC
|
||||||
|
|
||||||
override suspend fun toolchainListChanged() {
|
override suspend fun toolchainListChanged() {
|
||||||
withContext(Dispatchers.EDT + toolchainBox.asContextElement()) {
|
withContext(Dispatchers.EDT + toolchainBox.asContextElement()) {
|
||||||
val list = getModelList()
|
val direnv = sharedState.getUserData(DirenvService.STATE_KEY) ?: project?.let { DirenvService.getInstance(it).isEnabled } ?: DirenvState.Disabled
|
||||||
|
val list = getModelList(project, direnv)
|
||||||
model.updateContents(list)
|
model.updateContents(list)
|
||||||
val onReload = selectOnNextReload
|
val onReload = selectOnNextReload
|
||||||
selectOnNextReload = null
|
selectOnNextReload = null
|
||||||
|
@ -115,9 +128,16 @@ class ZigToolchainEditor(private val isForDefaultProject: Boolean = false): SubC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onUserDataChanged(key: Key<*>) {
|
||||||
|
if (key == DirenvService.STATE_KEY) {
|
||||||
|
zigCoroutineScope.launch { toolchainListChanged() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun attach(p: Panel): Unit = with(p) {
|
override fun attach(p: Panel): Unit = with(p) {
|
||||||
row(ZigBrainsBundle.message(
|
row(ZigBrainsBundle.message(
|
||||||
if (isForDefaultProject)
|
if (project?.isDefault == true)
|
||||||
"settings.toolchain.editor.toolchain-default.label"
|
"settings.toolchain.editor.toolchain-default.label"
|
||||||
else
|
else
|
||||||
"settings.toolchain.editor.toolchain.label")
|
"settings.toolchain.editor.toolchain.label")
|
||||||
|
@ -169,25 +189,23 @@ class ZigToolchainEditor(private val isForDefaultProject: Boolean = false): SubC
|
||||||
}
|
}
|
||||||
|
|
||||||
override val newProjectBeforeInitSelector get() = true
|
override val newProjectBeforeInitSelector get() = true
|
||||||
|
|
||||||
class Provider: ZigProjectConfigurationProvider {
|
class Provider: ZigProjectConfigurationProvider {
|
||||||
override fun create(project: Project?): SubConfigurable<Project>? {
|
override fun create(project: Project?, sharedState: ZigProjectConfigurationProvider.IUserDataBridge): SubConfigurable<Project>? {
|
||||||
return ZigToolchainEditor(project?.isDefault ?: false).also { it.reset(project) }
|
return ZigToolchainEditor(project, sharedState).also { it.reset(project) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override val index: Int get() = 0
|
override val index: Int get() = 0
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun getModelList(): List<TCListElemIn> {
|
private fun getModelList(project: Project?, direnv: DirenvState): 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.addAll(suggestZigToolchains().map { it.asPending() })
|
modelList.add(suggestZigToolchains(project, direnv).asPending())
|
||||||
return modelList
|
return modelList
|
||||||
}
|
}
|
|
@ -66,7 +66,7 @@ class ZigToolchainListEditor : MasterDetailsComponent(), ToolchainListChangeList
|
||||||
val modelList = ArrayList<TCListElemIn>()
|
val modelList = ArrayList<TCListElemIn>()
|
||||||
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.addAll(suggestZigToolchains().map { it.asPending() })
|
modelList.add(suggestZigToolchains().asPending())
|
||||||
val model = TCModel(modelList)
|
val model = TCModel(modelList)
|
||||||
val context = TCContext(null, model)
|
val context = TCContext(null, model)
|
||||||
val popup = TCComboBoxPopup(context, null, ::onItemSelected)
|
val popup = TCComboBoxPopup(context, null, ::onItemSelected)
|
||||||
|
|
|
@ -24,9 +24,9 @@ package com.falsepattern.zigbrains.project.toolchain.ui
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
import kotlinx.coroutines.Deferred
|
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ internal sealed interface TCListElem : TCListElemIn {
|
||||||
object None: TCListElem
|
object None: TCListElem
|
||||||
object Download : TCListElem, Pseudo
|
object Download : TCListElem, Pseudo
|
||||||
object FromDisk : TCListElem, Pseudo
|
object FromDisk : TCListElem, Pseudo
|
||||||
data class Pending(val elem: Deferred<TCListElem>): TCListElem
|
data class Pending(val elems: Flow<TCListElem>): TCListElem
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val fetchGroup get() = listOf(Download, FromDisk)
|
val fetchGroup get() = listOf(Download, FromDisk)
|
||||||
|
@ -59,4 +59,4 @@ internal fun Pair<UUID, ZigToolchain>.asActual() = TCListElem.Toolchain.Actual(f
|
||||||
|
|
||||||
internal fun ZigToolchain.asSuggested() = TCListElem.Toolchain.Suggested(this)
|
internal fun ZigToolchain.asSuggested() = TCListElem.Toolchain.Suggested(this)
|
||||||
|
|
||||||
internal fun Deferred<ZigToolchain>.asPending() = TCListElem.Pending(zigCoroutineScope.async { this@asPending.await().asSuggested() })
|
internal fun Flow<ZigToolchain>.asPending() = TCListElem.Pending(map { it.asSuggested() })
|
|
@ -22,7 +22,6 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.project.toolchain.ui
|
package com.falsepattern.zigbrains.project.toolchain.ui
|
||||||
|
|
||||||
import ai.grazie.utils.attributes.value
|
|
||||||
import com.falsepattern.zigbrains.Icons
|
import com.falsepattern.zigbrains.Icons
|
||||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
import com.falsepattern.zigbrains.project.toolchain.base.render
|
import com.falsepattern.zigbrains.project.toolchain.base.render
|
||||||
|
@ -31,16 +30,12 @@ import com.intellij.icons.AllIcons
|
||||||
import com.intellij.openapi.application.EDT
|
import com.intellij.openapi.application.EDT
|
||||||
import com.intellij.openapi.application.ModalityState
|
import com.intellij.openapi.application.ModalityState
|
||||||
import com.intellij.openapi.application.asContextElement
|
import com.intellij.openapi.application.asContextElement
|
||||||
import com.intellij.openapi.application.impl.ModalityStateEx
|
|
||||||
import com.intellij.openapi.application.runInEdt
|
import com.intellij.openapi.application.runInEdt
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.ui.ComboBox
|
import com.intellij.openapi.ui.ComboBox
|
||||||
import com.intellij.ui.AnimatedIcon
|
|
||||||
import com.intellij.ui.CellRendererPanel
|
import com.intellij.ui.CellRendererPanel
|
||||||
import com.intellij.ui.ClientProperty
|
|
||||||
import com.intellij.ui.CollectionComboBoxModel
|
import com.intellij.ui.CollectionComboBoxModel
|
||||||
import com.intellij.ui.ColoredListCellRenderer
|
import com.intellij.ui.ColoredListCellRenderer
|
||||||
import com.intellij.ui.ComponentUtil
|
|
||||||
import com.intellij.ui.GroupHeaderSeparator
|
import com.intellij.ui.GroupHeaderSeparator
|
||||||
import com.intellij.ui.SimpleColoredComponent
|
import com.intellij.ui.SimpleColoredComponent
|
||||||
import com.intellij.ui.SimpleTextAttributes
|
import com.intellij.ui.SimpleTextAttributes
|
||||||
|
@ -51,17 +46,13 @@ import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||||
import com.intellij.util.ui.EmptyIcon
|
import com.intellij.util.ui.EmptyIcon
|
||||||
import com.intellij.util.ui.JBUI
|
import com.intellij.util.ui.JBUI
|
||||||
import com.intellij.util.ui.UIUtil
|
import com.intellij.util.ui.UIUtil
|
||||||
import fleet.util.async.awaitResult
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.awt.BorderLayout
|
import java.awt.BorderLayout
|
||||||
import java.awt.Component
|
import java.awt.Component
|
||||||
import java.util.IdentityHashMap
|
import java.util.IdentityHashMap
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
|
||||||
import javax.accessibility.AccessibleContext
|
import javax.accessibility.AccessibleContext
|
||||||
import javax.swing.CellRendererPane
|
|
||||||
import javax.swing.JComponent
|
|
||||||
import javax.swing.JList
|
import javax.swing.JList
|
||||||
import javax.swing.border.Border
|
import javax.swing.border.Border
|
||||||
|
|
||||||
|
@ -145,33 +136,43 @@ internal class TCModel private constructor(elements: List<TCListElem>, private v
|
||||||
if (elem !is TCListElem.Pending)
|
if (elem !is TCListElem.Pending)
|
||||||
continue
|
continue
|
||||||
zigCoroutineScope.launch(Dispatchers.EDT + ModalityState.any().asContextElement()) {
|
zigCoroutineScope.launch(Dispatchers.EDT + ModalityState.any().asContextElement()) {
|
||||||
val newElem = elem.elem.awaitResult().getOrNull()
|
elem.elems.collect { newElem ->
|
||||||
swap(elem, newElem, counter)
|
insertBefore(elem, newElem, counter)
|
||||||
|
}
|
||||||
|
remove(elem, counter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresEdt
|
@RequiresEdt
|
||||||
private fun swap(old: TCListElem, new: TCListElem?, oldCounter: Int) {
|
private fun remove(old: TCListElem, oldCounter: Int) {
|
||||||
|
val newCounter = this@TCModel.counter
|
||||||
|
if (oldCounter != newCounter) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val index = this@TCModel.getElementIndex(old)
|
||||||
|
this@TCModel.remove(index)
|
||||||
|
val sep = separators.remove(old)
|
||||||
|
if (sep != null && this@TCModel.size > index) {
|
||||||
|
this@TCModel.getElementAt(index)?.let { separators[it] = sep }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresEdt
|
||||||
|
private fun insertBefore(old: TCListElem, new: TCListElem?, oldCounter: Int) {
|
||||||
val newCounter = this@TCModel.counter
|
val newCounter = this@TCModel.counter
|
||||||
if (oldCounter != newCounter) {
|
if (oldCounter != newCounter) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (new == null) {
|
if (new == null) {
|
||||||
val index = this@TCModel.getElementIndex(old)
|
|
||||||
this@TCModel.remove(index)
|
|
||||||
val sep = separators.remove(old)
|
|
||||||
if (sep != null && this@TCModel.size > index) {
|
|
||||||
this@TCModel.getElementAt(index)?.let { separators[it] = sep }
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val currentIndex = this@TCModel.getElementIndex(old)
|
val currentIndex = this@TCModel.getElementIndex(old)
|
||||||
separators.remove(old)?.let {
|
separators.remove(old)?.let {
|
||||||
separators.put(new, it)
|
separators.put(new, it)
|
||||||
}
|
}
|
||||||
this@TCModel.setElementAt(new, currentIndex)
|
this@TCModel.add(currentIndex, new)
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresEdt
|
@RequiresEdt
|
||||||
|
|
|
@ -190,6 +190,9 @@
|
||||||
<projectConfigProvider
|
<projectConfigProvider
|
||||||
implementation="com.falsepattern.zigbrains.project.toolchain.ui.ZigToolchainEditor$Provider"
|
implementation="com.falsepattern.zigbrains.project.toolchain.ui.ZigToolchainEditor$Provider"
|
||||||
/>
|
/>
|
||||||
|
<projectConfigProvider
|
||||||
|
implementation="com.falsepattern.zigbrains.direnv.ui.DirenvEditor$Provider"
|
||||||
|
/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
<actions resource-bundle="zigbrains.ActionsBundle">
|
<actions resource-bundle="zigbrains.ActionsBundle">
|
||||||
|
|
|
@ -152,3 +152,4 @@ settings.toolchain.local-selector.state.invalid=Invalid toolchain path
|
||||||
settings.toolchain.local-selector.state.already-exists-unnamed=Toolchain already exists
|
settings.toolchain.local-selector.state.already-exists-unnamed=Toolchain already exists
|
||||||
settings.toolchain.local-selector.state.already-exists-named=Toolchain already exists as "{0}"
|
settings.toolchain.local-selector.state.already-exists-named=Toolchain already exists as "{0}"
|
||||||
settings.toolchain.local-selector.state.ok=Toolchain path OK
|
settings.toolchain.local-selector.state.ok=Toolchain path OK
|
||||||
|
settings.direnv.enable.label=Direnv
|
||||||
|
|
Loading…
Add table
Reference in a new issue