work work work
This commit is contained in:
parent
e737058cb5
commit
2c500d40a5
8 changed files with 412 additions and 81 deletions
|
@ -32,6 +32,7 @@ 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 kotlinx.coroutines.runBlocking
|
||||
import java.nio.file.Path
|
||||
import java.util.UUID
|
||||
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? {
|
||||
val tc = LocalZigToolchain(path)
|
||||
var tc = LocalZigToolchain(path)
|
||||
if (!tc.zig.fileValid()) {
|
||||
return null
|
||||
}
|
||||
tc.zig
|
||||
.getEnvBlocking(null)
|
||||
.getOrNull()
|
||||
?.version
|
||||
?.let { "Zig $it" }
|
||||
?.let { tc = tc.copy(name = it) }
|
||||
return tc
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,8 +30,13 @@ 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.FileUtil
|
||||
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.IconUtil
|
||||
import com.intellij.util.system.OS
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
|
@ -98,4 +103,24 @@ class LocalZigToolchainProvider: ZigToolchainProvider {
|
|||
EnvironmentUtil.getValue("PATH")?.split(File.pathSeparatorChar)?.let { res.addAll(it.toList()) }
|
||||
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
|
||||
}
|
|
@ -22,111 +22,94 @@
|
|||
|
||||
package com.falsepattern.zigbrains.project.toolchain
|
||||
|
||||
import com.falsepattern.zigbrains.Icons
|
||||
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.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
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.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.ComboBox
|
||||
import com.intellij.openapi.ui.DialogBuilder
|
||||
import com.intellij.openapi.ui.MasterDetailsComponent
|
||||
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.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.platform.ide.progress.ModalTaskOwner
|
||||
import com.intellij.platform.ide.progress.TaskCancellation
|
||||
import com.intellij.platform.ide.progress.withModalProgress
|
||||
import com.intellij.platform.util.progress.withProgressText
|
||||
import com.intellij.ui.*
|
||||
import com.intellij.ui.components.JBList
|
||||
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.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.Consumer
|
||||
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.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.LayoutManager
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
import javax.swing.AbstractListModel
|
||||
import javax.swing.BoxLayout
|
||||
import javax.swing.DefaultListModel
|
||||
import javax.accessibility.AccessibleContext
|
||||
import javax.swing.DefaultComboBoxModel
|
||||
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.border.Border
|
||||
import javax.swing.event.DocumentEvent
|
||||
import javax.swing.tree.DefaultTreeModel
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
class ZigToolchainListEditor(): MasterDetailsComponent() {
|
||||
class ZigToolchainListEditor() : MasterDetailsComponent() {
|
||||
private var isTreeInitialized = false
|
||||
private var myComponent: JComponent? = null
|
||||
|
||||
override fun createComponent(): JComponent {
|
||||
if (!isTreeInitialized) {
|
||||
initTree()
|
||||
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 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?>? {
|
||||
val add = object : DumbAwareAction({"lmaoo"}, Presentation.NULL_STRING, IconUtil.addIcon) {
|
||||
override fun createActions(fromPopup: Boolean): List<AnAction> {
|
||||
val add = object : DumbAwareAction({ "lmaoo" }, Presentation.NULL_STRING, IconUtil.addIcon) {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val toolchains = suggestZigToolchains(zigToolchainList.toolchains.map { it.second }.toList())
|
||||
val final = ArrayList<Any>()
|
||||
final.addAll(toolchains)
|
||||
val popup = ToolchainPopup(ToolchainContext(null, CollectionListModel(final)), null, {})
|
||||
val final = ArrayList<TCListElemIn>()
|
||||
final.add(TCListElem.Download)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -140,6 +123,61 @@ class ZigToolchainListEditor(): MasterDetailsComponent() {
|
|||
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() {
|
||||
reloadTree()
|
||||
super.reset()
|
||||
|
@ -149,20 +187,160 @@ class ZigToolchainListEditor(): MasterDetailsComponent() {
|
|||
|
||||
override fun getDisplayName() = ZigBrainsBundle.message("settings.toolchains.title")
|
||||
|
||||
private fun addLocalToolchain(uuid: UUID, toolchain: LocalZigToolchain) {
|
||||
val node = MyNode(LocalZigToolchainConfigurable(uuid, toolchain, ProjectManager.getInstance().defaultProject))
|
||||
private fun addToolchain(uuid: UUID, toolchain: AbstractZigToolchain) {
|
||||
val node = MyNode(toolchain.createNamedConfigurable(uuid, ProjectManager.getInstance().defaultProject))
|
||||
addNode(node, myRoot)
|
||||
}
|
||||
|
||||
private fun reloadTree() {
|
||||
myRoot.removeAllChildren()
|
||||
zigToolchainList.toolchains.forEach { (uuid, toolchain) ->
|
||||
if (toolchain is LocalZigToolchain) {
|
||||
addLocalToolchain(uuid, toolchain)
|
||||
}
|
||||
addToolchain(uuid, toolchain)
|
||||
}
|
||||
(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)
|
|
@ -63,7 +63,6 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
|
|||
|
||||
data class State(
|
||||
@JvmField
|
||||
@MapAnnotation(surroundKeyWithTag = false, surroundValueWithTag = false)
|
||||
val toolchains: Map<String, AbstractZigToolchain.Ref> = emptyMap(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ 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.ui.SimpleColoredComponent
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
|
@ -46,6 +47,7 @@ sealed interface ZigToolchainProvider {
|
|||
fun matchesSuggestion(toolchain: AbstractZigToolchain, suggestion: AbstractZigToolchain): Boolean
|
||||
fun createConfigurable(uuid: UUID, toolchain: AbstractZigToolchain, project: Project): NamedConfigurable<UUID>
|
||||
fun suggestToolchains(): List<AbstractZigToolchain>
|
||||
fun render(toolchain: AbstractZigToolchain, component: SimpleColoredComponent)
|
||||
}
|
||||
|
||||
fun AbstractZigToolchain.Ref.resolve(): AbstractZigToolchain? {
|
||||
|
@ -75,4 +77,9 @@ fun suggestZigToolchains(existing: List<AbstractZigToolchain>): List<AbstractZig
|
|||
val suggestions = ext.suggestToolchains()
|
||||
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)
|
||||
}
|
|
@ -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")
|
|
@ -25,6 +25,7 @@ package com.falsepattern.zigbrains.project.toolchain.tools
|
|||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainEnvironmentSerializable
|
||||
import com.intellij.openapi.project.Project
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.nio.file.Path
|
||||
|
@ -45,6 +46,8 @@ class ZigCompilerTool(toolchain: AbstractZigToolchain) : ZigTool(toolchain) {
|
|||
Result.failure(IllegalStateException("could not deserialize zig env", e))
|
||||
}
|
||||
}
|
||||
|
||||
fun getEnvBlocking(project: Project?) = runBlocking { getEnv(project) }
|
||||
}
|
||||
|
||||
private val envJson = Json {
|
||||
|
|
|
@ -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 {
|
||||
return runInterruptible(Dispatchers.EDT + state.asContextElement(), block = targetAction)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue