more progress

This commit is contained in:
FalsePattern 2025-04-05 19:46:48 +02:00
parent f53b0e3283
commit e737058cb5
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
14 changed files with 246 additions and 244 deletions

View file

@ -26,6 +26,7 @@ import com.falsepattern.zigbrains.direnv.DirenvCmd
import com.falsepattern.zigbrains.project.settings.zigProjectSettings import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider
import com.falsepattern.zigbrains.project.toolchain.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
@ -80,7 +81,7 @@ class ZBStartup: ProjectActivity {
data.putUserData(LocalZigToolchain.DIRENV_KEY, data.putUserData(LocalZigToolchain.DIRENV_KEY,
DirenvCmd.direnvInstalled() && !project.isDefault && zigProjectState.direnv DirenvCmd.direnvInstalled() && !project.isDefault && zigProjectState.direnv
) )
val tc = ZigToolchainProvider.suggestToolchain(project, data) ?: return val tc = project.suggestZigToolchain(data) ?: return
if (tc is LocalZigToolchain) { if (tc is LocalZigToolchain) {
zigProjectState.toolchainPath = tc.location.pathString zigProjectState.toolchainPath = tc.location.pathString
project.zigProjectSettings.state = zigProjectState project.zigProjectSettings.state = zigProjectState

View file

@ -26,6 +26,7 @@ import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.direnv.DirenvCmd import com.falsepattern.zigbrains.direnv.DirenvCmd
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider 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.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
@ -107,7 +108,7 @@ class ZigProjectSettingsPanel(private val holder: ZigProjectConfigurationProvide
return return
val data = UserDataHolderBase() val data = UserDataHolderBase()
data.putUserData(LocalZigToolchain.DIRENV_KEY, !project.isDefault && direnv.isSelected && DirenvCmd.direnvInstalled()) 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) { if (tc !is LocalZigToolchain) {
TODO("Implement non-local zig toolchain in config") TODO("Implement non-local zig toolchain in config")
} }

View file

@ -25,7 +25,12 @@ package com.falsepattern.zigbrains.project.toolchain
import com.falsepattern.zigbrains.project.toolchain.tools.ZigCompilerTool import com.falsepattern.zigbrains.project.toolchain.tools.ZigCompilerTool
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.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.nio.file.Path
import java.util.UUID
abstract class AbstractZigToolchain { abstract class AbstractZigToolchain {
@ -36,4 +41,13 @@ abstract class AbstractZigToolchain {
abstract suspend fun patchCommandLine(commandLine: GeneralCommandLine, project: Project? = null): GeneralCommandLine abstract suspend fun patchCommandLine(commandLine: GeneralCommandLine, project: Project? = null): GeneralCommandLine
abstract fun pathToExecutable(toolName: String, project: Project? = null): Path 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<String, String>? = null,
)
} }

View file

@ -33,8 +33,10 @@ 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 java.nio.file.Path 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? { override fun workingDirectory(project: Project?): Path? {
return project?.guessProjectDir()?.toNioPathOrNull() return project?.guessProjectDir()?.toNioPathOrNull()
} }

View file

@ -25,22 +25,28 @@ package com.falsepattern.zigbrains.project.toolchain
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.NamedConfigurable import com.intellij.openapi.ui.NamedConfigurable
import com.intellij.openapi.util.NlsContexts import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.NlsSafe
import com.intellij.ui.dsl.builder.panel import com.intellij.ui.dsl.builder.panel
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.util.UUID
import javax.swing.JComponent import javax.swing.JComponent
class LocalZigToolchainConfigurable( class LocalZigToolchainConfigurable(
val toolchain: LocalZigToolchain, val uuid: UUID,
toolchain: LocalZigToolchain,
private val project: Project private val project: Project
): NamedConfigurable<LocalZigToolchain>() { ): NamedConfigurable<UUID>() {
var toolchain: LocalZigToolchain = toolchain
set(value) {
zigToolchainList.setToolchain(uuid, value)
field = value
}
private var myView: LocalZigToolchainPanel? = null private var myView: LocalZigToolchainPanel? = null
override fun setDisplayName(name: @NlsSafe String?) { override fun setDisplayName(name: String?) {
toolchain.name = name toolchain = toolchain.copy(name = name)
} }
override fun getEditableObject(): LocalZigToolchain? { override fun getEditableObject(): UUID {
return toolchain return uuid
} }
override fun getBannerSlogan(): @NlsContexts.DetailedDescription String? { override fun getBannerSlogan(): @NlsContexts.DetailedDescription String? {
@ -65,7 +71,7 @@ class LocalZigToolchainConfigurable(
val version = toolchain.zig.let { runBlocking { it.getEnv(project) } }.getOrNull()?.version val version = toolchain.zig.let { runBlocking { it.getEnv(project) } }.getOrNull()?.version
if (version != null) { if (version != null) {
theName = "Zig $version" theName = "Zig $version"
toolchain.name = theName toolchain = toolchain.copy(name = theName)
} }
} }
return theName return theName

View file

@ -101,12 +101,10 @@ class LocalZigToolchainPanel() : Disposable {
} }
fun apply(cfg: LocalZigToolchainConfigurable): Boolean { fun apply(cfg: LocalZigToolchainConfigurable): Boolean {
cfg.displayName = nameField.text
val tc = cfg.toolchain val tc = cfg.toolchain
val location = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull() ?: return false val location = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull() ?: return false
val std = if (stdFieldOverride.isSelected) pathToStd.text.ifBlank { null }?.toNioPathOrNull() else null val std = if (stdFieldOverride.isSelected) pathToStd.text.ifBlank { null }?.toNioPathOrNull() else null
tc.location = location cfg.toolchain = tc.copy(location = location, std = std, name = nameField.text ?: "")
tc.std = std
return true return true
} }

View file

@ -26,12 +26,22 @@ import com.falsepattern.zigbrains.direnv.DirenvCmd
import com.falsepattern.zigbrains.direnv.emptyEnv import com.falsepattern.zigbrains.direnv.emptyEnv
import com.falsepattern.zigbrains.project.settings.zigProjectSettings import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.intellij.openapi.project.Project 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.UserDataHolder
import com.intellij.openapi.util.io.toNioPathOrNull 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 kotlinx.serialization.json.*
import java.io.File
import java.util.UUID
import kotlin.io.path.pathString import kotlin.io.path.pathString
class LocalZigToolchainProvider: ZigToolchainProvider<LocalZigToolchain> { class LocalZigToolchainProvider: ZigToolchainProvider {
override suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): LocalZigToolchain? { override suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): LocalZigToolchain? {
val env = if (project != null && (extraData.getUserData(LocalZigToolchain.DIRENV_KEY) ?: project.zigProjectSettings.state.direnv)) { val env = if (project != null && (extraData.getUserData(LocalZigToolchain.DIRENV_KEY) ?: project.zigProjectSettings.state.direnv)) {
DirenvCmd.importDirenv(project) DirenvCmd.importDirenv(project)
@ -45,22 +55,47 @@ class LocalZigToolchainProvider: ZigToolchainProvider<LocalZigToolchain> {
override val serialMarker: String override val serialMarker: String
get() = "local" get() = "local"
override fun deserialize(data: JsonElement): LocalZigToolchain? { override fun deserialize(data: Map<String, String>): AbstractZigToolchain? {
if (data !is JsonObject) val location = data["location"]?.toNioPathOrNull() ?: return null
return null val std = data["std"]?.toNioPathOrNull()
val name = data["name"]
val loc = data["location"] as? JsonPrimitive ?: return null return LocalZigToolchain(location, std, name)
val path = loc.content.toNioPathOrNull() ?: return null
return LocalZigToolchain(path)
} }
override fun canSerialize(toolchain: AbstractZigToolchain): Boolean { override fun isCompatible(toolchain: AbstractZigToolchain): Boolean {
return toolchain is LocalZigToolchain return toolchain is LocalZigToolchain
} }
override fun serialize(toolchain: LocalZigToolchain): JsonElement { override fun serialize(toolchain: AbstractZigToolchain): Map<String, String> {
return buildJsonObject { toolchain as LocalZigToolchain
put("location", toolchain.location.pathString) val map = HashMap<String, String>()
} 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<UUID> {
toolchain as LocalZigToolchain
return LocalZigToolchainConfigurable(uuid, toolchain, project)
}
override fun suggestToolchains(): List<AbstractZigToolchain> {
val res = HashSet<String>()
EnvironmentUtil.getValue("PATH")?.split(File.pathSeparatorChar)?.let { res.addAll(it.toList()) }
return res.mapNotNull { LocalZigToolchain.tryFromPathString(it) }
} }
} }

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<String?> {
val res = HashSet<String>()
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"
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.toolchain
import com.intellij.util.xmlb.annotations.OptionTag
data class ZigToolchainList(
@get:OptionTag(converter = ZigToolchainListConverter::class)
var toolchains: List<AbstractZigToolchain> = emptyList(),
)

View file

@ -23,17 +23,51 @@
package com.falsepattern.zigbrains.project.toolchain package com.falsepattern.zigbrains.project.toolchain
import com.falsepattern.zigbrains.ZigBrainsBundle 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.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.Presentation import com.intellij.openapi.actionSystem.Presentation
import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager 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.roots.ui.configuration.SdkPopupFactory
import com.intellij.openapi.ui.MasterDetailsComponent 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.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.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 javax.swing.tree.DefaultTreeModel
import kotlin.io.path.pathString
class ZigToolchainListEditor(): MasterDetailsComponent() { class ZigToolchainListEditor(): MasterDetailsComponent() {
private var isTreeInitialized = false private var isTreeInitialized = false
@ -46,29 +80,62 @@ class ZigToolchainListEditor(): MasterDetailsComponent() {
return super.createComponent() return super.createComponent()
} }
class ToolchainContext(private val project: Project?, private val model: ListModel<Any>): ComboBoxPopup.Context<Any> {
override fun getProject(): Project? {
return project
}
override fun getModel(): ListModel<Any> {
return model
}
override fun getRenderer(): ListCellRenderer<in Any> {
return object: ColoredListCellRenderer<Any>() {
override fun customizeCellRenderer(
list: JList<out Any?>,
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<Any>
): ComboBoxPopup<Any>(context, selected, onItemSelected) {
}
override fun createActions(fromPopup: Boolean): List<AnAction?>? { override fun createActions(fromPopup: Boolean): List<AnAction?>? {
val add = object : DumbAwareAction({"lmaoo"}, Presentation.NULL_STRING, IconUtil.addIcon) { val add = object : DumbAwareAction({"lmaoo"}, Presentation.NULL_STRING, IconUtil.addIcon) {
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
SdkPopupFactory val toolchains = suggestZigToolchains(zigToolchainList.toolchains.map { it.second }.toList())
.newBuilder() val final = ArrayList<Any>()
.withSdkTypeFilter { it is ZigSDKType } final.addAll(toolchains)
.onSdkSelected { val popup = ToolchainPopup(ToolchainContext(null, CollectionListModel(final)), null, {})
val path = it.homePath?.toNioPathOrNull() ?: return@onSdkSelected popup.showInBestPositionFor(e.dataContext)
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()) return listOf(add, MyDeleteAction())
} }
override fun onItemDeleted(item: Any?) { override fun onItemDeleted(item: Any?) {
if (item is AbstractZigToolchain) { if (item is UUID) {
zigToolchainList.state = ZigToolchainList(zigToolchainList.state.toolchains.filter { it != item }) zigToolchainList.removeToolchain(item)
} }
super.onItemDeleted(item) super.onItemDeleted(item)
} }
@ -82,18 +149,20 @@ class ZigToolchainListEditor(): MasterDetailsComponent() {
override fun getDisplayName() = ZigBrainsBundle.message("settings.toolchains.title") override fun getDisplayName() = ZigBrainsBundle.message("settings.toolchains.title")
private fun addLocalToolchain(toolchain: LocalZigToolchain) { private fun addLocalToolchain(uuid: UUID, toolchain: LocalZigToolchain) {
val node = MyNode(LocalZigToolchainConfigurable(toolchain, ProjectManager.getInstance().defaultProject)) val node = MyNode(LocalZigToolchainConfigurable(uuid, toolchain, ProjectManager.getInstance().defaultProject))
addNode(node, myRoot) addNode(node, myRoot)
} }
private fun reloadTree() { private fun reloadTree() {
myRoot.removeAllChildren() myRoot.removeAllChildren()
zigToolchainList.state.toolchains.forEach { toolchain -> zigToolchainList.toolchains.forEach { (uuid, toolchain) ->
if (toolchain is LocalZigToolchain) { if (toolchain is LocalZigToolchain) {
addLocalToolchain(toolchain) addLocalToolchain(uuid, toolchain)
} }
} }
(myTree.model as DefaultTreeModel).reload() (myTree.model as DefaultTreeModel).reload()
} }
} }
private val EMPTY_ICON = EmptyIcon.create(1, 16)

View file

@ -22,37 +22,50 @@
package com.falsepattern.zigbrains.project.toolchain 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.components.*
import com.intellij.openapi.project.Project import com.intellij.util.xmlb.annotations.MapAnnotation
import kotlinx.coroutines.launch import java.util.UUID
@Service(Service.Level.APP) @Service(Service.Level.APP)
@State( @State(
name = "ZigProjectSettings", name = "ZigToolchainList",
storages = [Storage("zigbrains.xml")] storages = [Storage("zigbrains.xml")]
) )
class ZigToolchainListService(): PersistentStateComponent<ZigToolchainList> { class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchainListService.State>(State()) {
@Volatile fun setToolchain(uuid: UUID, toolchain: AbstractZigToolchain) {
private var state = ZigToolchainList() updateState {
val newMap = HashMap<String, AbstractZigToolchain.Ref>()
override fun getState(): ZigToolchainList { newMap.putAll(it.toolchains)
return state.copy() newMap.put(uuid.toString(), toolchain.toRef())
it.copy(toolchains = newMap)
}
} }
fun setState(value: ZigToolchainList) { fun getToolchain(uuid: UUID): AbstractZigToolchain? {
this.state = value return state.toolchains[uuid.toString()]?.resolve()
} }
override fun loadState(state: ZigToolchainList) { fun removeToolchain(uuid: UUID) {
setState(state) val str = uuid.toString()
updateState {
it.copy(toolchains = it.toolchains.filter { it.key != str })
}
} }
fun isModified(otherData: ZigToolchainList): Boolean { val toolchains: Sequence<Pair<UUID, AbstractZigToolchain>>
return state != otherData 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<String, AbstractZigToolchain.Ref> = emptyMap(),
)
} }
val zigToolchainList get() = service<ZigToolchainListService>() val zigToolchainList get() = service<ZigToolchainListService>()

View file

@ -22,71 +22,57 @@
package com.falsepattern.zigbrains.project.toolchain 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.extensions.ExtensionPointName
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.NamedConfigurable
import com.intellij.openapi.util.UserDataHolder import com.intellij.openapi.util.UserDataHolder
import com.intellij.util.asSafely import kotlinx.coroutines.flow.Flow
import com.intellij.util.xmlb.Converter import kotlinx.coroutines.flow.asFlow
import kotlinx.serialization.json.* 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<in T: AbstractZigToolchain> { private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigToolchainProvider>("com.falsepattern.zigbrains.toolchainProvider")
sealed interface ZigToolchainProvider {
suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): AbstractZigToolchain? suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): AbstractZigToolchain?
val serialMarker: String val serialMarker: String
fun deserialize(data: JsonElement): AbstractZigToolchain? fun isCompatible(toolchain: AbstractZigToolchain): Boolean
fun canSerialize(toolchain: AbstractZigToolchain): Boolean fun deserialize(data: Map<String, String>): AbstractZigToolchain?
fun serialize(toolchain: T): JsonElement fun serialize(toolchain: AbstractZigToolchain): Map<String, String>
fun matchesSuggestion(toolchain: AbstractZigToolchain, suggestion: AbstractZigToolchain): Boolean
companion object { fun createConfigurable(uuid: UUID, toolchain: AbstractZigToolchain, project: Project): NamedConfigurable<UUID>
val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigToolchainProvider<*>>("com.falsepattern.zigbrains.toolchainProvider") fun suggestToolchains(): List<AbstractZigToolchain>
suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): AbstractZigToolchain? {
return EXTENSION_POINT_NAME.extensionList.firstNotNullOfOrNull { it.suggestToolchain(project, extraData) }
} }
fun fromJson(json: JsonObject): AbstractZigToolchain? { fun AbstractZigToolchain.Ref.resolve(): AbstractZigToolchain? {
val marker = (json["marker"] as? JsonPrimitive)?.contentOrNull ?: return null val marker = this.marker ?: return null
val data = json["data"] ?: return null val data = this.data ?: return null
val provider = EXTENSION_POINT_NAME.extensionList.find { it.serialMarker == marker } ?: return null val provider = EXTENSION_POINT_NAME.extensionList.find { it.serialMarker == marker } ?: return null
return provider.deserialize(data) return provider.deserialize(data)
} }
fun toJson(tc: AbstractZigToolchain): JsonObject? { fun AbstractZigToolchain.toRef(): AbstractZigToolchain.Ref {
val provider = EXTENSION_POINT_NAME.extensionList.find { it.canSerialize(tc) } ?: return null val provider = EXTENSION_POINT_NAME.extensionList.find { it.isCompatible(this) } ?: throw IllegalStateException()
return buildJsonObject { return AbstractZigToolchain.Ref(provider.serialMarker, provider.serialize(this))
put("marker", provider.serialMarker)
put("data", provider.serialize(tc))
}
}
}
} }
@Suppress("UNCHECKED_CAST") suspend fun Project?.suggestZigToolchain(extraData: UserDataHolder): AbstractZigToolchain? {
private fun <T: AbstractZigToolchain> ZigToolchainProvider<T>.serialize(toolchain: AbstractZigToolchain) = serialize(toolchain as T) return EXTENSION_POINT_NAME.extensionList.firstNotNullOfOrNull { it.suggestToolchain(this, extraData) }
class ZigToolchainConverter: Converter<AbstractZigToolchain>() {
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? { fun AbstractZigToolchain.createNamedConfigurable(uuid: UUID, project: Project): NamedConfigurable<UUID> {
return ZigToolchainProvider.toJson(value)?.toString() val provider = EXTENSION_POINT_NAME.extensionList.find { it.isCompatible(this) } ?: throw IllegalStateException()
} return provider.createConfigurable(uuid, this, project)
} }
class ZigToolchainListConverter: Converter<List<AbstractZigToolchain>>() { fun suggestZigToolchains(existing: List<AbstractZigToolchain>): List<AbstractZigToolchain> {
override fun fromString(value: String): List<AbstractZigToolchain> { return EXTENSION_POINT_NAME.extensionList.flatMap { ext ->
val json = Json.parseToJsonElement(value) as? JsonArray ?: return emptyList() val compatibleExisting = existing.filter { ext.isCompatible(it) }
return json.mapNotNull { it.asSafely<JsonObject>()?.let { ZigToolchainProvider.fromJson(it) } } val suggestions = ext.suggestToolchains()
} suggestions.filter { suggestion -> compatibleExisting.none { existing -> ext.matchesSuggestion(existing, suggestion) } }
override fun toString(value: List<AbstractZigToolchain>): String {
return buildJsonArray {
value.mapNotNull { ZigToolchainProvider.toJson(it) }.forEach {
add(it)
}
}.toString()
} }
} }

View file

@ -150,7 +150,6 @@
id="ZigToolchainConfigurable" id="ZigToolchainConfigurable"
displayName="Toolchain" displayName="Toolchain"
/> />
<sdkType implementation="com.falsepattern.zigbrains.project.toolchain.ZigSDKType"/>
<programRunner <programRunner
implementation="com.falsepattern.zigbrains.project.run.ZigRegularRunner" implementation="com.falsepattern.zigbrains.project.run.ZigRegularRunner"

View file

@ -37,7 +37,7 @@ class ToolchainZLSConfigProvider: SuspendingZLSConfigProvider {
override suspend fun getEnvironment(project: Project, previous: ZLSConfig): ZLSConfig { override suspend fun getEnvironment(project: Project, previous: ZLSConfig): ZLSConfig {
val svc = project.zigProjectSettings val svc = project.zigProjectSettings
var state = svc.state var state = svc.state
val toolchain = state.toolchain ?: ZigToolchainProvider.suggestToolchain(project, UserDataHolderBase()) ?: return previous val toolchain = state.toolchain ?: project.suggestZigToolchain(UserDataHolderBase()) ?: return previous
val env = toolchain.zig.getEnv(project).getOrElse { throwable -> val env = toolchain.zig.getEnv(project).getOrElse { throwable ->
throwable.printStackTrace() throwable.printStackTrace()