From e737058cb50cbb4252a74591a0d16469fe549d90 Mon Sep 17 00:00:00 2001 From: FalsePattern Date: Sat, 5 Apr 2025 19:46:48 +0200 Subject: [PATCH] more progress --- .../com/falsepattern/zigbrains/ZBStartup.kt | 3 +- .../settings/ZigProjectSettingsPanel.kt | 3 +- .../project/toolchain/AbstractZigToolchain.kt | 14 +++ .../project/toolchain/LocalZigToolchain.kt | 4 +- .../LocalZigToolchainConfigurable.kt | 22 ++-- .../toolchain/LocalZigToolchainPanel.kt | 4 +- .../toolchain/LocalZigToolchainProvider.kt | 61 +++++++--- .../zigbrains/project/toolchain/ZigSDKType.kt | 92 --------------- .../project/toolchain/ZigToolchainList.kt | 30 ----- .../toolchain/ZigToolchainListEditor.kt | 109 ++++++++++++++---- .../toolchain/ZigToolchainListService.kt | 51 +++++--- .../project/toolchain/ZigToolchainProvider.kt | 94 +++++++-------- .../resources/META-INF/zigbrains-core.xml | 1 - .../toolchain/ToolchainZLSConfigProvider.kt | 2 +- 14 files changed, 246 insertions(+), 244 deletions(-) delete mode 100644 core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigSDKType.kt delete mode 100644 core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainList.kt diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/ZBStartup.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/ZBStartup.kt index b51e5b7f..fa4d6f56 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/ZBStartup.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/ZBStartup.kt @@ -26,6 +26,7 @@ import com.falsepattern.zigbrains.direnv.DirenvCmd import com.falsepattern.zigbrains.project.settings.zigProjectSettings import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider +import com.falsepattern.zigbrains.project.toolchain.suggestZigToolchain import com.intellij.ide.BrowserUtil import com.intellij.ide.plugins.PluginManager import com.intellij.notification.Notification @@ -80,7 +81,7 @@ class ZBStartup: ProjectActivity { data.putUserData(LocalZigToolchain.DIRENV_KEY, DirenvCmd.direnvInstalled() && !project.isDefault && zigProjectState.direnv ) - val tc = ZigToolchainProvider.suggestToolchain(project, data) ?: return + val tc = project.suggestZigToolchain(data) ?: return if (tc is LocalZigToolchain) { zigProjectState.toolchainPath = tc.location.pathString project.zigProjectSettings.state = zigProjectState diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettingsPanel.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettingsPanel.kt index 9c998089..b9a388a1 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettingsPanel.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettingsPanel.kt @@ -26,6 +26,7 @@ import com.falsepattern.zigbrains.ZigBrainsBundle import com.falsepattern.zigbrains.direnv.DirenvCmd import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider +import com.falsepattern.zigbrains.project.toolchain.suggestZigToolchain import com.falsepattern.zigbrains.shared.coroutine.launchWithEDT import com.falsepattern.zigbrains.shared.coroutine.withEDTContext import com.falsepattern.zigbrains.shared.zigCoroutineScope @@ -107,7 +108,7 @@ class ZigProjectSettingsPanel(private val holder: ZigProjectConfigurationProvide return val data = UserDataHolderBase() data.putUserData(LocalZigToolchain.DIRENV_KEY, !project.isDefault && direnv.isSelected && DirenvCmd.direnvInstalled()) - val tc = ZigToolchainProvider.suggestToolchain(project, data) ?: return + val tc = project.suggestZigToolchain(project) ?: return if (tc !is LocalZigToolchain) { TODO("Implement non-local zig toolchain in config") } 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 8db941ba..a388e64d 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 @@ -25,7 +25,12 @@ package com.falsepattern.zigbrains.project.toolchain import com.falsepattern.zigbrains.project.toolchain.tools.ZigCompilerTool import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.openapi.project.Project +import com.intellij.util.xmlb.Converter +import com.intellij.util.xmlb.annotations.Attribute +import com.intellij.util.xmlb.annotations.MapAnnotation +import com.intellij.util.xmlb.annotations.OptionTag import java.nio.file.Path +import java.util.UUID abstract class AbstractZigToolchain { @@ -36,4 +41,13 @@ abstract class AbstractZigToolchain { abstract suspend fun patchCommandLine(commandLine: GeneralCommandLine, project: Project? = null): GeneralCommandLine abstract fun pathToExecutable(toolName: String, project: Project? = null): Path + + data class Ref( + @JvmField + @Attribute + val marker: String? = null, + @JvmField + @MapAnnotation(surroundWithTag = false) + val data: Map? = null, + ) } \ No newline at end of file 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 76e6d8cb..2eeca5fe 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 @@ -33,8 +33,10 @@ import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.io.toNioPathOrNull import com.intellij.openapi.vfs.toNioPathOrNull import java.nio.file.Path +import java.util.UUID +import kotlin.io.path.pathString -class LocalZigToolchain(var location: Path, var std: Path? = null, var name: String? = null): AbstractZigToolchain() { +data class LocalZigToolchain(val location: Path, val std: Path? = null, val name: String? = null): AbstractZigToolchain() { override fun workingDirectory(project: Project?): Path? { return project?.guessProjectDir()?.toNioPathOrNull() } 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 index 95ea83c9..3cfb420d 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainConfigurable.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainConfigurable.kt @@ -25,22 +25,28 @@ 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 java.util.UUID import javax.swing.JComponent class LocalZigToolchainConfigurable( - val toolchain: LocalZigToolchain, + val uuid: UUID, + toolchain: LocalZigToolchain, private val project: Project -): NamedConfigurable() { +): NamedConfigurable() { + var toolchain: LocalZigToolchain = toolchain + set(value) { + zigToolchainList.setToolchain(uuid, value) + field = value + } private var myView: LocalZigToolchainPanel? = null - override fun setDisplayName(name: @NlsSafe String?) { - toolchain.name = name + override fun setDisplayName(name: String?) { + toolchain = toolchain.copy(name = name) } - override fun getEditableObject(): LocalZigToolchain? { - return toolchain + override fun getEditableObject(): UUID { + return uuid } override fun getBannerSlogan(): @NlsContexts.DetailedDescription String? { @@ -65,7 +71,7 @@ class LocalZigToolchainConfigurable( val version = toolchain.zig.let { runBlocking { it.getEnv(project) } }.getOrNull()?.version if (version != null) { theName = "Zig $version" - toolchain.name = theName + toolchain = toolchain.copy(name = theName) } } return theName 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 index e35d8e7e..4cea024c 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainPanel.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainPanel.kt @@ -101,12 +101,10 @@ class LocalZigToolchainPanel() : Disposable { } 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 + cfg.toolchain = tc.copy(location = location, std = std, name = nameField.text ?: "") return true } diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainProvider.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainProvider.kt index a7701ad6..d49f652b 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainProvider.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchainProvider.kt @@ -26,12 +26,22 @@ import com.falsepattern.zigbrains.direnv.DirenvCmd import com.falsepattern.zigbrains.direnv.emptyEnv import com.falsepattern.zigbrains.project.settings.zigProjectSettings import com.intellij.openapi.project.Project +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.roots.ui.configuration.SdkPopupBuilder +import com.intellij.openapi.ui.NamedConfigurable import com.intellij.openapi.util.UserDataHolder import com.intellij.openapi.util.io.toNioPathOrNull +import com.intellij.util.EnvironmentUtil +import com.intellij.util.system.OS +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.mapNotNull import kotlinx.serialization.json.* +import java.io.File +import java.util.UUID import kotlin.io.path.pathString -class LocalZigToolchainProvider: ZigToolchainProvider { +class LocalZigToolchainProvider: ZigToolchainProvider { override suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): LocalZigToolchain? { val env = if (project != null && (extraData.getUserData(LocalZigToolchain.DIRENV_KEY) ?: project.zigProjectSettings.state.direnv)) { DirenvCmd.importDirenv(project) @@ -45,22 +55,47 @@ class LocalZigToolchainProvider: ZigToolchainProvider { override val serialMarker: String get() = "local" - override fun deserialize(data: JsonElement): LocalZigToolchain? { - if (data !is JsonObject) - return null - - val loc = data["location"] as? JsonPrimitive ?: return null - val path = loc.content.toNioPathOrNull() ?: return null - return LocalZigToolchain(path) + override fun deserialize(data: Map): AbstractZigToolchain? { + val location = data["location"]?.toNioPathOrNull() ?: return null + val std = data["std"]?.toNioPathOrNull() + val name = data["name"] + return LocalZigToolchain(location, std, name) } - override fun canSerialize(toolchain: AbstractZigToolchain): Boolean { + override fun isCompatible(toolchain: AbstractZigToolchain): Boolean { return toolchain is LocalZigToolchain } - override fun serialize(toolchain: LocalZigToolchain): JsonElement { - return buildJsonObject { - put("location", toolchain.location.pathString) - } + override fun serialize(toolchain: AbstractZigToolchain): Map { + toolchain as LocalZigToolchain + val map = HashMap() + toolchain.location.pathString.let { map["location"] = it } + toolchain.std?.pathString?.let { map["std"] = it } + toolchain.name?.let { map["name"] = it } + return map + } + + override fun matchesSuggestion( + toolchain: AbstractZigToolchain, + suggestion: AbstractZigToolchain + ): Boolean { + toolchain as LocalZigToolchain + suggestion as LocalZigToolchain + return toolchain.location == suggestion.location + } + + override fun createConfigurable( + uuid: UUID, + toolchain: AbstractZigToolchain, + project: Project + ): NamedConfigurable { + toolchain as LocalZigToolchain + return LocalZigToolchainConfigurable(uuid, toolchain, project) + } + + override fun suggestToolchains(): List { + val res = HashSet() + EnvironmentUtil.getValue("PATH")?.split(File.pathSeparatorChar)?.let { res.addAll(it.toList()) } + return res.mapNotNull { LocalZigToolchain.tryFromPathString(it) } } } \ 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 deleted file mode 100644 index 31f25638..00000000 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigSDKType.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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 deleted file mode 100644 index 62fac96a..00000000 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainList.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 index 6ff47dcd..778b1b64 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainListEditor.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainListEditor.kt @@ -23,17 +23,51 @@ package com.falsepattern.zigbrains.project.toolchain import com.falsepattern.zigbrains.ZigBrainsBundle +import com.intellij.ide.projectView.TreeStructureProvider +import com.intellij.ide.util.treeView.AbstractTreeStructure +import com.intellij.ide.util.treeView.AbstractTreeStructureBase 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.Project import com.intellij.openapi.project.ProjectManager +import com.intellij.openapi.roots.ui.SdkAppearanceService +import com.intellij.openapi.roots.ui.configuration.SdkListPresenter import com.intellij.openapi.roots.ui.configuration.SdkPopupFactory import com.intellij.openapi.ui.MasterDetailsComponent -import com.intellij.openapi.util.io.toNioPathOrNull +import com.intellij.openapi.ui.popup.JBPopupFactory +import com.intellij.openapi.ui.popup.util.BaseTreePopupStep +import com.intellij.openapi.util.NlsContexts +import com.intellij.ui.CollectionListModel +import com.intellij.ui.ColoredListCellRenderer +import com.intellij.ui.SimpleTextAttributes +import com.intellij.ui.components.JBLabel +import com.intellij.ui.components.JBPanel +import com.intellij.ui.components.panels.HorizontalLayout +import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.dsl.gridLayout.UnscaledGaps +import com.intellij.ui.popup.list.ComboBoxPopup +import com.intellij.ui.treeStructure.SimpleNode +import com.intellij.ui.treeStructure.SimpleTreeStructure import com.intellij.util.IconUtil +import com.intellij.util.ui.EmptyIcon +import com.intellij.util.ui.UIUtil.FontColor +import java.awt.Component +import java.awt.LayoutManager +import java.util.* +import java.util.function.Consumer +import javax.swing.AbstractListModel +import javax.swing.BoxLayout +import javax.swing.DefaultListModel import javax.swing.JComponent +import javax.swing.JList +import javax.swing.JPanel +import javax.swing.ListCellRenderer +import javax.swing.ListModel +import javax.swing.SwingConstants import javax.swing.tree.DefaultTreeModel +import kotlin.io.path.pathString class ZigToolchainListEditor(): MasterDetailsComponent() { private var isTreeInitialized = false @@ -46,29 +80,62 @@ class ZigToolchainListEditor(): MasterDetailsComponent() { return super.createComponent() } + class ToolchainContext(private val project: Project?, private val model: ListModel): ComboBoxPopup.Context { + override fun getProject(): Project? { + return project + } + + override fun getModel(): ListModel { + return model + } + + override fun getRenderer(): ListCellRenderer { + return object: ColoredListCellRenderer() { + override fun customizeCellRenderer( + list: JList, + value: Any?, + index: Int, + selected: Boolean, + hasFocus: Boolean + ) { + icon = EMPTY_ICON + if (value is LocalZigToolchain) { + icon = IconUtil.addIcon + append(SdkListPresenter.presentDetectedSdkPath(value.location.pathString)) + if (value.name != null) { + append(" ") + append(value.name, SimpleTextAttributes.GRAYED_ATTRIBUTES) + } + } + } + } + } + + } + + class ToolchainPopup(context: ToolchainContext, + selected: Any?, + onItemSelected: Consumer + ): ComboBoxPopup(context, selected, onItemSelected) { + + } + 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) + val toolchains = suggestZigToolchains(zigToolchainList.toolchains.map { it.second }.toList()) + val final = ArrayList() + final.addAll(toolchains) + val popup = ToolchainPopup(ToolchainContext(null, CollectionListModel(final)), null, {}) + popup.showInBestPositionFor(e.dataContext) } } return listOf(add, MyDeleteAction()) } override fun onItemDeleted(item: Any?) { - if (item is AbstractZigToolchain) { - zigToolchainList.state = ZigToolchainList(zigToolchainList.state.toolchains.filter { it != item }) + if (item is UUID) { + zigToolchainList.removeToolchain(item) } super.onItemDeleted(item) } @@ -82,18 +149,20 @@ class ZigToolchainListEditor(): MasterDetailsComponent() { override fun getDisplayName() = ZigBrainsBundle.message("settings.toolchains.title") - private fun addLocalToolchain(toolchain: LocalZigToolchain) { - val node = MyNode(LocalZigToolchainConfigurable(toolchain, ProjectManager.getInstance().defaultProject)) + private fun addLocalToolchain(uuid: UUID, toolchain: LocalZigToolchain) { + val node = MyNode(LocalZigToolchainConfigurable(uuid, toolchain, ProjectManager.getInstance().defaultProject)) addNode(node, myRoot) } private fun reloadTree() { myRoot.removeAllChildren() - zigToolchainList.state.toolchains.forEach { toolchain -> + zigToolchainList.toolchains.forEach { (uuid, toolchain) -> if (toolchain is LocalZigToolchain) { - addLocalToolchain(toolchain) + addLocalToolchain(uuid, toolchain) } } (myTree.model as DefaultTreeModel).reload() } -} \ No newline at end of file +} + +private val EMPTY_ICON = EmptyIcon.create(1, 16) \ 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 index 5c301b90..b999d1b5 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainListService.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ZigToolchainListService.kt @@ -22,37 +22,50 @@ 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 +import com.intellij.util.xmlb.annotations.MapAnnotation +import java.util.UUID @Service(Service.Level.APP) @State( - name = "ZigProjectSettings", + name = "ZigToolchainList", storages = [Storage("zigbrains.xml")] ) -class ZigToolchainListService(): PersistentStateComponent { - @Volatile - private var state = ZigToolchainList() - - override fun getState(): ZigToolchainList { - return state.copy() +class ZigToolchainListService: SerializablePersistentStateComponent(State()) { + fun setToolchain(uuid: UUID, toolchain: AbstractZigToolchain) { + updateState { + val newMap = HashMap() + newMap.putAll(it.toolchains) + newMap.put(uuid.toString(), toolchain.toRef()) + it.copy(toolchains = newMap) + } } - fun setState(value: ZigToolchainList) { - this.state = value + fun getToolchain(uuid: UUID): AbstractZigToolchain? { + return state.toolchains[uuid.toString()]?.resolve() } - override fun loadState(state: ZigToolchainList) { - setState(state) + fun removeToolchain(uuid: UUID) { + val str = uuid.toString() + updateState { + it.copy(toolchains = it.toolchains.filter { it.key != str }) + } } - fun isModified(otherData: ZigToolchainList): Boolean { - return state != otherData - } + val toolchains: Sequence> + get() = state.toolchains + .asSequence() + .mapNotNull { + val uuid = UUID.fromString(it.key) ?: return@mapNotNull null + val tc = it.value.resolve() ?: return@mapNotNull null + uuid to tc + } + + data class State( + @JvmField + @MapAnnotation(surroundKeyWithTag = false, surroundValueWithTag = false) + val toolchains: Map = emptyMap(), + ) } 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 1ad68e8d..f6c7eccf 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 @@ -22,71 +22,57 @@ package com.falsepattern.zigbrains.project.toolchain -import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider.Companion.EXTENSION_POINT_NAME import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.NamedConfigurable import com.intellij.openapi.util.UserDataHolder -import com.intellij.util.asSafely -import com.intellij.util.xmlb.Converter -import kotlinx.serialization.json.* +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapMerge +import kotlinx.coroutines.flow.flattenConcat +import kotlinx.coroutines.flow.map +import java.util.UUID -sealed interface ZigToolchainProvider { +private val EXTENSION_POINT_NAME = ExtensionPointName.create("com.falsepattern.zigbrains.toolchainProvider") + +sealed interface ZigToolchainProvider { suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): AbstractZigToolchain? val serialMarker: String - fun deserialize(data: JsonElement): AbstractZigToolchain? - fun canSerialize(toolchain: AbstractZigToolchain): Boolean - fun serialize(toolchain: T): JsonElement - - companion object { - val EXTENSION_POINT_NAME = ExtensionPointName.create>("com.falsepattern.zigbrains.toolchainProvider") - - 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)) - } - } - } + fun isCompatible(toolchain: AbstractZigToolchain): Boolean + fun deserialize(data: Map): AbstractZigToolchain? + fun serialize(toolchain: AbstractZigToolchain): Map + fun matchesSuggestion(toolchain: AbstractZigToolchain, suggestion: AbstractZigToolchain): Boolean + fun createConfigurable(uuid: UUID, toolchain: AbstractZigToolchain, project: Project): NamedConfigurable + fun suggestToolchains(): List } -@Suppress("UNCHECKED_CAST") -private fun ZigToolchainProvider.serialize(toolchain: AbstractZigToolchain) = serialize(toolchain as T) - -class ZigToolchainConverter: Converter() { - override fun fromString(value: String): AbstractZigToolchain? { - val json = Json.parseToJsonElement(value) as? JsonObject ?: return null - return ZigToolchainProvider.fromJson(json) - } - - override fun toString(value: AbstractZigToolchain): String? { - return ZigToolchainProvider.toJson(value)?.toString() - } +fun AbstractZigToolchain.Ref.resolve(): AbstractZigToolchain? { + val marker = this.marker ?: return null + val data = this.data ?: return null + val provider = EXTENSION_POINT_NAME.extensionList.find { it.serialMarker == marker } ?: return null + return provider.deserialize(data) } -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) } } - } +fun AbstractZigToolchain.toRef(): AbstractZigToolchain.Ref { + val provider = EXTENSION_POINT_NAME.extensionList.find { it.isCompatible(this) } ?: throw IllegalStateException() + return AbstractZigToolchain.Ref(provider.serialMarker, provider.serialize(this)) +} - override fun toString(value: List): String { - return buildJsonArray { - value.mapNotNull { ZigToolchainProvider.toJson(it) }.forEach { - add(it) - } - }.toString() +suspend fun Project?.suggestZigToolchain(extraData: UserDataHolder): AbstractZigToolchain? { + return EXTENSION_POINT_NAME.extensionList.firstNotNullOfOrNull { it.suggestToolchain(this, extraData) } +} + +fun AbstractZigToolchain.createNamedConfigurable(uuid: UUID, project: Project): NamedConfigurable { + val provider = EXTENSION_POINT_NAME.extensionList.find { it.isCompatible(this) } ?: throw IllegalStateException() + return provider.createConfigurable(uuid, this, project) +} + +fun suggestZigToolchains(existing: List): List { + return EXTENSION_POINT_NAME.extensionList.flatMap { ext -> + val compatibleExisting = existing.filter { ext.isCompatible(it) } + val suggestions = ext.suggestToolchains() + suggestions.filter { suggestion -> compatibleExisting.none { existing -> ext.matchesSuggestion(existing, suggestion) } } } } \ No newline at end of file diff --git a/core/src/main/resources/META-INF/zigbrains-core.xml b/core/src/main/resources/META-INF/zigbrains-core.xml index ec6a4f81..b5ec5fe5 100644 --- a/core/src/main/resources/META-INF/zigbrains-core.xml +++ b/core/src/main/resources/META-INF/zigbrains-core.xml @@ -150,7 +150,6 @@ id="ZigToolchainConfigurable" displayName="Toolchain" /> - throwable.printStackTrace()