From f53b0e3283cb998c8a4d5d44aa1da80a085cd2b4 Mon Sep 17 00:00:00 2001 From: FalsePattern Date: Sat, 5 Apr 2025 13:33:31 +0200 Subject: [PATCH] begin implementation of multi-toolchain management --- .../project/toolchain/AbstractZigToolchain.kt | 1 + .../project/toolchain/LocalZigToolchain.kt | 15 +- .../LocalZigToolchainConfigurable.kt | 89 +++++++++ .../toolchain/LocalZigToolchainPanel.kt | 176 ++++++++++++++++++ .../zigbrains/project/toolchain/ZigSDKType.kt | 92 +++++++++ .../project/toolchain/ZigToolchainList.kt | 30 +++ .../toolchain/ZigToolchainListEditor.kt | 99 ++++++++++ .../toolchain/ZigToolchainListService.kt | 58 ++++++ .../project/toolchain/ZigToolchainProvider.kt | 41 +++- .../project/toolchain/tools/ZigTool.kt | 6 + .../resources/META-INF/zigbrains-core.xml | 7 + .../resources/zigbrains/Bundle.properties | 2 + 12 files changed, 606 insertions(+), 10 deletions(-) create mode 100644 core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainConfigurable.kt create mode 100644 core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainPanel.kt create mode 100644 core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigSDKType.kt create mode 100644 core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainList.kt create mode 100644 core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainListEditor.kt create mode 100644 core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainListService.kt 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" /> + +