fully functional selection logic
This commit is contained in:
parent
9676b70821
commit
ee5a2463b9
14 changed files with 618 additions and 264 deletions
|
@ -35,6 +35,7 @@ import com.intellij.openapi.project.Project
|
|||
import com.intellij.openapi.ui.DialogBuilder
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.platform.util.progress.reportSequentialProgress
|
||||
import com.intellij.ui.BrowserHyperlinkListener
|
||||
import com.intellij.ui.HyperlinkLabel
|
||||
import com.intellij.ui.components.JBPanel
|
||||
|
@ -47,6 +48,7 @@ import com.jetbrains.cidr.execution.debugger.CidrDebuggerPathManager
|
|||
import com.jetbrains.cidr.execution.debugger.backend.bin.UrlProvider
|
||||
import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBDriverConfiguration
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
|
@ -167,7 +169,9 @@ class ZigDebuggerToolchainService {
|
|||
}
|
||||
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
downloadAndUnArchive(baseDir, downloadableBinaries)
|
||||
}
|
||||
return DownloadResult.Ok(baseDir)
|
||||
} catch (e: IOException) {
|
||||
//TODO logging
|
||||
|
@ -206,6 +210,7 @@ class ZigDebuggerToolchainService {
|
|||
@Throws(IOException::class)
|
||||
@RequiresEdt
|
||||
private suspend fun downloadAndUnArchive(baseDir: Path, binariesToDownload: List<DownloadableDebuggerBinary>) {
|
||||
reportSequentialProgress { reporter ->
|
||||
val service = DownloadableFileService.getInstance()
|
||||
|
||||
val downloadDir = baseDir.toFile()
|
||||
|
@ -217,7 +222,7 @@ class ZigDebuggerToolchainService {
|
|||
|
||||
val downloader = service.createDownloader(descriptions, "Debugger downloading")
|
||||
val downloadDirectory = downloadPath().toFile()
|
||||
val downloadResults = withContext(Dispatchers.IO) {
|
||||
val downloadResults = reporter.sizedStep(100) {
|
||||
coroutineToIndicator {
|
||||
downloader.download(downloadDirectory)
|
||||
}
|
||||
|
@ -228,13 +233,18 @@ class ZigDebuggerToolchainService {
|
|||
val binaryToDownload = binariesToDownload.first { it.url == downloadUrl }
|
||||
val propertyName = binaryToDownload.propertyName
|
||||
val archiveFile = result.first
|
||||
reporter.indeterminateStep {
|
||||
coroutineToIndicator {
|
||||
Unarchiver.unarchive(archiveFile.toPath(), baseDir, binaryToDownload.prefix)
|
||||
}
|
||||
}
|
||||
archiveFile.delete()
|
||||
versions[propertyName] = binaryToDownload.version
|
||||
}
|
||||
|
||||
saveVersionsFile(baseDir, versions)
|
||||
}
|
||||
}
|
||||
|
||||
private fun lldbUrls(): Pair<URL, URL>? {
|
||||
val lldb = UrlProvider.lldb(OS.CURRENT, CpuArch.CURRENT) ?: return null
|
||||
|
|
|
@ -25,7 +25,9 @@ package com.falsepattern.zigbrains.project.toolchain
|
|||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.resolve
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.toRef
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.intellij.openapi.components.*
|
||||
import kotlinx.coroutines.launch
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.UUID
|
||||
|
||||
|
@ -37,19 +39,43 @@ import java.util.UUID
|
|||
class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchainListService.State>(State()) {
|
||||
private val changeListeners = ArrayList<WeakReference<ToolchainListChangeListener>>()
|
||||
fun setToolchain(uuid: UUID, toolchain: ZigToolchain) {
|
||||
val str = uuid.toString()
|
||||
val ref = toolchain.toRef()
|
||||
updateState {
|
||||
val newMap = HashMap<String, ZigToolchain.Ref>()
|
||||
newMap.putAll(it.toolchains)
|
||||
newMap[uuid.toString()] = toolchain.toRef()
|
||||
newMap[str] = ref
|
||||
it.copy(toolchains = newMap)
|
||||
}
|
||||
notifyChanged()
|
||||
}
|
||||
|
||||
fun registerNewToolchain(toolchain: ZigToolchain): UUID {
|
||||
val ref = toolchain.toRef()
|
||||
var uuid = UUID.randomUUID()
|
||||
updateState {
|
||||
val newMap = HashMap<String, ZigToolchain.Ref>()
|
||||
newMap.putAll(it.toolchains)
|
||||
var uuidStr = uuid.toString()
|
||||
while (newMap.containsKey(uuidStr)) {
|
||||
uuid = UUID.randomUUID()
|
||||
uuidStr = uuid.toString()
|
||||
}
|
||||
newMap[uuidStr] = ref
|
||||
it.copy(toolchains = newMap)
|
||||
}
|
||||
notifyChanged()
|
||||
return uuid
|
||||
}
|
||||
|
||||
fun getToolchain(uuid: UUID): ZigToolchain? {
|
||||
return state.toolchains[uuid.toString()]?.resolve()
|
||||
}
|
||||
|
||||
fun hasToolchain(uuid: UUID): Boolean {
|
||||
return state.toolchains.containsKey(uuid.toString())
|
||||
}
|
||||
|
||||
fun removeToolchain(uuid: UUID) {
|
||||
val str = uuid.toString()
|
||||
updateState {
|
||||
|
@ -67,7 +93,9 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
|
|||
changeListeners.removeAt(i)
|
||||
continue
|
||||
}
|
||||
zigCoroutineScope.launch {
|
||||
v.toolchainListChanged()
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
@ -88,6 +116,7 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
val toolchains: Sequence<Pair<UUID, ZigToolchain>>
|
||||
get() = state.toolchains
|
||||
.asSequence()
|
||||
|
@ -109,6 +138,6 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
|
|||
|
||||
@FunctionalInterface
|
||||
interface ToolchainListChangeListener {
|
||||
fun toolchainListChanged()
|
||||
suspend fun toolchainListChanged()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,16 @@ import java.util.UUID
|
|||
)
|
||||
class ZigToolchainService: SerializablePersistentStateComponent<ZigToolchainService.State>(State()) {
|
||||
var toolchainUUID: UUID?
|
||||
get() = state.toolchain.ifBlank { null }?.let { UUID.fromString(it) }
|
||||
get() = state.toolchain.ifBlank { null }?.let { UUID.fromString(it) }?.takeIf {
|
||||
if (ZigToolchainListService.getInstance().hasToolchain(it)) {
|
||||
true
|
||||
} else {
|
||||
updateState {
|
||||
it.copy(toolchain = "")
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
set(value) {
|
||||
updateState {
|
||||
it.copy(toolchain = value?.toString() ?: "")
|
||||
|
@ -49,11 +58,10 @@ class ZigToolchainService: SerializablePersistentStateComponent<ZigToolchainServ
|
|||
val toolchain: ZigToolchain?
|
||||
get() = toolchainUUID?.let { ZigToolchainListService.getInstance().getToolchain(it) }
|
||||
|
||||
@JvmRecord
|
||||
data class State(
|
||||
@JvmField
|
||||
@Attribute
|
||||
val toolchain: String = ""
|
||||
var toolchain: String = ""
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package com.falsepattern.zigbrains.project.toolchain.downloader
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.isDirectory
|
||||
|
||||
enum class DirectoryState {
|
||||
Invalid,
|
||||
NotAbsolute,
|
||||
NotDirectory,
|
||||
NotEmpty,
|
||||
CreateNew,
|
||||
Ok;
|
||||
|
||||
fun isValid(): Boolean {
|
||||
return when(this) {
|
||||
Invalid, NotAbsolute, NotDirectory, NotEmpty -> false
|
||||
CreateNew, Ok -> true
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun determine(path: Path?): DirectoryState {
|
||||
if (path == null) {
|
||||
return Invalid
|
||||
}
|
||||
if (!path.isAbsolute) {
|
||||
return NotAbsolute
|
||||
}
|
||||
if (!path.exists()) {
|
||||
var parent: Path? = path.parent
|
||||
while(parent != null) {
|
||||
if (!parent.exists()) {
|
||||
parent = parent.parent
|
||||
continue
|
||||
}
|
||||
if (!parent.isDirectory()) {
|
||||
return NotDirectory
|
||||
}
|
||||
return CreateNew
|
||||
}
|
||||
return Invalid
|
||||
}
|
||||
if (!path.isDirectory()) {
|
||||
return NotDirectory
|
||||
}
|
||||
val isEmpty = Files.newDirectoryStream(path).use { !it.iterator().hasNext() }
|
||||
if (!isEmpty) {
|
||||
return NotEmpty
|
||||
}
|
||||
return Ok
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,14 +20,16 @@
|
|||
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.falsepattern.zigbrains.project.toolchain.ui
|
||||
package com.falsepattern.zigbrains.project.toolchain.downloader
|
||||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.project.toolchain.downloader.ZigVersionInfo
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
|
||||
import com.falsepattern.zigbrains.shared.coroutine.asContextElement
|
||||
import com.falsepattern.zigbrains.shared.coroutine.runInterruptibleEDT
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||
import com.intellij.openapi.observable.util.whenFocusGained
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.ui.DialogBuilder
|
||||
import com.intellij.openapi.util.Disposer
|
||||
|
@ -43,95 +45,38 @@ import com.intellij.ui.dsl.builder.AlignX
|
|||
import com.intellij.ui.dsl.builder.Cell
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import com.intellij.util.asSafely
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import java.awt.Component
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.UUID
|
||||
import java.util.*
|
||||
import javax.swing.DefaultComboBoxModel
|
||||
import javax.swing.JList
|
||||
import javax.swing.event.DocumentEvent
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.isDirectory
|
||||
|
||||
//TODO lang
|
||||
object Downloader {
|
||||
suspend fun downloadToolchain(component: Component): UUID? {
|
||||
suspend fun downloadToolchain(component: Component): ZigToolchain? {
|
||||
val info = withModalProgress(
|
||||
ModalTaskOwner.component(component),
|
||||
"Fetching zig version information",
|
||||
TaskCancellation.cancellable()) {
|
||||
withContext(Dispatchers.IO) {
|
||||
TaskCancellation.cancellable()
|
||||
) {
|
||||
ZigVersionInfo.downloadVersionList()
|
||||
}
|
||||
}
|
||||
val (downloadPath, version) = runInterruptibleEDT(component.asContextElement()) {
|
||||
selectToolchain(info)
|
||||
} ?: return null
|
||||
withModalProgress(
|
||||
ModalTaskOwner.component(component),
|
||||
"Downloading zig tarball",
|
||||
TaskCancellation.cancellable()) {
|
||||
withContext(Dispatchers.IO) {
|
||||
"Installing Zig ${version.version}",
|
||||
TaskCancellation.cancellable()
|
||||
) {
|
||||
version.downloadAndUnpack(downloadPath)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private enum class DirectoryState {
|
||||
Invalid,
|
||||
NotAbsolute,
|
||||
NotDirectory,
|
||||
NotEmpty,
|
||||
CreateNew,
|
||||
Ok;
|
||||
|
||||
fun isValid(): Boolean {
|
||||
return when(this) {
|
||||
Invalid, NotAbsolute, NotDirectory, NotEmpty -> false
|
||||
CreateNew, Ok -> true
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
@JvmStatic
|
||||
fun determine(path: Path?): DirectoryState {
|
||||
if (path == null) {
|
||||
return Invalid
|
||||
}
|
||||
if (!path.isAbsolute) {
|
||||
return NotAbsolute
|
||||
}
|
||||
if (!path.exists()) {
|
||||
var parent: Path? = path.parent
|
||||
while(parent != null) {
|
||||
if (!parent.exists()) {
|
||||
parent = parent.parent
|
||||
continue
|
||||
}
|
||||
if (!parent.isDirectory()) {
|
||||
return NotDirectory
|
||||
}
|
||||
return CreateNew
|
||||
}
|
||||
return Invalid
|
||||
}
|
||||
if (!path.isDirectory()) {
|
||||
return NotDirectory
|
||||
}
|
||||
val isEmpty = Files.newDirectoryStream(path).use { !it.iterator().hasNext() }
|
||||
if (!isEmpty) {
|
||||
return NotEmpty
|
||||
}
|
||||
return Ok
|
||||
}
|
||||
}
|
||||
return LocalZigToolchain.tryFromPath(downloadPath)
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
private fun selectToolchain(info: List<ZigVersionInfo>): Pair<Path, ZigVersionInfo>? {
|
||||
val dialog = DialogBuilder()
|
||||
val theList = ComboBox(DefaultComboBoxModel(info.toTypedArray()))
|
||||
|
@ -148,14 +93,14 @@ object Downloader {
|
|||
}
|
||||
val outputPath = textFieldWithBrowseButton(
|
||||
null,
|
||||
FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.zig-toolchain"))
|
||||
FileChooserDescriptorFactory.createSingleFolderDescriptor()
|
||||
.withTitle(ZigBrainsBundle.message("dialog.title.zig-toolchain"))
|
||||
)
|
||||
Disposer.register(dialog, outputPath)
|
||||
outputPath.textField.columns = 50
|
||||
|
||||
lateinit var errorMessageBox: JBLabel
|
||||
outputPath.addDocumentListener(object: DocumentAdapter() {
|
||||
override fun textChanged(e: DocumentEvent) {
|
||||
fun onChanged() {
|
||||
val path = outputPath.text.ifBlank { null }?.toNioPathOrNull()
|
||||
val state = DirectoryState.determine(path)
|
||||
if (state.isValid()) {
|
||||
|
@ -175,6 +120,13 @@ object Downloader {
|
|||
}
|
||||
dialog.window.repaint()
|
||||
}
|
||||
outputPath.whenFocusGained {
|
||||
onChanged()
|
||||
}
|
||||
outputPath.addDocumentListener(object: DocumentAdapter() {
|
||||
override fun textChanged(e: DocumentEvent) {
|
||||
onChanged()
|
||||
}
|
||||
})
|
||||
var archiveSizeCell: Cell<*>? = null
|
||||
fun detect(item: ZigVersionInfo) {
|
||||
|
@ -200,7 +152,7 @@ object Downloader {
|
|||
}
|
||||
detect(info[0])
|
||||
dialog.centerPanel(center)
|
||||
dialog.setTitle("Version Selector")
|
||||
dialog.setTitle("Zig Downloader")
|
||||
dialog.addCancelAction()
|
||||
dialog.addOkAction().also { it.setText("Download") }
|
||||
if (!dialog.showAndGet()) {
|
||||
|
@ -216,8 +168,4 @@ object Downloader {
|
|||
|
||||
return path to version
|
||||
}
|
||||
|
||||
private suspend fun installToolchain(path: Path, version: ZigVersionInfo): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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.falsepattern.zigbrains.Icons
|
||||
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.local.LocalZigToolchain
|
||||
import com.falsepattern.zigbrains.shared.coroutine.asContextElement
|
||||
import com.falsepattern.zigbrains.shared.coroutine.runInterruptibleEDT
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||
import com.intellij.openapi.ui.DialogBuilder
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.ui.DocumentAdapter
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.textFieldWithBrowseButton
|
||||
import com.intellij.ui.dsl.builder.AlignX
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import java.awt.Component
|
||||
import javax.swing.event.DocumentEvent
|
||||
|
||||
object LocalSelector {
|
||||
suspend fun browseFromDisk(component: Component): ZigToolchain? {
|
||||
return runInterruptibleEDT(component.asContextElement()) {
|
||||
doBrowseFromDisk()
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
private fun doBrowseFromDisk(): ZigToolchain? {
|
||||
val dialog = DialogBuilder()
|
||||
val path = textFieldWithBrowseButton(
|
||||
null,
|
||||
FileChooserDescriptorFactory.createSingleFolderDescriptor()
|
||||
.withTitle(ZigBrainsBundle.message("dialog.title.zig-toolchain"))
|
||||
)
|
||||
Disposer.register(dialog, path)
|
||||
path.textField.columns = 50
|
||||
lateinit var errorMessageBox: JBLabel
|
||||
path.addDocumentListener(object: DocumentAdapter() {
|
||||
override fun textChanged(e: DocumentEvent) {
|
||||
val tc = LocalZigToolchain.tryFromPathString(path.text)
|
||||
if (tc == null) {
|
||||
errorMessageBox.icon = AllIcons.General.Error
|
||||
errorMessageBox.text = "Invalid toolchain path"
|
||||
dialog.setOkActionEnabled(false)
|
||||
} else if (ZigToolchainListService
|
||||
.getInstance()
|
||||
.toolchains
|
||||
.mapNotNull { it.second as? LocalZigToolchain }
|
||||
.any { it.location == tc.location }
|
||||
) {
|
||||
errorMessageBox.icon = AllIcons.General.Warning
|
||||
errorMessageBox.text = tc.name?.let { "Toolchain already exists as \"$it\"" } ?: "Toolchain already exists"
|
||||
dialog.setOkActionEnabled(true)
|
||||
} else {
|
||||
errorMessageBox.icon = Icons.Zig
|
||||
errorMessageBox.text = tc.name ?: "OK"
|
||||
dialog.setOkActionEnabled(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
val center = panel {
|
||||
row("Path:") {
|
||||
cell(path).resizableColumn().align(AlignX.FILL)
|
||||
}
|
||||
row {
|
||||
errorMessageBox = JBLabel()
|
||||
cell(errorMessageBox)
|
||||
}
|
||||
}
|
||||
dialog.centerPanel(center)
|
||||
dialog.setTitle("Zig Browser")
|
||||
dialog.addCancelAction()
|
||||
dialog.addOkAction().also { it.setText("Add") }
|
||||
if (!dialog.showAndGet()) {
|
||||
return null
|
||||
}
|
||||
return LocalZigToolchain.tryFromPathString(path.text)
|
||||
}
|
||||
}
|
|
@ -24,12 +24,12 @@ package com.falsepattern.zigbrains.project.toolchain.downloader
|
|||
|
||||
import com.falsepattern.zigbrains.shared.Unarchiver
|
||||
import com.intellij.openapi.application.PathManager
|
||||
import com.intellij.openapi.progress.EmptyProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.coroutineToIndicator
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.platform.util.progress.reportProgress
|
||||
import com.intellij.platform.util.progress.reportSequentialProgress
|
||||
import com.intellij.platform.util.progress.withProgressText
|
||||
import com.intellij.platform.util.progress.*
|
||||
import com.intellij.util.asSafely
|
||||
import com.intellij.util.download.DownloadableFileService
|
||||
import com.intellij.util.io.createDirectories
|
||||
|
@ -38,6 +38,8 @@ import com.intellij.util.io.move
|
|||
import com.intellij.util.system.CpuArch
|
||||
import com.intellij.util.system.OS
|
||||
import com.intellij.util.text.SemVer
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
|
@ -47,9 +49,14 @@ import kotlinx.serialization.json.JsonPrimitive
|
|||
import kotlinx.serialization.json.decodeFromJsonElement
|
||||
import kotlinx.serialization.json.decodeFromStream
|
||||
import java.io.File
|
||||
import java.lang.IllegalStateException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import kotlin.io.path.ExperimentalPathApi
|
||||
import kotlin.io.path.deleteRecursively
|
||||
import kotlin.io.path.isDirectory
|
||||
import kotlin.io.path.name
|
||||
|
||||
@JvmRecord
|
||||
data class ZigVersionInfo(
|
||||
|
@ -60,59 +67,102 @@ data class ZigVersionInfo(
|
|||
val src: Tarball?,
|
||||
val dist: Tarball
|
||||
) {
|
||||
suspend fun downloadAndUnpack(into: Path): Boolean {
|
||||
return reportProgress { reporter ->
|
||||
try {
|
||||
@Throws(Exception::class)
|
||||
suspend fun downloadAndUnpack(into: Path) {
|
||||
reportProgress { reporter ->
|
||||
into.createDirectories()
|
||||
} catch (e: Exception) {
|
||||
return@reportProgress false
|
||||
val tarball = downloadTarball(dist, into, reporter)
|
||||
unpackTarball(tarball, into, reporter)
|
||||
tarball.delete()
|
||||
flattenDownloadDir(into, reporter)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
suspend fun downloadVersionList(): List<ZigVersionInfo> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val service = DownloadableFileService.getInstance()
|
||||
val tempFile = FileUtil.createTempFile(tempPluginDir, "index", ".json", false, false)
|
||||
val desc = service.createFileDescription("https://ziglang.org/download/index.json", tempFile.name)
|
||||
val downloader = service.createDownloader(listOf(desc), "Zig version information")
|
||||
val downloadResults = coroutineToIndicator {
|
||||
downloader.download(tempPluginDir)
|
||||
}
|
||||
if (downloadResults.isEmpty())
|
||||
return@withContext emptyList()
|
||||
val index = downloadResults[0].first
|
||||
val info = index.inputStream().use { Json.decodeFromStream<JsonObject>(it) }
|
||||
index.delete()
|
||||
return@withContext info.mapNotNull { (version, data) -> parseVersion(version, data) }.toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmRecord
|
||||
@Serializable
|
||||
data class Tarball(val tarball: String, val shasum: String, val size: Int)
|
||||
}
|
||||
|
||||
private suspend fun downloadTarball(dist: ZigVersionInfo.Tarball, into: Path, reporter: ProgressReporter): Path {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val service = DownloadableFileService.getInstance()
|
||||
val fileName = dist.tarball.substringAfterLast('/')
|
||||
val tempFile = FileUtil.createTempFile(into.toFile(), "tarball", fileName, false, false)
|
||||
val desc = service.createFileDescription(dist.tarball, tempFile.name)
|
||||
val downloader = service.createDownloader(listOf(desc), "Zig version information downloading")
|
||||
val downloader = service.createDownloader(listOf(desc), "Zig tarball")
|
||||
val downloadResults = reporter.sizedStep(100) {
|
||||
coroutineToIndicator {
|
||||
downloader.download(into.toFile())
|
||||
}
|
||||
}
|
||||
if (downloadResults.isEmpty())
|
||||
return@reportProgress false
|
||||
val tarball = downloadResults[0].first
|
||||
reporter.indeterminateStep("Extracting tarball") {
|
||||
Unarchiver.unarchive(tarball.toPath(), into)
|
||||
tarball.delete()
|
||||
val contents = Files.newDirectoryStream(into).use { it.toList() }
|
||||
throw IllegalStateException("No file downloaded")
|
||||
return@withContext downloadResults[0].first.toPath()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun flattenDownloadDir(dir: Path, reporter: ProgressReporter) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val contents = Files.newDirectoryStream(dir).use { it.toList() }
|
||||
if (contents.size == 1 && contents[0].isDirectory()) {
|
||||
val src = contents[0]
|
||||
reporter.indeterminateStep {
|
||||
coroutineToIndicator {
|
||||
val indicator = ProgressManager.getInstance().progressIndicator ?: EmptyProgressIndicator()
|
||||
indicator.isIndeterminate = true
|
||||
indicator.text = "Flattening directory"
|
||||
Files.newDirectoryStream(src).use { stream ->
|
||||
stream.forEach {
|
||||
it.move(into.resolve(src.relativize(it)))
|
||||
indicator.text2 = it.name
|
||||
it.move(dir.resolve(src.relativize(it)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
src.delete()
|
||||
}
|
||||
}
|
||||
return@reportProgress true
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPathApi::class)
|
||||
private suspend fun unpackTarball(tarball: Path, into: Path, reporter: ProgressReporter) {
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
reporter.indeterminateStep {
|
||||
coroutineToIndicator {
|
||||
Unarchiver.unarchive(tarball, into)
|
||||
}
|
||||
}
|
||||
companion object {
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
suspend fun downloadVersionList(): List<ZigVersionInfo> {
|
||||
val service = DownloadableFileService.getInstance()
|
||||
val tempFile = FileUtil.createTempFile(tempPluginDir, "index", ".json", false, false)
|
||||
val desc = service.createFileDescription("https://ziglang.org/download/index.json", tempFile.name)
|
||||
val downloader = service.createDownloader(listOf(desc), "Zig version information downloading")
|
||||
val downloadResults = coroutineToIndicator {
|
||||
downloader.download(tempPluginDir)
|
||||
} catch (e: Throwable) {
|
||||
tarball.delete()
|
||||
val contents = Files.newDirectoryStream(into).use { it.toList() }
|
||||
if (contents.size == 1 && contents[0].isDirectory()) {
|
||||
contents[0].deleteRecursively()
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
if (downloadResults.isEmpty())
|
||||
return emptyList()
|
||||
val index = downloadResults[0].first
|
||||
val info = index.inputStream().use { Json.decodeFromStream<JsonObject>(it) }
|
||||
index.delete()
|
||||
return info.mapNotNull { (version, data) -> parseVersion(version, data) }.toList()
|
||||
}
|
||||
|
||||
private fun parseVersion(versionKey: String, data: JsonElement): ZigVersionInfo? {
|
||||
|
@ -126,7 +176,7 @@ data class ZigVersionInfo(
|
|||
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 src = data["src"]?.asSafely<JsonObject>()?.let { Json.decodeFromJsonElement<ZigVersionInfo.Tarball>(it) }
|
||||
val dist = data.firstNotNullOfOrNull { (dist, tb) -> getTarballIfCompatible(dist, tb) }
|
||||
?: return null
|
||||
|
||||
|
@ -134,7 +184,7 @@ data class ZigVersionInfo(
|
|||
return ZigVersionInfo(version, date, docs, notes, src, dist)
|
||||
}
|
||||
|
||||
private fun getTarballIfCompatible(dist: String, tb: JsonElement): Tarball? {
|
||||
private fun getTarballIfCompatible(dist: String, tb: JsonElement): ZigVersionInfo.Tarball? {
|
||||
if (!dist.contains('-'))
|
||||
return null
|
||||
val (arch, os) = dist.split('-', limit = 2)
|
||||
|
@ -155,13 +205,7 @@ data class ZigVersionInfo(
|
|||
if (theArch != CpuArch.CURRENT || theOS != OS.CURRENT) {
|
||||
return null
|
||||
}
|
||||
return Json.decodeFromJsonElement<Tarball>(tb)
|
||||
return Json.decodeFromJsonElement<ZigVersionInfo.Tarball>(tb)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmRecord
|
||||
@Serializable
|
||||
data class Tarball(val tarball: String, val shasum: String, val size: Int)
|
||||
|
||||
private val tempPluginDir get(): File = PathManager.getTempPath().toNioPathOrNull()!!.resolve("zigbrains").toFile()
|
|
@ -65,8 +65,8 @@ data class LocalZigToolchain(val location: Path, val std: Path? = null, override
|
|||
}
|
||||
}
|
||||
|
||||
fun tryFromPathString(pathStr: String): LocalZigToolchain? {
|
||||
return pathStr.toNioPathOrNull()?.let(::tryFromPath)
|
||||
fun tryFromPathString(pathStr: String?): LocalZigToolchain? {
|
||||
return pathStr?.ifBlank { null }?.toNioPathOrNull()?.let(::tryFromPath)
|
||||
}
|
||||
|
||||
fun tryFromPath(path: Path): LocalZigToolchain? {
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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.downloader.Downloader
|
||||
import com.falsepattern.zigbrains.project.toolchain.downloader.LocalSelector
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import java.awt.Component
|
||||
import java.util.UUID
|
||||
|
||||
internal object ZigToolchainComboBoxHandler {
|
||||
@RequiresBackgroundThread
|
||||
suspend fun onItemSelected(context: Component, elem: TCListElem.Pseudo): UUID? = when(elem) {
|
||||
is TCListElem.Toolchain.Suggested -> elem.toolchain
|
||||
is TCListElem.Download -> Downloader.downloadToolchain(context)
|
||||
is TCListElem.FromDisk -> LocalSelector.browseFromDisk(context)
|
||||
}?.let { ZigToolchainListService.getInstance().registerNewToolchain(it) }
|
||||
}
|
|
@ -25,15 +25,33 @@ 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.falsepattern.zigbrains.shared.coroutine.asContextElement
|
||||
import com.falsepattern.zigbrains.shared.coroutine.launchWithEDT
|
||||
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.options.Configurable
|
||||
import com.intellij.openapi.options.ShowSettingsUtil
|
||||
import com.intellij.openapi.options.newEditor.SettingsDialog
|
||||
import com.intellij.openapi.options.newEditor.SettingsTreeView
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.DialogWrapper
|
||||
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 com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.awt.event.ItemEvent
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Method
|
||||
import java.util.UUID
|
||||
import javax.swing.JComponent
|
||||
import kotlin.collections.addAll
|
||||
|
||||
|
@ -74,7 +92,7 @@ class ZigToolchainEditor(private val project: Project): Configurable {
|
|||
|
||||
inner class UI(): Disposable, ZigToolchainListService.ToolchainListChangeListener {
|
||||
private val toolchainBox: TCComboBox
|
||||
private var oldSelectionIndex: Int = 0
|
||||
private var selectOnNextReload: UUID? = null
|
||||
private val model: TCModel
|
||||
init {
|
||||
model = TCModel(getModelList())
|
||||
|
@ -89,27 +107,44 @@ class ZigToolchainEditor(private val project: Project): Configurable {
|
|||
return
|
||||
}
|
||||
val item = event.item
|
||||
if (item !is TCListElem) {
|
||||
toolchainBox.selectedIndex = oldSelectionIndex
|
||||
if (item !is TCListElem.Pseudo)
|
||||
return
|
||||
}
|
||||
when(item) {
|
||||
is TCListElem.None, is TCListElem.Toolchain.Actual -> {
|
||||
oldSelectionIndex = toolchainBox.selectedIndex
|
||||
}
|
||||
else -> {
|
||||
toolchainBox.selectedIndex = oldSelectionIndex
|
||||
zigCoroutineScope.launch(toolchainBox.asContextElement()) {
|
||||
val uuid = ZigToolchainComboBoxHandler.onItemSelected(toolchainBox, item)
|
||||
withEDTContext(toolchainBox.asContextElement()) {
|
||||
applyUUIDNowOrOnReload(uuid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun toolchainListChanged() {
|
||||
val selected = model.selected
|
||||
override suspend fun toolchainListChanged() {
|
||||
withContext(Dispatchers.EDT + toolchainBox.asContextElement()) {
|
||||
val list = getModelList()
|
||||
model.updateContents(list)
|
||||
val onReload = selectOnNextReload
|
||||
selectOnNextReload = null
|
||||
if (onReload != null) {
|
||||
val element = list.firstOrNull { when(it) {
|
||||
is TCListElem.Toolchain.Actual -> it.uuid == onReload
|
||||
else -> false
|
||||
} }
|
||||
model.selectedItem = element
|
||||
return@withContext
|
||||
}
|
||||
val selected = model.selected
|
||||
if (selected != null && list.contains(selected)) {
|
||||
model.selectedItem = selected
|
||||
} else {
|
||||
return@withContext
|
||||
}
|
||||
if (selected is TCListElem.Toolchain.Actual) {
|
||||
val uuid = selected.uuid
|
||||
val element = list.firstOrNull { when(it) {
|
||||
is TCListElem.Toolchain.Actual -> it.uuid == uuid
|
||||
else -> false
|
||||
} }
|
||||
model.selectedItem = element
|
||||
return@withContext
|
||||
}
|
||||
model.selectedItem = TCListElem.None
|
||||
}
|
||||
}
|
||||
|
@ -117,6 +152,35 @@ class ZigToolchainEditor(private val project: Project): Configurable {
|
|||
fun attach(p: Panel): Unit = with(p) {
|
||||
row("Toolchain") {
|
||||
cell(toolchainBox).resizableColumn().align(AlignX.FILL)
|
||||
button("Funny") { e ->
|
||||
zigCoroutineScope.launchWithEDT(toolchainBox.asContextElement()) {
|
||||
val config = ZigToolchainListEditor()
|
||||
var inited = false
|
||||
var selectedUUID: UUID? = toolchainBox.selectedToolchain
|
||||
config.addItemSelectedListener {
|
||||
if (inited) {
|
||||
selectedUUID = it
|
||||
}
|
||||
}
|
||||
val apply = ShowSettingsUtil.getInstance().editConfigurable(DialogWrapper.findInstance(toolchainBox)?.contentPane, config) {
|
||||
config.selectNodeInTree(selectedUUID)
|
||||
inited = true
|
||||
}
|
||||
if (apply) {
|
||||
applyUUIDNowOrOnReload(selectedUUID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
private fun applyUUIDNowOrOnReload(uuid: UUID?) {
|
||||
toolchainBox.selectedToolchain = uuid
|
||||
if (uuid != null && toolchainBox.selectedToolchain == null) {
|
||||
selectOnNextReload = uuid
|
||||
} else {
|
||||
selectOnNextReload = null
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,9 +194,10 @@ class ZigToolchainEditor(private val project: Project): Configurable {
|
|||
|
||||
fun reset() {
|
||||
toolchainBox.selectedToolchain = ZigToolchainService.getInstance(project).toolchainUUID
|
||||
oldSelectionIndex = toolchainBox.selectedIndex
|
||||
}
|
||||
|
||||
|
||||
|
||||
override fun dispose() {
|
||||
ZigToolchainListService.getInstance().removeChangeListener(this)
|
||||
}
|
||||
|
|
|
@ -22,27 +22,56 @@
|
|||
|
||||
package com.falsepattern.zigbrains.project.toolchain.ui
|
||||
|
||||
import com.falsepattern.zigbrains.Icons
|
||||
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.project.toolchain.downloader.Downloader
|
||||
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
|
||||
import com.falsepattern.zigbrains.shared.coroutine.asContextElement
|
||||
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.EDT
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.intellij.openapi.ui.DialogBuilder
|
||||
import com.intellij.openapi.ui.MasterDetailsComponent
|
||||
import com.intellij.openapi.ui.NamedConfigurable
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.ui.DocumentAdapter
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.textFieldWithBrowseButton
|
||||
import com.intellij.ui.dsl.builder.AlignX
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import com.intellij.util.Consumer
|
||||
import com.intellij.util.IconUtil
|
||||
import com.intellij.util.asSafely
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.UUID
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.event.DocumentEvent
|
||||
import javax.swing.tree.DefaultTreeModel
|
||||
|
||||
class ZigToolchainListEditor() : MasterDetailsComponent(), ZigToolchainListService.ToolchainListChangeListener {
|
||||
class ZigToolchainListEditor : MasterDetailsComponent(), ZigToolchainListService.ToolchainListChangeListener {
|
||||
private var isTreeInitialized = false
|
||||
private var registered: Boolean = false
|
||||
private var itemSelectedListeners = ArrayList<Consumer<UUID?>>()
|
||||
|
||||
fun addItemSelectedListener(c: Consumer<UUID?>) {
|
||||
synchronized(itemSelectedListeners) {
|
||||
itemSelectedListeners.add(c)
|
||||
}
|
||||
}
|
||||
|
||||
override fun createComponent(): JComponent {
|
||||
if (!isTreeInitialized) {
|
||||
|
@ -72,6 +101,14 @@ class ZigToolchainListEditor() : MasterDetailsComponent(), ZigToolchainListServi
|
|||
return listOf(add, MyDeleteAction())
|
||||
}
|
||||
|
||||
override fun updateSelection(configurable: NamedConfigurable<*>?) {
|
||||
super.updateSelection(configurable)
|
||||
val uuid = configurable?.editableObject as? UUID
|
||||
synchronized(itemSelectedListeners) {
|
||||
itemSelectedListeners.forEach { it.consume(uuid) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemDeleted(item: Any?) {
|
||||
if (item is UUID) {
|
||||
ZigToolchainListService.getInstance().removeToolchain(item)
|
||||
|
@ -80,18 +117,15 @@ class ZigToolchainListEditor() : MasterDetailsComponent(), ZigToolchainListServi
|
|||
}
|
||||
|
||||
private fun onItemSelected(elem: TCListElem) {
|
||||
when (elem) {
|
||||
is TCListElem.Toolchain -> {
|
||||
val uuid = UUID.randomUUID()
|
||||
ZigToolchainListService.getInstance().setToolchain(uuid, elem.toolchain)
|
||||
}
|
||||
is TCListElem.Download -> {
|
||||
if (elem !is TCListElem.Pseudo)
|
||||
return
|
||||
zigCoroutineScope.launch(myWholePanel.asContextElement()) {
|
||||
Downloader.downloadToolchain(myWholePanel)
|
||||
val uuid = ZigToolchainComboBoxHandler.onItemSelected(myWholePanel, elem)
|
||||
if (uuid != null) {
|
||||
withEDTContext(myWholePanel.asContextElement()) {
|
||||
selectNodeInTree(uuid)
|
||||
}
|
||||
}
|
||||
is TCListElem.FromDisk -> {}
|
||||
is TCListElem.None -> {}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,11 +144,15 @@ class ZigToolchainListEditor() : MasterDetailsComponent(), ZigToolchainListServi
|
|||
}
|
||||
|
||||
private fun reloadTree() {
|
||||
val currentSelection = selectedObject?.asSafely<UUID>()
|
||||
myRoot.removeAllChildren()
|
||||
ZigToolchainListService.getInstance().toolchains.forEach { (uuid, toolchain) ->
|
||||
addToolchain(uuid, toolchain)
|
||||
}
|
||||
(myTree.model as DefaultTreeModel).reload()
|
||||
currentSelection?.let {
|
||||
selectNodeInTree(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun disposeUIResources() {
|
||||
|
@ -124,7 +162,9 @@ class ZigToolchainListEditor() : MasterDetailsComponent(), ZigToolchainListServi
|
|||
}
|
||||
}
|
||||
|
||||
override fun toolchainListChanged() {
|
||||
override suspend fun toolchainListChanged() {
|
||||
withEDTContext(myWholePanel.asContextElement()) {
|
||||
reloadTree()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,20 +27,20 @@ import java.util.UUID
|
|||
|
||||
|
||||
internal sealed interface TCListElemIn
|
||||
|
||||
internal sealed interface TCListElem : TCListElemIn {
|
||||
sealed interface Pseudo: TCListElem
|
||||
sealed interface Toolchain : TCListElem {
|
||||
val toolchain: ZigToolchain
|
||||
|
||||
@JvmRecord
|
||||
data class Suggested(override val toolchain: ZigToolchain): Toolchain
|
||||
data class Suggested(override val toolchain: ZigToolchain): Toolchain, Pseudo
|
||||
|
||||
@JvmRecord
|
||||
data class Actual(val uuid: UUID, override val toolchain: ZigToolchain): Toolchain
|
||||
}
|
||||
object None: TCListElem
|
||||
object Download : TCListElem
|
||||
object FromDisk : TCListElem
|
||||
object Download : TCListElem, Pseudo
|
||||
object FromDisk : TCListElem, Pseudo
|
||||
|
||||
companion object {
|
||||
val fetchGroup get() = listOf(Download, FromDisk)
|
||||
|
|
|
@ -22,8 +22,9 @@
|
|||
|
||||
package com.falsepattern.zigbrains.shared
|
||||
|
||||
import com.intellij.openapi.progress.EmptyProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.util.io.Decompressor
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.name
|
||||
|
@ -51,11 +52,18 @@ enum class Unarchiver {
|
|||
|
||||
companion object {
|
||||
@Throws(IOException::class)
|
||||
suspend fun unarchive(archivePath: Path, dst: Path, prefix: String? = null) {
|
||||
runInterruptible {
|
||||
fun unarchive(archivePath: Path, dst: Path, prefix: String? = null) {
|
||||
val unarchiver = entries.find { archivePath.name.endsWith(it.extension) }
|
||||
?: error("Unexpected archive type: $archivePath")
|
||||
val dec = unarchiver.createDecompressor(archivePath)
|
||||
val indicator = ProgressManager.getInstance().progressIndicator ?: EmptyProgressIndicator()
|
||||
indicator.isIndeterminate = true
|
||||
indicator.text = "Extracting archive"
|
||||
dec.filter {
|
||||
indicator.text2 = it
|
||||
indicator.checkCanceled()
|
||||
true
|
||||
}
|
||||
if (prefix != null) {
|
||||
dec.removePrefixPath(prefix)
|
||||
}
|
||||
|
@ -63,4 +71,3 @@ enum class Unarchiver {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -63,7 +63,10 @@ suspend inline fun <T> runInterruptibleEDT(context: CoroutineContext, noinline t
|
|||
}
|
||||
|
||||
fun CoroutineScope.launchWithEDT(state: ModalityState, block: suspend CoroutineScope.() -> Unit): Job {
|
||||
return launch(Dispatchers.EDT + state.asContextElement(), block = block)
|
||||
return launchWithEDT(state.asContextElement(), block = block)
|
||||
}
|
||||
fun CoroutineScope.launchWithEDT(context: CoroutineContext, block: suspend CoroutineScope.() -> Unit): Job {
|
||||
return launch(Dispatchers.EDT + context, block = block)
|
||||
}
|
||||
|
||||
fun Component.asContextElement(): CoroutineContext {
|
||||
|
|
Loading…
Add table
Reference in a new issue