work work work

This commit is contained in:
FalsePattern 2025-04-06 02:25:22 +02:00
parent e737058cb5
commit 2c500d40a5
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
8 changed files with 412 additions and 81 deletions

View file

@ -32,6 +32,7 @@ import com.intellij.openapi.util.KeyWithDefaultValue
import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.io.toNioPathOrNull import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.openapi.vfs.toNioPathOrNull import com.intellij.openapi.vfs.toNioPathOrNull
import kotlinx.coroutines.runBlocking
import java.nio.file.Path import java.nio.file.Path
import java.util.UUID import java.util.UUID
import kotlin.io.path.pathString import kotlin.io.path.pathString
@ -71,10 +72,16 @@ data class LocalZigToolchain(val location: Path, val std: Path? = null, val name
} }
fun tryFromPath(path: Path): LocalZigToolchain? { fun tryFromPath(path: Path): LocalZigToolchain? {
val tc = LocalZigToolchain(path) var tc = LocalZigToolchain(path)
if (!tc.zig.fileValid()) { if (!tc.zig.fileValid()) {
return null return null
} }
tc.zig
.getEnvBlocking(null)
.getOrNull()
?.version
?.let { "Zig $it" }
?.let { tc = tc.copy(name = it) }
return tc return tc
} }
} }

View file

@ -30,8 +30,13 @@ import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.ui.configuration.SdkPopupBuilder import com.intellij.openapi.roots.ui.configuration.SdkPopupBuilder
import com.intellij.openapi.ui.NamedConfigurable import com.intellij.openapi.ui.NamedConfigurable
import com.intellij.openapi.util.UserDataHolder import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.toNioPathOrNull import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.openapi.util.text.StringUtil
import com.intellij.ui.SimpleColoredComponent
import com.intellij.ui.SimpleTextAttributes
import com.intellij.util.EnvironmentUtil import com.intellij.util.EnvironmentUtil
import com.intellij.util.IconUtil
import com.intellij.util.system.OS import com.intellij.util.system.OS
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
@ -98,4 +103,24 @@ class LocalZigToolchainProvider: ZigToolchainProvider {
EnvironmentUtil.getValue("PATH")?.split(File.pathSeparatorChar)?.let { res.addAll(it.toList()) } EnvironmentUtil.getValue("PATH")?.split(File.pathSeparatorChar)?.let { res.addAll(it.toList()) }
return res.mapNotNull { LocalZigToolchain.tryFromPathString(it) } return res.mapNotNull { LocalZigToolchain.tryFromPathString(it) }
} }
override fun render(toolchain: AbstractZigToolchain, component: SimpleColoredComponent) {
toolchain as LocalZigToolchain
component.append(presentDetectedPath(toolchain.location.pathString))
if (toolchain.name != null) {
component.append(" ")
component.append(toolchain.name, SimpleTextAttributes.GRAYED_ATTRIBUTES)
}
}
}
private fun presentDetectedPath(home: String, maxLength: Int = 50, suffixLength: Int = 30): String {
//for macOS, let's try removing Bundle internals
var home = home
home = StringUtil.trimEnd(home, "/Contents/Home") //NON-NLS
home = StringUtil.trimEnd(home, "/Contents/MacOS") //NON-NLS
home = FileUtil.getLocationRelativeToUserHome(home, false)
home = StringUtil.shortenTextWithEllipsis(home, maxLength, suffixLength)
return home
} }

View file

