diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/AbstractZigToolchain.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/AbstractZigToolchain.kt
index ec3e3b94..8db941ba 100644
--- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/AbstractZigToolchain.kt
+++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/AbstractZigToolchain.kt
@@ -27,6 +27,7 @@ import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.openapi.project.Project
import java.nio.file.Path
+
abstract class AbstractZigToolchain {
val zig: ZigCompilerTool by lazy { ZigCompilerTool(this) }
diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchain.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchain.kt
index b534ecb7..76e6d8cb 100644
--- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchain.kt
+++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchain.kt
@@ -30,10 +30,11 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.util.KeyWithDefaultValue
import com.intellij.openapi.util.SystemInfo
+import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.openapi.vfs.toNioPathOrNull
import java.nio.file.Path
-class LocalZigToolchain(val location: Path): AbstractZigToolchain() {
+class LocalZigToolchain(var location: Path, var std: Path? = null, var name: String? = null): AbstractZigToolchain() {
override fun workingDirectory(project: Project?): Path? {
return project?.guessProjectDir()?.toNioPathOrNull()
}
@@ -62,5 +63,17 @@ class LocalZigToolchain(val location: Path): AbstractZigToolchain() {
throw ExecutionException("The debugger only supports local zig toolchain")
}
}
+
+ fun tryFromPathString(pathStr: String): LocalZigToolchain? {
+ return pathStr.toNioPathOrNull()?.let(::tryFromPath)
+ }
+
+ fun tryFromPath(path: Path): LocalZigToolchain? {
+ val tc = LocalZigToolchain(path)
+ if (!tc.zig.fileValid()) {
+ return null
+ }
+ return tc
+ }
}
}
\ No newline at end of file
diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainConfigurable.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainConfigurable.kt
new file mode 100644
index 00000000..95ea83c9
--- /dev/null
+++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainConfigurable.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.project.toolchain
+
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.ui.NamedConfigurable
+import com.intellij.openapi.util.NlsContexts
+import com.intellij.openapi.util.NlsSafe
+import com.intellij.ui.dsl.builder.panel
+import kotlinx.coroutines.runBlocking
+import javax.swing.JComponent
+
+class LocalZigToolchainConfigurable(
+ val toolchain: LocalZigToolchain,
+ private val project: Project
+): NamedConfigurable() {
+ private var myView: LocalZigToolchainPanel? = null
+ override fun setDisplayName(name: @NlsSafe String?) {
+ toolchain.name = name
+ }
+
+ override fun getEditableObject(): LocalZigToolchain? {
+ return toolchain
+ }
+
+ override fun getBannerSlogan(): @NlsContexts.DetailedDescription String? {
+ return displayName
+ }
+
+ override fun createOptionsPanel(): JComponent? {
+ var view = myView
+ if (view == null) {
+ view = LocalZigToolchainPanel()
+ view.reset(this)
+ myView = view
+ }
+ return panel {
+ view.attach(this)
+ }
+ }
+
+ override fun getDisplayName(): @NlsContexts.ConfigurableName String? {
+ var theName = toolchain.name
+ if (theName == null) {
+ val version = toolchain.zig.let { runBlocking { it.getEnv(project) } }.getOrNull()?.version
+ if (version != null) {
+ theName = "Zig $version"
+ toolchain.name = theName
+ }
+ }
+ return theName
+ }
+
+ override fun isModified(): Boolean {
+ return myView?.isModified(this) == true
+ }
+
+ override fun apply() {
+ myView?.apply(this)
+ }
+
+ override fun reset() {
+ myView?.reset(this)
+ }
+
+ override fun disposeUIResources() {
+ super.disposeUIResources()
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainPanel.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainPanel.kt
new file mode 100644
index 00000000..e35d8e7e
--- /dev/null
+++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainPanel.kt
@@ -0,0 +1,176 @@
+/*
+ * 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.project.toolchain
+
+import com.falsepattern.zigbrains.ZigBrainsBundle
+import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
+import com.falsepattern.zigbrains.shared.zigCoroutineScope
+import com.intellij.openapi.Disposable
+import com.intellij.openapi.application.ModalityState
+import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
+import com.intellij.openapi.util.Disposer
+import com.intellij.openapi.util.io.toNioPathOrNull
+import com.intellij.ui.DocumentAdapter
+import com.intellij.ui.JBColor
+import com.intellij.ui.components.JBCheckBox
+import com.intellij.ui.components.JBTextArea
+import com.intellij.ui.components.JBTextField
+import com.intellij.ui.components.textFieldWithBrowseButton
+import com.intellij.ui.dsl.builder.AlignX
+import com.intellij.ui.dsl.builder.Panel
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import javax.swing.event.DocumentEvent
+import kotlin.io.path.pathString
+
+class LocalZigToolchainPanel() : Disposable {
+ private val nameField = JBTextField()
+ private val pathToToolchain = textFieldWithBrowseButton(
+ null,
+ FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.zig-toolchain"))
+ ).also {
+ it.textField.document.addDocumentListener(object : DocumentAdapter() {
+ override fun textChanged(e: DocumentEvent) {
+ dispatchUpdateUI()
+ }
+ })
+ Disposer.register(this, it)
+ }
+ private val toolchainVersion = JBTextArea().also { it.isEditable = false }
+ private val stdFieldOverride = JBCheckBox(ZigBrainsBundle.message("settings.project.label.override-std")).apply {
+ addChangeListener {
+ if (isSelected) {
+ pathToStd.isEnabled = true
+ } else {
+ pathToStd.isEnabled = false
+ updateUI()
+ }
+ }
+ }
+ private val pathToStd = textFieldWithBrowseButton(
+ null,
+ FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.zig-std"))
+ ).also { Disposer.register(this, it) }
+ private var debounce: Job? = null
+
+ fun attach(p: Panel): Unit = with(p) {
+ row("Name") {
+ cell(nameField).resizableColumn().align(AlignX.FILL)
+ }
+ separator()
+ row(ZigBrainsBundle.message("settings.project.label.toolchain")) {
+ cell(pathToToolchain).resizableColumn().align(AlignX.FILL)
+ }
+ row(ZigBrainsBundle.message("settings.project.label.toolchain-version")) {
+ cell(toolchainVersion)
+ }
+ row(ZigBrainsBundle.message("settings.project.label.std-location")) {
+ cell(pathToStd).resizableColumn().align(AlignX.FILL)
+ cell(stdFieldOverride)
+ }
+ }
+
+ fun isModified(cfg: LocalZigToolchainConfigurable): Boolean {
+ val name = nameField.text.ifBlank { null } ?: return false
+ val tc = cfg.toolchain
+ val location = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull() ?: return false
+ val std = if (stdFieldOverride.isSelected) pathToStd.text.ifBlank { null }?.toNioPathOrNull() else null
+ return name != cfg.displayName || tc.location != location || tc.std != std
+ }
+
+ fun apply(cfg: LocalZigToolchainConfigurable): Boolean {
+ cfg.displayName = nameField.text
+ val tc = cfg.toolchain
+ val location = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull() ?: return false
+ val std = if (stdFieldOverride.isSelected) pathToStd.text.ifBlank { null }?.toNioPathOrNull() else null
+ tc.location = location
+ tc.std = std
+ return true
+ }
+
+ fun reset(cfg: LocalZigToolchainConfigurable) {
+ nameField.text = cfg.displayName ?: ""
+ val tc = cfg.toolchain
+ this.pathToToolchain.text = tc.location.pathString
+ val std = tc.std
+ if (std != null) {
+ stdFieldOverride.isSelected = true
+ pathToStd.text = std.pathString
+ pathToStd.isEnabled = true
+ } else {
+ stdFieldOverride.isSelected = false
+ pathToStd.text = ""
+ pathToStd.isEnabled = false
+ dispatchUpdateUI()
+ }
+ }
+
+ private fun dispatchUpdateUI() {
+ debounce?.cancel("New debounce")
+ debounce = zigCoroutineScope.launch {
+ updateUI()
+ }
+ }
+
+ private suspend fun updateUI() {
+ delay(200)
+ val pathToToolchain = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull()
+ if (pathToToolchain == null) {
+ withEDTContext(ModalityState.any()) {
+ toolchainVersion.text = "[toolchain path empty or invalid]"
+ if (!stdFieldOverride.isSelected) {
+ pathToStd.text = ""
+ }
+ }
+ return
+ }
+ val toolchain = LocalZigToolchain(pathToToolchain)
+ val zig = toolchain.zig
+ val env = zig.getEnv(null).getOrElse { throwable ->
+ throwable.printStackTrace()
+ withEDTContext(ModalityState.any()) {
+ toolchainVersion.text = "[failed to run \"zig env\"]\n${throwable.message}"
+ if (!stdFieldOverride.isSelected) {
+ pathToStd.text = ""
+ }
+ }
+ return
+ }
+ val version = env.version
+ val stdPath = env.stdPath(toolchain, null)
+
+ withEDTContext(ModalityState.any()) {
+ toolchainVersion.text = version
+ toolchainVersion.foreground = JBColor.foreground()
+ if (!stdFieldOverride.isSelected) {
+ pathToStd.text = stdPath?.pathString ?: ""
+ }
+ }
+ }
+
+ override fun dispose() {
+ debounce?.cancel("Disposed")
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigSDKType.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigSDKType.kt
new file mode 100644
index 00000000..31f25638
--- /dev/null
+++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigSDKType.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.project.toolchain
+
+import com.falsepattern.zigbrains.direnv.emptyEnv
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.projectRoots.AdditionalDataConfigurable
+import com.intellij.openapi.projectRoots.SdkAdditionalData
+import com.intellij.openapi.projectRoots.SdkModel
+import com.intellij.openapi.projectRoots.SdkModificator
+import com.intellij.openapi.projectRoots.SdkType
+import com.intellij.openapi.util.UserDataHolderBase
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.util.EnvironmentUtil
+import com.intellij.util.asSafely
+import com.intellij.util.system.OS
+import kotlinx.coroutines.runBlocking
+import org.jdom.Element
+import org.jetbrains.annotations.Nls
+import java.io.File
+import kotlin.io.path.pathString
+
+class ZigSDKType: SdkType("Zig") {
+ override fun suggestHomePath(): String? {
+ return null
+ }
+
+ private fun getPathEnv(path: String): ZigToolchainEnvironmentSerializable? {
+ return LocalZigToolchain.tryFromPathString(path)?.zig?.let { runBlocking { it.getEnv(null) } }?.getOrNull()
+ }
+
+ override fun isValidSdkHome(path: String): Boolean {
+ return LocalZigToolchain.tryFromPathString(path) != null
+ }
+
+ override fun suggestSdkName(currentSdkName: String?, sdkHome: String): String {
+ return getVersionString(sdkHome)?.let { "Zig $it" } ?: currentSdkName ?: "Zig"
+ }
+
+ override fun getVersionString(sdkHome: String): String? {
+ return getPathEnv(sdkHome)?.version
+ }
+
+ override fun suggestHomePaths(): Collection {
+ val res = HashSet()
+ EnvironmentUtil.getValue("PATH")?.split(File.pathSeparatorChar)?.let { res.addAll(it.toList()) }
+ if (OS.CURRENT != OS.Windows) {
+ EnvironmentUtil.getValue("HOME")?.let { res.add("$it/.local/share/zigup") }
+ EnvironmentUtil.getValue("XDG_DATA_HOME")?.let { res.add("$it/zigup") }
+ }
+ return res
+ }
+
+ override fun createAdditionalDataConfigurable(
+ sdkModel: SdkModel,
+ sdkModificator: SdkModificator
+ ): AdditionalDataConfigurable? {
+ return null
+ }
+
+ override fun getPresentableName(): @Nls(capitalization = Nls.Capitalization.Title) String {
+ return "Zig"
+ }
+
+ override fun saveAdditionalData(additionalData: SdkAdditionalData, additional: Element) {
+
+ }
+
+ override fun isRelevantForFile(project: Project, file: VirtualFile): Boolean {
+ return file.extension == "zig" || file.extension == "zon"
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainList.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainList.kt
new file mode 100644
index 00000000..62fac96a
--- /dev/null
+++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainList.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.project.toolchain
+
+import com.intellij.util.xmlb.annotations.OptionTag
+
+data class ZigToolchainList(
+ @get:OptionTag(converter = ZigToolchainListConverter::class)
+ var toolchains: List = emptyList(),
+)
diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainListEditor.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainListEditor.kt
new file mode 100644
index 00000000..6ff47dcd
--- /dev/null
+++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainListEditor.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.project.toolchain
+
+import com.falsepattern.zigbrains.ZigBrainsBundle
+import com.intellij.openapi.actionSystem.AnAction
+import com.intellij.openapi.actionSystem.AnActionEvent
+import com.intellij.openapi.actionSystem.Presentation
+import com.intellij.openapi.project.DumbAwareAction
+import com.intellij.openapi.project.ProjectManager
+import com.intellij.openapi.roots.ui.configuration.SdkPopupFactory
+import com.intellij.openapi.ui.MasterDetailsComponent
+import com.intellij.openapi.util.io.toNioPathOrNull
+import com.intellij.util.IconUtil
+import javax.swing.JComponent
+import javax.swing.tree.DefaultTreeModel
+
+class ZigToolchainListEditor(): MasterDetailsComponent() {
+ private var isTreeInitialized = false
+
+ override fun createComponent(): JComponent {
+ if (!isTreeInitialized) {
+ initTree()
+ isTreeInitialized = true
+ }
+ return super.createComponent()
+ }
+
+ override fun createActions(fromPopup: Boolean): List? {
+ val add = object : DumbAwareAction({"lmaoo"}, Presentation.NULL_STRING, IconUtil.addIcon) {
+ override fun actionPerformed(e: AnActionEvent) {
+ SdkPopupFactory
+ .newBuilder()
+ .withSdkTypeFilter { it is ZigSDKType }
+ .onSdkSelected {
+ val path = it.homePath?.toNioPathOrNull() ?: return@onSdkSelected
+ val toolchain = LocalZigToolchain(path)
+ zigToolchainList.state = ZigToolchainList(zigToolchainList.state.toolchains + listOf(toolchain))
+ addLocalToolchain(toolchain)
+ (myTree.model as DefaultTreeModel).reload()
+ }
+ .buildPopup()
+ .showPopup(e)
+ }
+ }
+ return listOf(add, MyDeleteAction())
+ }
+
+ override fun onItemDeleted(item: Any?) {
+ if (item is AbstractZigToolchain) {
+ zigToolchainList.state = ZigToolchainList(zigToolchainList.state.toolchains.filter { it != item })
+ }
+ super.onItemDeleted(item)
+ }
+
+ override fun reset() {
+ reloadTree()
+ super.reset()
+ }
+
+ override fun getEmptySelectionString() = ZigBrainsBundle.message("settings.toolchains.empty")
+
+ override fun getDisplayName() = ZigBrainsBundle.message("settings.toolchains.title")
+
+ private fun addLocalToolchain(toolchain: LocalZigToolchain) {
+ val node = MyNode(LocalZigToolchainConfigurable(toolchain, ProjectManager.getInstance().defaultProject))
+ addNode(node, myRoot)
+ }
+
+ private fun reloadTree() {
+ myRoot.removeAllChildren()
+ zigToolchainList.state.toolchains.forEach { toolchain ->
+ if (toolchain is LocalZigToolchain) {
+ addLocalToolchain(toolchain)
+ }
+ }
+ (myTree.model as DefaultTreeModel).reload()
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainListService.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainListService.kt
new file mode 100644
index 00000000..5c301b90
--- /dev/null
+++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainListService.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.project.toolchain
+
+import com.falsepattern.zigbrains.project.settings.ZigProjectSettings
+import com.falsepattern.zigbrains.project.toolchain.stdlib.ZigSyntheticLibrary
+import com.falsepattern.zigbrains.shared.zigCoroutineScope
+import com.intellij.openapi.components.*
+import com.intellij.openapi.project.Project
+import kotlinx.coroutines.launch
+
+@Service(Service.Level.APP)
+@State(
+ name = "ZigProjectSettings",
+ storages = [Storage("zigbrains.xml")]
+)
+class ZigToolchainListService(): PersistentStateComponent {
+ @Volatile
+ private var state = ZigToolchainList()
+
+ override fun getState(): ZigToolchainList {
+ return state.copy()
+ }
+
+ fun setState(value: ZigToolchainList) {
+ this.state = value
+ }
+
+ override fun loadState(state: ZigToolchainList) {
+ setState(state)
+ }
+
+ fun isModified(otherData: ZigToolchainList): Boolean {
+ return state != otherData
+ }
+}
+
+val zigToolchainList get() = service()
\ No newline at end of file
diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainProvider.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainProvider.kt
index 57aeb51b..1ad68e8d 100644
--- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainProvider.kt
+++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainProvider.kt
@@ -26,6 +26,7 @@ import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider.Compani
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.UserDataHolder
+import com.intellij.util.asSafely
import com.intellij.util.xmlb.Converter
import kotlinx.serialization.json.*
@@ -43,6 +44,21 @@ sealed interface ZigToolchainProvider {
suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): AbstractZigToolchain? {
return EXTENSION_POINT_NAME.extensionList.firstNotNullOfOrNull { it.suggestToolchain(project, extraData) }
}
+
+ fun fromJson(json: JsonObject): AbstractZigToolchain? {
+ val marker = (json["marker"] as? JsonPrimitive)?.contentOrNull ?: return null
+ val data = json["data"] ?: return null
+ val provider = EXTENSION_POINT_NAME.extensionList.find { it.serialMarker == marker } ?: return null
+ return provider.deserialize(data)
+ }
+
+ fun toJson(tc: AbstractZigToolchain): JsonObject? {
+ val provider = EXTENSION_POINT_NAME.extensionList.find { it.canSerialize(tc) } ?: return null
+ return buildJsonObject {
+ put("marker", provider.serialMarker)
+ put("data", provider.serialize(tc))
+ }
+ }
}
}
@@ -52,18 +68,25 @@ private fun ZigToolchainProvider.serialize(toolchai
class ZigToolchainConverter: Converter() {
override fun fromString(value: String): AbstractZigToolchain? {
val json = Json.parseToJsonElement(value) as? JsonObject ?: return null
- val marker = (json["marker"] as? JsonPrimitive)?.contentOrNull ?: return null
- val data = json["data"] ?: return null
- val provider = EXTENSION_POINT_NAME.extensionList.find { it.serialMarker == marker } ?: return null
- return provider.deserialize(data)
+ return ZigToolchainProvider.fromJson(json)
}
override fun toString(value: AbstractZigToolchain): String? {
- val provider = EXTENSION_POINT_NAME.extensionList.find { it.canSerialize(value) } ?: return null
- return buildJsonObject {
- put("marker", provider.serialMarker)
- put("data", provider.serialize(value))
- }.toString()
+ return ZigToolchainProvider.toJson(value)?.toString()
+ }
+}
+
+class ZigToolchainListConverter: Converter>() {
+ override fun fromString(value: String): List {
+ val json = Json.parseToJsonElement(value) as? JsonArray ?: return emptyList()
+ return json.mapNotNull { it.asSafely()?.let { ZigToolchainProvider.fromJson(it) } }
}
+ override fun toString(value: List): String {
+ return buildJsonArray {
+ value.mapNotNull { ZigToolchainProvider.toJson(it) }.forEach {
+ add(it)
+ }
+ }.toString()
+ }
}
\ No newline at end of file
diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigTool.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigTool.kt
index 27b106bf..c7a1e507 100644
--- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigTool.kt
+++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigTool.kt
@@ -29,6 +29,7 @@ import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.process.ProcessOutput
import com.intellij.openapi.project.Project
import java.nio.file.Path
+import kotlin.io.path.isRegularFile
abstract class ZigTool(val toolchain: AbstractZigToolchain) {
abstract val toolName: String
@@ -38,6 +39,11 @@ abstract class ZigTool(val toolchain: AbstractZigToolchain) {
return cli.call(timeoutMillis, ipcProject = ipcProject)
}
+ fun fileValid(): Boolean {
+ val exe = toolchain.pathToExecutable(toolName)
+ return exe.isRegularFile()
+ }
+
private suspend fun createBaseCommandLine(
workingDirectory: Path?,
vararg parameters: String
diff --git a/core/src/main/resources/META-INF/zigbrains-core.xml b/core/src/main/resources/META-INF/zigbrains-core.xml
index 45220600..ec6a4f81 100644
--- a/core/src/main/resources/META-INF/zigbrains-core.xml
+++ b/core/src/main/resources/META-INF/zigbrains-core.xml
@@ -144,6 +144,13 @@
id="ZigConfigurable"
displayName="Zig"
/>
+
+