project-level toolchain selector
This commit is contained in:
parent
9541bb9752
commit
8bb4e8bef1
20 changed files with 1016 additions and 538 deletions
|
@ -1,332 +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.Icons
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.createNamedConfigurable
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.render
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.suggestZigToolchains
|
||||
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.fileChooser.FileChooserDescriptorFactory
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.ui.DialogBuilder
|
||||
import com.intellij.openapi.ui.MasterDetailsComponent
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.platform.ide.progress.ModalTaskOwner
|
||||
import com.intellij.platform.ide.progress.TaskCancellation
|
||||
import com.intellij.platform.ide.progress.withModalProgress
|
||||
import com.intellij.ui.*
|
||||
import com.intellij.ui.components.panels.OpaquePanel
|
||||
import com.intellij.ui.components.textFieldWithBrowseButton
|
||||
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.popup.list.ComboBoxPopup
|
||||
import com.intellij.util.Consumer
|
||||
import com.intellij.util.IconUtil
|
||||
import com.intellij.util.ui.EmptyIcon
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import kotlinx.coroutines.async
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Component
|
||||
import java.util.*
|
||||
import javax.accessibility.AccessibleContext
|
||||
import javax.swing.DefaultComboBoxModel
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JList
|
||||
import javax.swing.ListCellRenderer
|
||||
import javax.swing.border.Border
|
||||
import javax.swing.tree.DefaultTreeModel
|
||||
|
||||
class ZigToolchainListEditor() : MasterDetailsComponent() {
|
||||
private var isTreeInitialized = false
|
||||
private var myComponent: JComponent? = null
|
||||
|
||||
override fun createComponent(): JComponent {
|
||||
if (!isTreeInitialized) {
|
||||
initTree()
|
||||
isTreeInitialized = true
|
||||
}
|
||||
val comp = super.createComponent()
|
||||
myComponent = comp
|
||||
return comp
|
||||
}
|
||||
|
||||
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<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)
|
||||
}
|
||||
}
|
||||
return listOf(add, MyDeleteAction())
|
||||
}
|
||||
|
||||
override fun onItemDeleted(item: Any?) {
|
||||
if (item is UUID) {
|
||||
zigToolchainList.removeToolchain(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() {
|
||||
reloadTree()
|
||||
super.reset()
|
||||
}
|
||||
|
||||
override fun getEmptySelectionString() = ZigBrainsBundle.message("settings.toolchains.empty")
|
||||
|
||||
override fun getDisplayName() = ZigBrainsBundle.message("settings.toolchains.title")
|
||||
|
||||
private fun addToolchain(uuid: UUID, toolchain: ZigToolchain) {
|
||||
val node = MyNode(toolchain.createNamedConfigurable(uuid, ProjectManager.getInstance().defaultProject))
|
||||
addNode(node, myRoot)
|
||||
}
|
||||
|
||||
private fun reloadTree() {
|
||||
myRoot.removeAllChildren()
|
||||
zigToolchainList.toolchains.forEach { (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: ZigToolchain) : 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)
|
|
@ -67,6 +67,9 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
|
|||
@JvmField
|
||||
val toolchains: Map<String, ZigToolchain.Ref> = emptyMap(),
|
||||
)
|
||||
}
|
||||
|
||||
val zigToolchainList get() = service<ZigToolchainListService>()
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(): ZigToolchainListService = service()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* 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.project.toolchain.base.ZigToolchain
|
||||
import com.intellij.openapi.components.SerializablePersistentStateComponent
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.State
|
||||
import com.intellij.openapi.components.Storage
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.util.xmlb.annotations.Attribute
|
||||
import java.util.UUID
|
||||
|
||||
@Service(Service.Level.PROJECT)
|
||||
@State(
|
||||
name = "ZigToolchain",
|
||||
storages = [Storage("zigbrains.xml")]
|
||||
)
|
||||
class ZigToolchainService: SerializablePersistentStateComponent<ZigToolchainService.State>(State()) {
|
||||
var toolchainUUID: UUID?
|
||||
get() = state.toolchain.ifBlank { null }?.let { UUID.fromString(it) }
|
||||
set(value) {
|
||||
updateState {
|
||||
it.copy(toolchain = value?.toString() ?: "")
|
||||
}
|
||||
}
|
||||
|
||||
val toolchain: ZigToolchain?
|
||||
get() = toolchainUUID?.let { ZigToolchainListService.getInstance().getToolchain(it) }
|
||||
|
||||
@JvmRecord
|
||||
data class State(
|
||||
@JvmField
|
||||
@Attribute
|
||||
val toolchain: String = ""
|
||||
)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(project: Project): ZigToolchainService = project.service()
|
||||
}
|
||||
}
|
|
@ -1,106 +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.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,13 +25,16 @@ package com.falsepattern.zigbrains.project.toolchain.base
|
|||
import com.falsepattern.zigbrains.project.toolchain.tools.ZigCompilerTool
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.UserDataHolderBase
|
||||
import com.intellij.util.xmlb.annotations.Attribute
|
||||
import com.intellij.util.xmlb.annotations.MapAnnotation
|
||||
import java.nio.file.Path
|
||||
|
||||
abstract class ZigToolchain {
|
||||
abstract class ZigToolchain: UserDataHolderBase() {
|
||||
val zig: ZigCompilerTool by lazy { ZigCompilerTool(this) }
|
||||
|
||||
abstract val name: String?
|
||||
|
||||
abstract fun workingDirectory(project: Project? = null): Path?
|
||||
|
||||
abstract suspend fun patchCommandLine(commandLine: GeneralCommandLine, project: Project? = null): GeneralCommandLine
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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.base
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
|
||||
import com.intellij.openapi.ui.NamedConfigurable
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import java.util.UUID
|
||||
import javax.swing.JComponent
|
||||
|
||||
abstract class ZigToolchainConfigurable<T: ZigToolchain>(
|
||||
val uuid: UUID,
|
||||
tc: T
|
||||
): NamedConfigurable<UUID>() {
|
||||
var toolchain: T = tc
|
||||
set(value) {
|
||||
ZigToolchainListService.getInstance().setToolchain(uuid, value)
|
||||
field = value
|
||||
}
|
||||
private var myView: ZigToolchainPanel<T>? = null
|
||||
|
||||
abstract fun createPanel(): ZigToolchainPanel<T>
|
||||
|
||||
override fun createOptionsPanel(): JComponent? {
|
||||
var view = myView
|
||||
if (view == null) {
|
||||
view = createPanel()
|
||||
view.reset(toolchain)
|
||||
myView = view
|
||||
}
|
||||
return panel {
|
||||
view.attach(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEditableObject(): UUID? {
|
||||
return uuid
|
||||
}
|
||||
|
||||
override fun getBannerSlogan(): @NlsContexts.DetailedDescription String? {
|
||||
return displayName
|
||||
}
|
||||
|
||||
override fun getDisplayName(): @NlsContexts.ConfigurableName String? {
|
||||
return toolchain.name
|
||||
}
|
||||
|
||||
override fun isModified(): Boolean {
|
||||
return myView?.isModified(toolchain) == true
|
||||
}
|
||||
|
||||
override fun apply() {
|
||||
myView?.apply(toolchain)?.let { toolchain = it }
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
myView?.reset(toolchain)
|
||||
}
|
||||
|
||||
override fun disposeUIResources() {
|
||||
myView?.dispose()
|
||||
myView = null
|
||||
super.disposeUIResources()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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.base
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.ui.components.JBTextField
|
||||
import com.intellij.ui.dsl.builder.AlignX
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
|
||||
abstract class ZigToolchainPanel<T: ZigToolchain>: Disposable {
|
||||
private val nameField = JBTextField()
|
||||
|
||||
protected var nameFieldValue: String?
|
||||
get() = nameField.text.ifBlank { null }
|
||||
set(value) {nameField.text = value ?: ""}
|
||||
|
||||
open fun attach(p: Panel): Unit = with(p) {
|
||||
row("Name") {
|
||||
cell(nameField).resizableColumn().align(AlignX.FILL)
|
||||
}
|
||||
separator()
|
||||
}
|
||||
|
||||
abstract fun isModified(toolchain: T): Boolean
|
||||
abstract fun apply(toolchain: T): T?
|
||||
abstract fun reset(toolchain: T)
|
||||
}
|
|
@ -22,6 +22,7 @@
|
|||
|
||||
package com.falsepattern.zigbrains.project.toolchain.base
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.NamedConfigurable
|
||||
|
@ -39,7 +40,7 @@ internal interface ZigToolchainProvider {
|
|||
fun deserialize(data: Map<String, String>): ZigToolchain?
|
||||
fun serialize(toolchain: ZigToolchain): Map<String, String>
|
||||
fun matchesSuggestion(toolchain: ZigToolchain, suggestion: ZigToolchain): Boolean
|
||||
fun createConfigurable(uuid: UUID, toolchain: ZigToolchain, project: Project): NamedConfigurable<UUID>
|
||||
fun createConfigurable(uuid: UUID, toolchain: ZigToolchain): ZigToolchainConfigurable<*>
|
||||
fun suggestToolchains(): List<ZigToolchain>
|
||||
fun render(toolchain: ZigToolchain, component: SimpleColoredComponent)
|
||||
}
|
||||
|
@ -60,12 +61,13 @@ suspend fun Project?.suggestZigToolchain(extraData: UserDataHolder): ZigToolchai
|
|||
return EXTENSION_POINT_NAME.extensionList.firstNotNullOfOrNull { it.suggestToolchain(this, extraData) }
|
||||
}
|
||||
|
||||
fun ZigToolchain.createNamedConfigurable(uuid: UUID, project: Project): NamedConfigurable<UUID> {
|
||||
fun ZigToolchain.createNamedConfigurable(uuid: UUID): ZigToolchainConfigurable<*> {
|
||||
val provider = EXTENSION_POINT_NAME.extensionList.find { it.isCompatible(this) } ?: throw IllegalStateException()
|
||||
return provider.createConfigurable(uuid, this, project)
|
||||
return provider.createConfigurable(uuid, this)
|
||||
}
|
||||
|
||||
fun suggestZigToolchains(existing: List<ZigToolchain>): List<ZigToolchain> {
|
||||
fun suggestZigToolchains(): List<ZigToolchain> {
|
||||
val existing = ZigToolchainListService.getInstance().toolchains.map { (uuid, tc) -> tc }.toList()
|
||||
return EXTENSION_POINT_NAME.extensionList.flatMap { ext ->
|
||||
val compatibleExisting = existing.filter { ext.isCompatible(it) }
|
||||
val suggestions = ext.suggestToolchains()
|
||||
|
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* 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.downloader
|
||||
|
||||
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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
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 {
|
||||
suspend fun download(): List<Pair<String, ZigVersionInfo>> {
|
||||
return withProgressText("Fetching zig version information") {
|
||||
withContext(Dispatchers.IO) {
|
||||
doDownload()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
private suspend fun doDownload(): List<Pair<String, ZigVersionInfo>> {
|
||||
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) }
|
||||
}
|
||||
}
|
||||
return info?.mapNotNull { (version, data) -> parseVersion(data)?.let { Pair(version, it) } }?.toList() ?: emptyList()
|
||||
}
|
||||
|
||||
private fun parseVersion(data: JsonElement): ZigVersionInfo? {
|
||||
data as? JsonObject ?: return 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 { (dist, tb) -> getTarballIfCompatible(dist, tb) } ?: return null
|
||||
|
||||
return ZigVersionInfo(date, docs, notes, src, dist)
|
||||
}
|
||||
|
||||
private fun getTarballIfCompatible(dist: String, tb: JsonElement): Tarball? {
|
||||
if (!dist.contains('-'))
|
||||
return 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 null
|
||||
}
|
||||
val theOS = when (os) {
|
||||
"linux" -> OS.Linux
|
||||
"windows" -> OS.Windows
|
||||
"macos" -> OS.macOS
|
||||
"freebsd" -> OS.FreeBSD
|
||||
else -> return null
|
||||
}
|
||||
if (theArch != CpuArch.CURRENT || theOS != OS.CURRENT) {
|
||||
return null
|
||||
}
|
||||
return Json.decodeFromJsonElement<Tarball>(tb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmRecord
|
||||
@Serializable
|
||||
data class Tarball(val tarball: String, val shasum: String, val size: Int)
|
||||
|
||||
private val tempPluginDir get(): Path = PathManager.getTempPath().toNioPathOrNull()!!.resolve("zigbrains")
|
|
@ -35,7 +35,7 @@ import com.intellij.openapi.util.io.toNioPathOrNull
|
|||
import com.intellij.openapi.vfs.toNioPathOrNull
|
||||
import java.nio.file.Path
|
||||
|
||||
data class LocalZigToolchain(val location: Path, val std: Path? = null, val name: String? = null): ZigToolchain() {
|
||||
data class LocalZigToolchain(val location: Path, val std: Path? = null, override val name: String? = null): ZigToolchain() {
|
||||
override fun workingDirectory(project: Project?): Path? {
|
||||
return project?.guessProjectDir()?.toNioPathOrNull()
|
||||
}
|
||||
|
|
|
@ -22,75 +22,16 @@
|
|||
|
||||
package com.falsepattern.zigbrains.project.toolchain.local
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.NamedConfigurable
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainConfigurable
|
||||
import java.util.UUID
|
||||
import javax.swing.JComponent
|
||||
|
||||
class LocalZigToolchainConfigurable(
|
||||
val uuid: UUID,
|
||||
toolchain: LocalZigToolchain,
|
||||
private val project: Project
|
||||
): NamedConfigurable<UUID>() {
|
||||
var toolchain: LocalZigToolchain = toolchain
|
||||
set(value) {
|
||||
zigToolchainList.setToolchain(uuid, value)
|
||||
field = value
|
||||
}
|
||||
private var myView: LocalZigToolchainPanel? = null
|
||||
uuid: UUID,
|
||||
toolchain: LocalZigToolchain
|
||||
): ZigToolchainConfigurable<LocalZigToolchain>(uuid, toolchain) {
|
||||
override fun createPanel() = LocalZigToolchainPanel()
|
||||
|
||||
override fun setDisplayName(name: String?) {
|
||||
toolchain = toolchain.copy(name = name)
|
||||
}
|
||||
|
||||
override fun getEditableObject(): UUID {
|
||||
return uuid
|
||||
}
|
||||
|
||||
override fun getBannerSlogan(): @NlsContexts.DetailedDescription String? {
|
||||
return displayName
|
||||
}
|
||||
|
||||
override fun createOptionsPanel(): JComponent? {
|
||||
var view = myView
|
||||
if (view == null) {
|
||||
view = LocalZigToolchainPanel()
|
||||
view.reset(this)
|
||||
myView = view
|
||||
}
|
||||
return panel {
|
||||
view.attach(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDisplayName(): @NlsContexts.ConfigurableName String? {
|
||||
var theName = toolchain.name
|
||||
if (theName == null) {
|
||||
val version = toolchain.zig.let { runBlocking { it.getEnv(project) } }.getOrNull()?.version
|
||||
if (version != null) {
|
||||
theName = "Zig $version"
|
||||
toolchain = toolchain.copy(name = theName)
|
||||
}
|
||||
}
|
||||
return theName
|
||||
}
|
||||
|
||||
override fun isModified(): Boolean {
|
||||
return myView?.isModified(this) == true
|
||||
}
|
||||
|
||||
override fun apply() {
|
||||
myView?.apply(this)
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
myView?.reset(this)
|
||||
}
|
||||
|
||||
override fun disposeUIResources() {
|
||||
super.disposeUIResources()
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@
|
|||
package com.falsepattern.zigbrains.project.toolchain.local
|
||||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainPanel
|
||||
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.intellij.openapi.Disposable
|
||||
|
@ -45,8 +46,7 @@ import kotlinx.coroutines.launch
|
|||
import javax.swing.event.DocumentEvent
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
class LocalZigToolchainPanel() : Disposable {
|
||||
private val nameField = JBTextField()
|
||||
class LocalZigToolchainPanel() : ZigToolchainPanel<LocalZigToolchain>() {
|
||||
private val pathToToolchain = textFieldWithBrowseButton(
|
||||
null,
|
||||
FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.zig-toolchain"))
|
||||
|
@ -75,11 +75,8 @@ class LocalZigToolchainPanel() : Disposable {
|
|||
).also { Disposer.register(this, it) }
|
||||
private var debounce: Job? = null
|
||||
|
||||
fun attach(p: Panel): Unit = with(p) {
|
||||
row("Name") {
|
||||
cell(nameField).resizableColumn().align(AlignX.FILL)
|
||||
}
|
||||
separator()
|
||||
override fun attach(p: Panel): Unit = with(p) {
|
||||
super.attach(p)
|
||||
row(ZigBrainsBundle.message("settings.project.label.toolchain")) {
|
||||
cell(pathToToolchain).resizableColumn().align(AlignX.FILL)
|
||||
}
|
||||
|
@ -92,27 +89,23 @@ class LocalZigToolchainPanel() : Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
fun isModified(cfg: LocalZigToolchainConfigurable): Boolean {
|
||||
val name = nameField.text.ifBlank { null } ?: return false
|
||||
val tc = cfg.toolchain
|
||||
override fun isModified(toolchain: LocalZigToolchain): Boolean {
|
||||
val name = nameFieldValue ?: return false
|
||||
val location = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull() ?: return false
|
||||
val std = if (stdFieldOverride.isSelected) pathToStd.text.ifBlank { null }?.toNioPathOrNull() else null
|
||||
return name != cfg.displayName || tc.location != location || tc.std != std
|
||||
return name != toolchain.name || toolchain.location != location || toolchain.std != std
|
||||
}
|
||||
|
||||
fun apply(cfg: LocalZigToolchainConfigurable): Boolean {
|
||||
val tc = cfg.toolchain
|
||||
val location = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull() ?: return false
|
||||
override fun apply(toolchain: LocalZigToolchain): LocalZigToolchain? {
|
||||
val location = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull() ?: return null
|
||||
val std = if (stdFieldOverride.isSelected) pathToStd.text.ifBlank { null }?.toNioPathOrNull() else null
|
||||
cfg.toolchain = tc.copy(location = location, std = std, name = nameField.text ?: "")
|
||||
return true
|
||||
return toolchain.copy(location = location, std = std, name = nameFieldValue ?: "")
|
||||
}
|
||||
|
||||
fun reset(cfg: LocalZigToolchainConfigurable) {
|
||||
nameField.text = cfg.displayName ?: ""
|
||||
val tc = cfg.toolchain
|
||||
this.pathToToolchain.text = tc.location.pathString
|
||||
val std = tc.std
|
||||
override fun reset(toolchain: LocalZigToolchain) {
|
||||
nameFieldValue = toolchain.name
|
||||
this.pathToToolchain.text = toolchain.location.pathString
|
||||
val std = toolchain.std
|
||||
if (std != null) {
|
||||
stdFieldOverride.isSelected = true
|
||||
pathToStd.text = std.pathString
|
||||
|
|
|
@ -26,6 +26,7 @@ import com.falsepattern.zigbrains.direnv.DirenvCmd
|
|||
import com.falsepattern.zigbrains.direnv.emptyEnv
|
||||
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainConfigurable
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainProvider
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.NamedConfigurable
|
||||
|
@ -85,11 +86,10 @@ class LocalZigToolchainProvider: ZigToolchainProvider {
|
|||
|
||||
override fun createConfigurable(
|
||||
uuid: UUID,
|
||||
toolchain: ZigToolchain,
|
||||
project: Project
|
||||
): NamedConfigurable<UUID> {
|
||||
toolchain: ZigToolchain
|
||||
): ZigToolchainConfigurable<*> {
|
||||
toolchain as LocalZigToolchain
|
||||
return LocalZigToolchainConfigurable(uuid, toolchain, project)
|
||||
return LocalZigToolchainConfigurable(uuid, toolchain)
|
||||
}
|
||||
|
||||
override fun suggestToolchains(): List<ZigToolchain> {
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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.ui
|
||||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.project.toolchain.downloader.ZigVersionInfo
|
||||
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.ui.DialogBuilder
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.platform.ide.progress.ModalTaskOwner
|
||||
import com.intellij.platform.ide.progress.TaskCancellation
|
||||
import com.intellij.platform.ide.progress.withModalProgress
|
||||
import com.intellij.ui.components.textFieldWithBrowseButton
|
||||
import com.intellij.ui.dsl.builder.AlignX
|
||||
import com.intellij.ui.dsl.builder.Cell
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import java.awt.Component
|
||||
import javax.swing.DefaultComboBoxModel
|
||||
|
||||
object Downloader {
|
||||
suspend fun openDownloadDialog(component: Component) {
|
||||
val info = withModalProgress(
|
||||
component.let { ModalTaskOwner.component(it) },
|
||||
"Fetching zig version information",
|
||||
TaskCancellation.Companion.cancellable()) {
|
||||
ZigVersionInfo.Companion.download()
|
||||
}
|
||||
withEDTContext(ModalityState.stateForComponent(component)) {
|
||||
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()
|
||||
Disposer.dispose(dialog)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* 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.ui
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.suggestZigToolchains
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.options.Configurable
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.ui.dsl.builder.AlignX
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import java.awt.event.ItemEvent
|
||||
import javax.swing.JComponent
|
||||
import kotlin.collections.addAll
|
||||
|
||||
class ZigToolchainEditor(private val project: Project): Configurable {
|
||||
private var myUi: UI? = null
|
||||
override fun getDisplayName(): @NlsContexts.ConfigurableName String? {
|
||||
return "Zig"
|
||||
}
|
||||
|
||||
override fun createComponent(): JComponent? {
|
||||
if (myUi != null) {
|
||||
disposeUIResources()
|
||||
}
|
||||
val ui = UI()
|
||||
myUi = ui
|
||||
return panel {
|
||||
ui.attach(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isModified(): Boolean {
|
||||
return myUi?.isModified() == true
|
||||
}
|
||||
|
||||
override fun apply() {
|
||||
myUi?.apply()
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
myUi?.reset()
|
||||
}
|
||||
|
||||
override fun disposeUIResources() {
|
||||
myUi?.let { Disposer.dispose(it) }
|
||||
myUi = null
|
||||
super.disposeUIResources()
|
||||
}
|
||||
|
||||
inner class UI(): Disposable {
|
||||
private val toolchainBox: TCComboBox
|
||||
private var oldSelectionIndex: Int = 0
|
||||
init {
|
||||
val modelList = ArrayList<TCListElemIn>()
|
||||
modelList.add(TCListElem.None)
|
||||
modelList.addAll(ZigToolchainListService.Companion.getInstance().toolchains.map { it.asActual() })
|
||||
modelList.add(Separator("", true))
|
||||
modelList.addAll(TCListElem.fetchGroup)
|
||||
modelList.add(Separator("Detected toolchains", true))
|
||||
modelList.addAll(suggestZigToolchains().map { it.asSuggested() })
|
||||
toolchainBox = TCComboBox(TCModel(modelList))
|
||||
toolchainBox.addItemListener(::itemStateChanged)
|
||||
reset()
|
||||
}
|
||||
|
||||
private fun itemStateChanged(event: ItemEvent) {
|
||||
if (event.stateChange != ItemEvent.SELECTED) {
|
||||
return
|
||||
}
|
||||
val item = event.item
|
||||
if (item !is TCListElem) {
|
||||
toolchainBox.selectedIndex = oldSelectionIndex
|
||||
return
|
||||
}
|
||||
when(item) {
|
||||
is TCListElem.None, is TCListElem.Toolchain.Actual -> {
|
||||
oldSelectionIndex = toolchainBox.selectedIndex
|
||||
}
|
||||
else -> {
|
||||
toolchainBox.selectedIndex = oldSelectionIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun attach(p: Panel): Unit = with(p) {
|
||||
row("Toolchain") {
|
||||
cell(toolchainBox).resizableColumn().align(AlignX.FILL)
|
||||
}
|
||||
}
|
||||
|
||||
fun isModified(): Boolean {
|
||||
return ZigToolchainService.Companion.getInstance(project).toolchainUUID != toolchainBox.selectedToolchain
|
||||
}
|
||||
|
||||
fun apply() {
|
||||
ZigToolchainService.Companion.getInstance(project).toolchainUUID = toolchainBox.selectedToolchain
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
toolchainBox.selectedToolchain = ZigToolchainService.Companion.getInstance(project).toolchainUUID
|
||||
oldSelectionIndex = toolchainBox.selectedIndex
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* 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.ui
|
||||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.createNamedConfigurable
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.suggestZigToolchains
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
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.ui.MasterDetailsComponent
|
||||
import com.intellij.util.IconUtil
|
||||
import kotlinx.coroutines.async
|
||||
import java.util.UUID
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.tree.DefaultTreeModel
|
||||
|
||||
class ZigToolchainListEditor() : MasterDetailsComponent() {
|
||||
private var isTreeInitialized = false
|
||||
private var myComponent: JComponent? = null
|
||||
|
||||
override fun createComponent(): JComponent {
|
||||
if (!isTreeInitialized) {
|
||||
initTree()
|
||||
isTreeInitialized = true
|
||||
}
|
||||
val comp = super.createComponent()
|
||||
myComponent = comp
|
||||
return comp
|
||||
}
|
||||
|
||||
override fun createActions(fromPopup: Boolean): List<AnAction> {
|
||||
val add = object : DumbAwareAction({ "lmaoo" }, Presentation.NULL_STRING, IconUtil.addIcon) {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val modelList = ArrayList<TCListElemIn>()
|
||||
modelList.addAll(TCListElem.fetchGroup)
|
||||
modelList.add(Separator("Detected toolchains", true))
|
||||
modelList.addAll(suggestZigToolchains().map { it.asSuggested() })
|
||||
val model = TCModel.Companion(modelList)
|
||||
val context = TCContext(null, model)
|
||||
val popup = TCComboBoxPopup(context, null, ::onItemSelected)
|
||||
popup.showInBestPositionFor(e.dataContext)
|
||||
}
|
||||
}
|
||||
return listOf(add, MyDeleteAction())
|
||||
}
|
||||
|
||||
override fun onItemDeleted(item: Any?) {
|
||||
if (item is UUID) {
|
||||
ZigToolchainListService.getInstance().removeToolchain(item)
|
||||
}
|
||||
super.onItemDeleted(item)
|
||||
}
|
||||
|
||||
private fun onItemSelected(elem: TCListElem) {
|
||||
when (elem) {
|
||||
is TCListElem.Toolchain -> {
|
||||
val uuid = UUID.randomUUID()
|
||||
ZigToolchainListService.getInstance().setToolchain(uuid, elem.toolchain)
|
||||
addToolchain(uuid, elem.toolchain)
|
||||
(myTree.model as DefaultTreeModel).reload()
|
||||
}
|
||||
is TCListElem.Download -> {
|
||||
zigCoroutineScope.async {
|
||||
Downloader.openDownloadDialog(myComponent!!)
|
||||
}
|
||||
}
|
||||
is TCListElem.FromDisk -> {}
|
||||
is TCListElem.None -> {}
|
||||
}
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
reloadTree()
|
||||
super.reset()
|
||||
}
|
||||
|
||||
override fun getEmptySelectionString() = ZigBrainsBundle.message("settings.toolchains.empty")
|
||||
|
||||
override fun getDisplayName() = ZigBrainsBundle.message("settings.toolchains.title")
|
||||
|
||||
private fun addToolchain(uuid: UUID, toolchain: ZigToolchain) {
|
||||
val node = MyNode(toolchain.createNamedConfigurable(uuid))
|
||||
addNode(node, myRoot)
|
||||
}
|
||||
|
||||
private fun reloadTree() {
|
||||
myRoot.removeAllChildren()
|
||||
ZigToolchainListService.getInstance().toolchains.forEach { (uuid, toolchain) ->
|
||||
addToolchain(uuid, toolchain)
|
||||
}
|
||||
(myTree.model as DefaultTreeModel).reload()
|
||||
}
|
||||
|
||||
override fun disposeUIResources() {
|
||||
super.disposeUIResources()
|
||||
myComponent = null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* 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.ui
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import java.util.UUID
|
||||
|
||||
|
||||
internal sealed interface TCListElemIn
|
||||
|
||||
internal sealed interface TCListElem : TCListElemIn {
|
||||
sealed interface Toolchain : TCListElem {
|
||||
val toolchain: ZigToolchain
|
||||
|
||||
@JvmRecord
|
||||
data class Suggested(override val toolchain: ZigToolchain): Toolchain
|
||||
|
||||
@JvmRecord
|
||||
data class Actual(val uuid: UUID, override val toolchain: ZigToolchain): Toolchain
|
||||
}
|
||||
object None: TCListElem
|
||||
object Download : TCListElem
|
||||
object FromDisk : TCListElem
|
||||
|
||||
companion object {
|
||||
val fetchGroup get() = listOf(Download, FromDisk)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmRecord
|
||||
internal data class Separator(val text: String, val line: Boolean) : TCListElemIn
|
||||
|
||||
internal fun Pair<UUID, ZigToolchain>.asActual() = TCListElem.Toolchain.Actual(first, second)
|
||||
|
||||
internal fun ZigToolchain.asSuggested() = TCListElem.Toolchain.Suggested(this)
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* 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.ui
|
||||
|
||||
import ai.grazie.utils.attributes.value
|
||||
import com.falsepattern.zigbrains.Icons
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.render
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.ui.CellRendererPanel
|
||||
import com.intellij.ui.CollectionComboBoxModel
|
||||
import com.intellij.ui.ColoredListCellRenderer
|
||||
import com.intellij.ui.GroupHeaderSeparator
|
||||
import com.intellij.ui.SimpleColoredComponent
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.ui.components.panels.OpaquePanel
|
||||
import com.intellij.ui.popup.list.ComboBoxPopup
|
||||
import com.intellij.util.Consumer
|
||||
import com.intellij.util.ui.EmptyIcon
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Component
|
||||
import java.util.IdentityHashMap
|
||||
import java.util.UUID
|
||||
import javax.accessibility.AccessibleContext
|
||||
import javax.swing.JList
|
||||
import javax.swing.border.Border
|
||||
|
||||
internal class TCComboBoxPopup(
|
||||
context: TCContext,
|
||||
selected: TCListElem?,
|
||||
onItemSelected: Consumer<TCListElem>,
|
||||
) : ComboBoxPopup<TCListElem>(context, selected, onItemSelected)
|
||||
|
||||
internal class TCComboBox(model: TCModel): ComboBox<TCListElem>(model) {
|
||||
init {
|
||||
setRenderer(TCCellRenderer({model}))
|
||||
}
|
||||
|
||||
var selectedToolchain: UUID?
|
||||
set(value) {
|
||||
if (value == null) {
|
||||
selectedItem = TCListElem.None
|
||||
return
|
||||
}
|
||||
for (i in 0..<model.size) {
|
||||
val element = model.getElementAt(i)
|
||||
if (element is TCListElem.Toolchain.Actual) {
|
||||
if (element.uuid == value) {
|
||||
selectedIndex = i
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
selectedItem = TCListElem.None
|
||||
}
|
||||
get() {
|
||||
val item = selectedItem
|
||||
return when(item) {
|
||||
is TCListElem.Toolchain.Actual -> item.uuid
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class TCModel private constructor(elements: List<TCListElem>, private val separators: Map<TCListElem, Separator>) : CollectionComboBoxModel<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]
|
||||
}
|
||||
|
||||
internal 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(): TCCellRenderer {
|
||||
return TCCellRenderer(::getModel)
|
||||
}
|
||||
}
|
||||
|
||||
internal 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()
|
||||
|
||||
if (index == -1) {
|
||||
component.isOpaque = false
|
||||
panel.isOpaque = false
|
||||
return panel
|
||||
}
|
||||
|
||||
val separator = value?.let { model.separatorAbove(it) }
|
||||
|
||||
if (separator != null) {
|
||||
val vGap = if (UIUtil.isUnderNativeMacLookAndFeel()) 1 else 3
|
||||
val separatorComponent = GroupHeaderSeparator(JBUI.insets(vGap, 10, vGap, 0))
|
||||
separatorComponent.isHideLine = !separator.line
|
||||
separatorComponent.caption = separator.text.ifBlank { null }
|
||||
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 = when(value) {
|
||||
is TCListElem.Toolchain.Suggested -> AllIcons.General.Information
|
||||
is TCListElem.Toolchain.Actual -> 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")
|
||||
}
|
||||
|
||||
is TCListElem.None, null -> {
|
||||
icon = AllIcons.General.BalloonError
|
||||
append("<No Toolchain>", SimpleTextAttributes.ERROR_ATTRIBUTES)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val EMPTY_ICON = EmptyIcon.create(1, 16)
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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.ui.popup
|
||||
|
||||
import com.falsepattern.zigbrains.Icons
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.render
|
||||
import com.falsepattern.zigbrains.project.toolchain.ui.Separator
|
||||
import com.falsepattern.zigbrains.project.toolchain.ui.TCListElem
|
||||
import com.falsepattern.zigbrains.project.toolchain.ui.TCListElemIn
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.ui.CellRendererPanel
|
||||
import com.intellij.ui.CollectionListModel
|
||||
import com.intellij.ui.ColoredListCellRenderer
|
||||
import com.intellij.ui.GroupHeaderSeparator
|
||||
import com.intellij.ui.SimpleColoredComponent
|
||||
import com.intellij.ui.components.panels.OpaquePanel
|
||||
import com.intellij.ui.popup.list.ComboBoxPopup
|
||||
import com.intellij.util.Consumer
|
||||
import com.intellij.util.ui.EmptyIcon
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.Component
|
||||
import java.util.IdentityHashMap
|
||||
import javax.accessibility.AccessibleContext
|
||||
import javax.swing.JList
|
||||
import javax.swing.border.Border
|
||||
|
|
@ -140,13 +140,13 @@
|
|||
/>
|
||||
<projectConfigurable
|
||||
parentId="language"
|
||||
instance="com.falsepattern.zigbrains.project.settings.ZigConfigurable"
|
||||
instance="com.falsepattern.zigbrains.project.toolchain.ui.ZigToolchainEditor"
|
||||
id="ZigConfigurable"
|
||||
displayName="Zig"
|
||||
/>
|
||||
<applicationConfigurable
|
||||
parentId="ZigConfigurable"
|
||||
instance="com.falsepattern.zigbrains.project.toolchain.ZigToolchainListEditor"
|
||||
instance="com.falsepattern.zigbrains.project.toolchain.ui.ZigToolchainListEditor"
|
||||
id="ZigToolchainConfigurable"
|
||||
displayName="Toolchain"
|
||||
/>
|
||||
|
|
Loading…
Add table
Reference in a new issue