@ -22,111 +22,94 @@
package com.falsepattern.zigbrains.project.toolchain package com.falsepattern.zigbrains.project.toolchain
import com.falsepattern.zigbrains.Icons
import com.falsepattern.zigbrains.ZigBrainsBundle import com.falsepattern.zigbrains.ZigBrainsBundle
import com.intellij.ide.projectView.TreeStructureProvider import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
import com.intellij.ide.util.treeView.AbstractTreeStructure import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.ide.util.treeView.AbstractTreeStructureBase import com.intellij.icons.AllIcons
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.application.ModalityState
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.progress.coroutineToIndicator
import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.roots.ui.SdkAppearanceService import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.roots.ui.configuration.SdkListPresenter import com.intellij.openapi.ui.DialogBuilder
import com.intellij.openapi.roots.ui.configuration.SdkPopupFactory
import com.intellij.openapi.ui.MasterDetailsComponent import com.intellij.openapi.ui.MasterDetailsComponent
import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.util.Disposer
import com.intellij.openapi.ui.popup.util.BaseTreePopupStep import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.openapi.util.NlsContexts import com.intellij.platform.ide.progress.ModalTaskOwner
import com.intellij.ui.CollectionListModel import com.intellij.platform.ide.progress.TaskCancellation
import com.intellij.ui.ColoredListCellRenderer import com.intellij.platform.ide.progress.withModalProgress
import com.intellij.ui.SimpleTextAttributes import com.intellij.platform.util.progress.withProgressText
import com.intellij.ui.components.JBLabel import com.intellij.ui.*
import com.intellij.ui.components.JBPanel import com.intellij.ui.components.JBList
import com.intellij.ui.components.panels.HorizontalLayout import com.intellij.ui.components.panels.OpaquePanel
import com.intellij.ui.components.textFieldWithBrowseButton
import com.intellij.ui.dsl.builder.Align
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.Cell
import com.intellij.ui.dsl.builder.panel 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.popup.list.ComboBoxPopup
import com.intellij.ui.treeStructure.SimpleNode import com.intellij.util.Consumer
import com.intellij.ui.treeStructure.SimpleTreeStructure
import com.intellij.util.IconUtil import com.intellij.util.IconUtil
import com.intellij.util.download.DownloadableFileService
import com.intellij.util.system.CpuArch
import com.intellij.util.text.SemVer
import com.intellij.util.ui.EmptyIcon import com.intellij.util.ui.EmptyIcon
import com.intellij.util.ui.UIUtil.FontColor import com.intellij.util.ui.JBUI
import com.intellij.util.ui.UIUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.decodeFromStream
import java.awt.BorderLayout
import java.awt.Component import java.awt.Component
import java.awt.LayoutManager import java.nio.file.Path
import java.util.* import java.util.*
import java.util.function.Consumer import javax.accessibility.AccessibleContext
import javax.swing.AbstractListModel import javax.swing.DefaultComboBoxModel
import javax.swing.BoxLayout
import javax.swing.DefaultListModel
import javax.swing.JComponent import javax.swing.JComponent
import javax.swing.JList import javax.swing.JList
import javax.swing.JPanel import javax.swing.JPanel
import javax.swing.ListCellRenderer import javax.swing.ListCellRenderer
import javax.swing.ListModel import javax.swing.border.Border
import javax.swing.SwingConstants import javax.swing.event.DocumentEvent
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
private var myComponent: JComponent? = null
override fun createComponent(): JComponent { override fun createComponent(): JComponent {
if (!isTreeInitialized) { if (!isTreeInitialized) {
initTree() initTree()
isTreeInitialized = true isTreeInitialized = true
} }
return super.createComponent() val comp = super.createComponent()
myComponent = comp
return comp
} }
class ToolchainContext(private val project: Project?, private val model: ListModel<Any>): ComboBoxPopup.Context<Any> { override fun createActions(fromPopup: Boolean): List<AnAction> {
override fun getProject(): Project? { val add = object : DumbAwareAction({ "lmaoo" }, Presentation.NULL_STRING, IconUtil.addIcon) {
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?>? {
val add = object : DumbAwareAction({"lmaoo"}, Presentation.NULL_STRING, IconUtil.addIcon) {
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
val toolchains = suggestZigToolchains(zigToolchainList.toolchains.map { it.second }.toList()) val toolchains = suggestZigToolchains(zigToolchainList.toolchains.map { it.second }.toList())
val final = ArrayList<Any>() val final = ArrayList<TCListElemIn>()
final.addAll(toolchains) final.add(TCListElem.Download)
val popup = ToolchainPopup(ToolchainContext(null, CollectionListModel(final)), null, {}) final.add(TCListElem.FromDisk)
final.add(Separator("Detected toolchains", true))
final.addAll(toolchains.map { TCListElem.Toolchain(it) })
val model = TCModel(final)
val context = TCContext(null, model)
val popup = TCPopup(context, null, ::onItemSelected)
popup.showInBestPositionFor(e.dataContext) popup.showInBestPositionFor(e.dataContext)
} }
} }
@ -140,6 +123,61 @@ class ZigToolchainListEditor(): MasterDetailsComponent() {
super.onItemDeleted(item) super.onItemDeleted(item)
} }
private fun onItemSelected(elem: TCListElem) {
when (elem) {
is TCListElem.Toolchain -> {
val uuid = UUID.randomUUID()
zigToolchainList.setToolchain(uuid, elem.toolchain)
addToolchain(uuid, elem.toolchain)
(myTree.model as DefaultTreeModel).reload()
}
is TCListElem.Download -> {
zigCoroutineScope.async {
withEDTContext(ModalityState.stateForComponent(myComponent!!)) {
val info = withModalProgress(myComponent?.let { ModalTaskOwner.component(it) } ?: ModalTaskOwner.guess(), "Fetching zig version information", TaskCancellation.cancellable()) {
ZigVersionInfo.download()
}
val dialog = DialogBuilder()
val theList = ComboBox<String>(DefaultComboBoxModel(info.map { it.first }.toTypedArray()))
val outputPath = textFieldWithBrowseButton(
null,
FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.zig-toolchain"))
).also {
Disposer.register(dialog, it)
}
var archiveSizeCell: Cell<*>? = null
fun detect(item: String) {
outputPath.text = System.getProperty("user.home") + "/.zig/" + item
val data = info.firstOrNull { it.first == item } ?: return
val size = data.second.dist.size
val sizeMb = size / (1024f * 1024f)
archiveSizeCell?.comment?.text = "Archive size: %.2fMB".format(sizeMb)
}
theList.addItemListener {
detect(it.item as String)
}
val center = panel {
row("Version:") {
cell(theList).resizableColumn().align(AlignX.FILL)
}
row("Location:") {
cell(outputPath).resizableColumn().align(AlignX.FILL).apply { archiveSizeCell = comment("") }
}
}
detect(info[0].first)
dialog.centerPanel(center)
dialog.setTitle("Version Selector")
dialog.addCancelAction()
dialog.showAndGet()
}
}
}
is TCListElem.FromDisk -> {}
}
}
override fun reset() { override fun reset() {
reloadTree() reloadTree()
super.reset() super.reset()
@ -149,20 +187,160 @@ class ZigToolchainListEditor(): MasterDetailsComponent() {
override fun getDisplayName() = ZigBrainsBundle.message("settings.toolchains.title") override fun getDisplayName() = ZigBrainsBundle.message("settings.toolchains.title")
private fun addLocalToolchain(uuid: UUID, toolchain: LocalZigToolchain) { private fun addToolchain(uuid: UUID, toolchain: AbstractZigToolchain) {
val node = MyNode(LocalZigToolchainConfigurable(uuid, toolchain, ProjectManager.getInstance().defaultProject)) val node = MyNode(toolchain.createNamedConfigurable(uuid, ProjectManager.getInstance().defaultProject))
addNode(node, myRoot) addNode(node, myRoot)
} }
private fun reloadTree() { private fun reloadTree() {
myRoot.removeAllChildren() myRoot.removeAllChildren()
zigToolchainList.toolchains.forEach { (uuid, toolchain) -> zigToolchainList.toolchains.forEach { (uuid, toolchain) ->
if (toolchain is LocalZigToolchain) { addToolchain(uuid, toolchain)
addLocalToolchain(uuid, toolchain)
}
} }
(myTree.model as DefaultTreeModel).reload() (myTree.model as DefaultTreeModel).reload()
} }
override fun disposeUIResources() {
super.disposeUIResources()
myComponent = null
}
}
private sealed interface TCListElemIn
private sealed interface TCListElem : TCListElemIn {
@JvmRecord
data class Toolchain(val toolchain: AbstractZigToolchain) : TCListElem
object Download : TCListElem
object FromDisk : TCListElem
}
@JvmRecord
private data class Separator(val text: String, val separatorBar: Boolean) : TCListElemIn
private class TCPopup(
context: TCContext,
selected: TCListElem?,
onItemSelected: Consumer<TCListElem>,
) : ComboBoxPopup<TCListElem>(context, selected, onItemSelected)
private class TCModel private constructor(elements: List<TCListElem>, private val separators: Map<TCListElem, Separator>) : CollectionListModel<TCListElem>(elements) {
companion object {
operator fun invoke(input: List<TCListElemIn>): TCModel {
val separators = IdentityHashMap<TCListElem, Separator>()
var lastSeparator: Separator? = null
val elements = ArrayList<TCListElem>()
input.forEach {
when (it) {
is TCListElem -> {
if (lastSeparator != null) {
separators[it] = lastSeparator
lastSeparator = null
}
elements.add(it)
}
is Separator -> lastSeparator = it
}
}
val model = TCModel(elements, separators)
return model
}
}
fun separatorAbove(elem: TCListElem) = separators[elem]
}
private class TCContext(private val project: Project?, private val model: TCModel) : ComboBoxPopup.Context<TCListElem> {
override fun getProject(): Project? {
return project
}
override fun getModel(): TCModel {
return model
}
override fun getRenderer(): ListCellRenderer<in TCListElem> {
return TCCellRenderer(::getModel)
}
}
private class TCCellRenderer(val getModel: () -> TCModel) : ColoredListCellRenderer<TCListElem>() {
override fun getListCellRendererComponent(
list: JList<out TCListElem?>?,
value: TCListElem?,
index: Int,
selected: Boolean,
hasFocus: Boolean
): Component? {
val component = super.getListCellRendererComponent(list, value, index, selected, hasFocus) as SimpleColoredComponent
val panel = object : CellRendererPanel(BorderLayout()) {
val myContext = component.accessibleContext
override fun getAccessibleContext(): AccessibleContext? {
return myContext
}
override fun setBorder(border: Border?) {
component.border = border
}
}
panel.add(component, BorderLayout.CENTER)
component.isOpaque = true
list?.let { background = if (selected) it.selectionBackground else it.background }
val model = getModel()
val separator = value?.let { model.separatorAbove(it) }
if (separator != null) {
val separatorText = separator.text
val vGap = if (UIUtil.isUnderNativeMacLookAndFeel()) 1 else 3
val separatorComponent = GroupHeaderSeparator(JBUI.insets(vGap, 10, vGap, 0))
separatorComponent.isHideLine = !separator.separatorBar
if (separatorText.isNotBlank()) {
separatorComponent.caption = separatorText
}
val wrapper = OpaquePanel(BorderLayout())
wrapper.add(separatorComponent, BorderLayout.CENTER)
list?.let { wrapper.background = it.background }
panel.add(wrapper, BorderLayout.NORTH)
}
return panel
}
override fun customizeCellRenderer(
list: JList<out TCListElem?>,
value: TCListElem?,
index: Int,
selected: Boolean,
hasFocus: Boolean
) {
icon = EMPTY_ICON
when (value) {
is TCListElem.Toolchain -> {
icon = Icons.Zig
val toolchain = value.toolchain
toolchain.render(this)
}
is TCListElem.Download -> {
icon = AllIcons.Actions.Download
append("Download Zig\u2026")
}
is TCListElem.FromDisk -> {
icon = AllIcons.General.OpenDisk
append("Add Zig from disk\u2026")
}
null -> {}
}
}
} }
private val EMPTY_ICON = EmptyIcon.create(1, 16) private val EMPTY_ICON = EmptyIcon.create(1, 16)

View file

@ -63,7 +63,6 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
data class State( data class State(
@JvmField @JvmField
@MapAnnotation(surroundKeyWithTag = false, surroundValueWithTag = false)
val toolchains: Map<String, AbstractZigToolchain.Ref> = emptyMap(), val toolchains: Map<String, AbstractZigToolchain.Ref> = emptyMap(),
) )
} }

View file

@ -26,6 +26,7 @@ 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.ui.NamedConfigurable
import com.intellij.openapi.util.UserDataHolder import com.intellij.openapi.util.UserDataHolder
import com.intellij.ui.SimpleColoredComponent
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
@ -46,6 +47,7 @@ sealed interface ZigToolchainProvider {
fun matchesSuggestion(toolchain: AbstractZigToolchain, suggestion: AbstractZigToolchain): Boolean fun matchesSuggestion(toolchain: AbstractZigToolchain, suggestion: AbstractZigToolchain): Boolean
fun createConfigurable(uuid: UUID, toolchain: AbstractZigToolchain, project: Project): NamedConfigurable<UUID> fun createConfigurable(uuid: UUID, toolchain: AbstractZigToolchain, project: Project): NamedConfigurable<UUID>
fun suggestToolchains(): List<AbstractZigToolchain> fun suggestToolchains(): List<AbstractZigToolchain>
fun render(toolchain: AbstractZigToolchain, component: SimpleColoredComponent)
} }
fun AbstractZigToolchain.Ref.resolve(): AbstractZigToolchain? { fun AbstractZigToolchain.Ref.resolve(): AbstractZigToolchain? {
@ -76,3 +78,8 @@ fun suggestZigToolchains(existing: List<AbstractZigToolchain>): List<AbstractZig
suggestions.filter { suggestion -> compatibleExisting.none { existing -> ext.matchesSuggestion(existing, suggestion) } } suggestions.filter { suggestion -> compatibleExisting.none { existing -> ext.matchesSuggestion(existing, suggestion) } }
} }
} }
fun AbstractZigToolchain.render(component: SimpleColoredComponent) {
val provider = EXTENSION_POINT_NAME.extensionList.find { it.isCompatible(this) } ?: throw IllegalStateException()
return provider.render(this, component)
}

View file

@ -0,0 +1,106 @@
/*
* 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.openapi.application.PathManager
import com.intellij.openapi.progress.coroutineToIndicator
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.platform.util.progress.withProgressText
import com.intellij.util.asSafely
import com.intellij.util.download.DownloadableFileService
import com.intellij.util.system.CpuArch
import com.intellij.util.system.OS
import com.intellij.util.text.SemVer
import com.jetbrains.rd.util.firstOrNull
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.decodeFromStream
import java.nio.file.Path
@JvmRecord
data class ZigVersionInfo(val date: String, val docs: String, val notes: String, val src: Tarball?, val dist: Tarball) {
companion object {
@OptIn(ExperimentalSerializationApi::class)
suspend fun download(): List<Pair<String, ZigVersionInfo>> {
return withProgressText("Fetching zig version information") {
withContext(Dispatchers.IO) {
val service = DownloadableFileService.getInstance()
val desc = service.createFileDescription("https://ziglang.org/download/index.json", "index.json")
val downloader = service.createDownloader(listOf(desc), "Zig version information downloading")
val downloadDirectory = tempPluginDir.toFile()
val downloadResults = coroutineToIndicator {
downloader.download(downloadDirectory)
}
var info: JsonObject? = null
for (result in downloadResults) {
if (result.second.defaultFileName == "index.json") {
info = result.first.inputStream().use { Json.decodeFromStream<JsonObject>(it) }
}
}
info?.mapNotNull getVersions@{ (version, data) ->
data as? JsonObject ?: return@getVersions null
val date = data["date"]?.asSafely<JsonPrimitive>()?.content ?: ""
val docs = data["docs"]?.asSafely<JsonPrimitive>()?.content ?: ""
val notes = data["notes"]?.asSafely<JsonPrimitive>()?.content ?: ""
val src = data["src"]?.asSafely<JsonObject>()?.let { Json.decodeFromJsonElement<Tarball>(it) }
val dist = data.firstNotNullOfOrNull findCompatible@{ (dist, tb) ->
if (!dist.contains('-'))
return@findCompatible null
val (arch, os) = dist.split('-', limit = 2)
val theArch = when(arch) {
"x86_64" -> CpuArch.X86_64
"i386" -> CpuArch.X86
"armv7a" -> CpuArch.ARM32
"aarch64" -> CpuArch.ARM64
else -> return@findCompatible null
}
val theOS = when(os) {
"linux" -> OS.Linux
"windows" -> OS.Windows
"macos" -> OS.macOS
"freebsd" -> OS.FreeBSD
else -> return@findCompatible null
}
if (theArch == CpuArch.CURRENT && theOS == OS.CURRENT) {
Json.decodeFromJsonElement<Tarball>(tb)
} else null
} ?: return@getVersions null
Pair(version, ZigVersionInfo(date, docs, notes, src, dist))
} ?.toList() ?: emptyList()
}
}
}
}
}
@JvmRecord
@Serializable
data class Tarball(val tarball: String, val shasum: String, val size: Int)
private val tempPluginDir get(): Path = PathManager.getTempPath().toNioPathOrNull()!!.resolve("zigbrains")

View file

@ -25,6 +25,7 @@ package com.falsepattern.zigbrains.project.toolchain.tools
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainEnvironmentSerializable import com.falsepattern.zigbrains.project.toolchain.ZigToolchainEnvironmentSerializable
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.nio.file.Path import java.nio.file.Path
@ -45,6 +46,8 @@ class ZigCompilerTool(toolchain: AbstractZigToolchain) : ZigTool(toolchain) {
Result.failure(IllegalStateException("could not deserialize zig env", e)) Result.failure(IllegalStateException("could not deserialize zig env", e))
} }
} }
fun getEnvBlocking(project: Project?) = runBlocking { getEnv(project) }
} }
private val envJson = Json { private val envJson = Json {

View file

@ -49,6 +49,12 @@ suspend inline fun <T> withCurrentEDTModalityContext(noinline block: suspend Cor
} }
} }
fun <T, R> T.letBlocking(targetAction: suspend CoroutineScope.(T) -> R): R {
return runBlocking {
targetAction(this@letBlocking)
}
}
suspend inline fun <T> runInterruptibleEDT(state: ModalityState, noinline targetAction: () -> T): T { suspend inline fun <T> runInterruptibleEDT(state: ModalityState, noinline targetAction: () -> T): T {
return runInterruptible(Dispatchers.EDT + state.asContextElement(), block = targetAction) return runInterruptible(Dispatchers.EDT + state.asContextElement(), block = targetAction)
} }