Working change sync and downloader
This commit is contained in:
parent
9023026478
commit
9676b70821
9 changed files with 437 additions and 134 deletions
|
@ -23,6 +23,7 @@
|
||||||
package com.falsepattern.zigbrains.debugger.toolchain
|
package com.falsepattern.zigbrains.debugger.toolchain
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.debugger.ZigDebugBundle
|
import com.falsepattern.zigbrains.debugger.ZigDebugBundle
|
||||||
|
import com.falsepattern.zigbrains.shared.Unarchiver
|
||||||
import com.intellij.notification.Notification
|
import com.intellij.notification.Notification
|
||||||
import com.intellij.notification.NotificationType
|
import com.intellij.notification.NotificationType
|
||||||
import com.intellij.openapi.application.PathManager
|
import com.intellij.openapi.application.PathManager
|
||||||
|
@ -40,14 +41,12 @@ import com.intellij.ui.components.JBPanel
|
||||||
import com.intellij.util.application
|
import com.intellij.util.application
|
||||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||||
import com.intellij.util.download.DownloadableFileService
|
import com.intellij.util.download.DownloadableFileService
|
||||||
import com.intellij.util.io.Decompressor
|
|
||||||
import com.intellij.util.system.CpuArch
|
import com.intellij.util.system.CpuArch
|
||||||
import com.intellij.util.system.OS
|
import com.intellij.util.system.OS
|
||||||
import com.jetbrains.cidr.execution.debugger.CidrDebuggerPathManager
|
import com.jetbrains.cidr.execution.debugger.CidrDebuggerPathManager
|
||||||
import com.jetbrains.cidr.execution.debugger.backend.bin.UrlProvider
|
import com.jetbrains.cidr.execution.debugger.backend.bin.UrlProvider
|
||||||
import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBDriverConfiguration
|
import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBDriverConfiguration
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.runInterruptible
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
@ -329,38 +328,6 @@ class ZigDebuggerToolchainService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum class Unarchiver {
|
|
||||||
ZIP {
|
|
||||||
override val extension = "zip"
|
|
||||||
override fun createDecompressor(file: Path) = Decompressor.Zip(file)
|
|
||||||
},
|
|
||||||
TAR {
|
|
||||||
override val extension = "tar.gz"
|
|
||||||
override fun createDecompressor(file: Path) = Decompressor.Tar(file)
|
|
||||||
},
|
|
||||||
VSIX {
|
|
||||||
override val extension = "vsix"
|
|
||||||
override fun createDecompressor(file: Path) = Decompressor.Zip(file)
|
|
||||||
};
|
|
||||||
|
|
||||||
protected abstract val extension: String
|
|
||||||
protected abstract fun createDecompressor(file: Path): Decompressor
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
@Throws(IOException::class)
|
|
||||||
suspend fun unarchive(archivePath: Path, dst: Path, prefix: String? = null) {
|
|
||||||
runInterruptible {
|
|
||||||
val unarchiver = entries.find { archivePath.name.endsWith(it.extension) } ?: error("Unexpected archive type: $archivePath")
|
|
||||||
val dec = unarchiver.createDecompressor(archivePath)
|
|
||||||
if (prefix != null) {
|
|
||||||
dec.removePrefixPath(prefix)
|
|
||||||
}
|
|
||||||
dec.extract(dst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class DownloadResult {
|
sealed class DownloadResult {
|
||||||
class Ok(val baseDir: Path): DownloadResult()
|
class Ok(val baseDir: Path): DownloadResult()
|
||||||
data object NoUrls: DownloadResult()
|
data object NoUrls: DownloadResult()
|
||||||
|
|
|
@ -26,6 +26,7 @@ import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||||
import com.falsepattern.zigbrains.project.toolchain.base.resolve
|
import com.falsepattern.zigbrains.project.toolchain.base.resolve
|
||||||
import com.falsepattern.zigbrains.project.toolchain.base.toRef
|
import com.falsepattern.zigbrains.project.toolchain.base.toRef
|
||||||
import com.intellij.openapi.components.*
|
import com.intellij.openapi.components.*
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@Service(Service.Level.APP)
|
@Service(Service.Level.APP)
|
||||||
|
@ -34,13 +35,15 @@ import java.util.UUID
|
||||||
storages = [Storage("zigbrains.xml")]
|
storages = [Storage("zigbrains.xml")]
|
||||||
)
|
)
|
||||||
class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchainListService.State>(State()) {
|
class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchainListService.State>(State()) {
|
||||||
|
private val changeListeners = ArrayList<WeakReference<ToolchainListChangeListener>>()
|
||||||
fun setToolchain(uuid: UUID, toolchain: ZigToolchain) {
|
fun setToolchain(uuid: UUID, toolchain: ZigToolchain) {
|
||||||
updateState {
|
updateState {
|
||||||
val newMap = HashMap<String, ZigToolchain.Ref>()
|
val newMap = HashMap<String, ZigToolchain.Ref>()
|
||||||
newMap.putAll(it.toolchains)
|
newMap.putAll(it.toolchains)
|
||||||
newMap.put(uuid.toString(), toolchain.toRef())
|
newMap[uuid.toString()] = toolchain.toRef()
|
||||||
it.copy(toolchains = newMap)
|
it.copy(toolchains = newMap)
|
||||||
}
|
}
|
||||||
|
notifyChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getToolchain(uuid: UUID): ZigToolchain? {
|
fun getToolchain(uuid: UUID): ZigToolchain? {
|
||||||
|
@ -52,6 +55,37 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(toolchains = it.toolchains.filter { it.key != str })
|
it.copy(toolchains = it.toolchains.filter { it.key != str })
|
||||||
}
|
}
|
||||||
|
notifyChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notifyChanged() {
|
||||||
|
synchronized(changeListeners) {
|
||||||
|
var i = 0
|
||||||
|
while (i < changeListeners.size) {
|
||||||
|
val v = changeListeners[i].get()
|
||||||
|
if (v == null) {
|
||||||
|
changeListeners.removeAt(i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v.toolchainListChanged()
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addChangeListener(listener: ToolchainListChangeListener) {
|
||||||
|
synchronized(changeListeners) {
|
||||||
|
changeListeners.add(WeakReference(listener))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeChangeListener(listener: ToolchainListChangeListener) {
|
||||||
|
synchronized(changeListeners) {
|
||||||
|
changeListeners.removeIf {
|
||||||
|
val v = it.get()
|
||||||
|
v == null || v === listener
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val toolchains: Sequence<Pair<UUID, ZigToolchain>>
|
val toolchains: Sequence<Pair<UUID, ZigToolchain>>
|
||||||
|
@ -72,4 +106,9 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getInstance(): ZigToolchainListService = service()
|
fun getInstance(): ZigToolchainListService = service()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface ToolchainListChangeListener {
|
||||||
|
fun toolchainListChanged()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,16 +22,22 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.project.toolchain.downloader
|
package com.falsepattern.zigbrains.project.toolchain.downloader
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.shared.Unarchiver
|
||||||
import com.intellij.openapi.application.PathManager
|
import com.intellij.openapi.application.PathManager
|
||||||
import com.intellij.openapi.progress.coroutineToIndicator
|
import com.intellij.openapi.progress.coroutineToIndicator
|
||||||
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
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.withProgressText
|
||||||
import com.intellij.util.asSafely
|
import com.intellij.util.asSafely
|
||||||
import com.intellij.util.download.DownloadableFileService
|
import com.intellij.util.download.DownloadableFileService
|
||||||
|
import com.intellij.util.io.createDirectories
|
||||||
|
import com.intellij.util.io.delete
|
||||||
|
import com.intellij.util.io.move
|
||||||
import com.intellij.util.system.CpuArch
|
import com.intellij.util.system.CpuArch
|
||||||
import com.intellij.util.system.OS
|
import com.intellij.util.system.OS
|
||||||
import kotlinx.coroutines.Dispatchers
|
import com.intellij.util.text.SemVer
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
@ -40,46 +46,92 @@ import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import kotlinx.serialization.json.decodeFromJsonElement
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
import kotlinx.serialization.json.decodeFromStream
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.isDirectory
|
||||||
|
|
||||||
@JvmRecord
|
@JvmRecord
|
||||||
data class ZigVersionInfo(val date: String, val docs: String, val notes: String, val src: Tarball?, val dist: Tarball) {
|
data class ZigVersionInfo(
|
||||||
companion object {
|
val version: SemVer,
|
||||||
suspend fun download(): List<Pair<String, ZigVersionInfo>> {
|
val date: String,
|
||||||
return withProgressText("Fetching zig version information") {
|
val docs: String,
|
||||||
withContext(Dispatchers.IO) {
|
val notes: String,
|
||||||
doDownload()
|
val src: Tarball?,
|
||||||
|
val dist: Tarball
|
||||||
|
) {
|
||||||
|
suspend fun downloadAndUnpack(into: Path): Boolean {
|
||||||
|
return reportProgress { reporter ->
|
||||||
|
try {
|
||||||
|
into.createDirectories()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
return@reportProgress false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
|
||||||
private suspend fun doDownload(): List<Pair<String, ZigVersionInfo>> {
|
|
||||||
val service = DownloadableFileService.getInstance()
|
val service = DownloadableFileService.getInstance()
|
||||||
val desc = service.createFileDescription("https://ziglang.org/download/index.json", "index.json")
|
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 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() }
|
||||||
|
if (contents.size == 1 && contents[0].isDirectory()) {
|
||||||
|
val src = contents[0]
|
||||||
|
Files.newDirectoryStream(src).use { stream ->
|
||||||
|
stream.forEach {
|
||||||
|
it.move(into.resolve(src.relativize(it)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
src.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return@reportProgress true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 downloader = service.createDownloader(listOf(desc), "Zig version information downloading")
|
||||||
val downloadDirectory = tempPluginDir.toFile()
|
|
||||||
val downloadResults = coroutineToIndicator {
|
val downloadResults = coroutineToIndicator {
|
||||||
downloader.download(downloadDirectory)
|
downloader.download(tempPluginDir)
|
||||||
}
|
}
|
||||||
var info: JsonObject? = null
|
if (downloadResults.isEmpty())
|
||||||
for (result in downloadResults) {
|
return emptyList()
|
||||||
if (result.second.defaultFileName == "index.json") {
|
val index = downloadResults[0].first
|
||||||
info = result.first.inputStream().use { Json.decodeFromStream<JsonObject>(it) }
|
val info = index.inputStream().use { Json.decodeFromStream<JsonObject>(it) }
|
||||||
}
|
index.delete()
|
||||||
}
|
return info.mapNotNull { (version, data) -> parseVersion(version, data) }.toList()
|
||||||
return info?.mapNotNull { (version, data) -> parseVersion(data)?.let { Pair(version, it) } }?.toList() ?: emptyList()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseVersion(data: JsonElement): ZigVersionInfo? {
|
private fun parseVersion(versionKey: String, data: JsonElement): ZigVersionInfo? {
|
||||||
data as? JsonObject ?: return null
|
if (data !is JsonObject)
|
||||||
|
return null
|
||||||
|
|
||||||
|
val versionTag = data["version"]?.asSafely<JsonPrimitive>()?.content
|
||||||
|
|
||||||
|
val version = SemVer.parseFromText(versionTag) ?: SemVer.parseFromText(versionKey)
|
||||||
|
?: return null
|
||||||
val date = data["date"]?.asSafely<JsonPrimitive>()?.content ?: ""
|
val date = data["date"]?.asSafely<JsonPrimitive>()?.content ?: ""
|
||||||
val docs = data["docs"]?.asSafely<JsonPrimitive>()?.content ?: ""
|
val docs = data["docs"]?.asSafely<JsonPrimitive>()?.content ?: ""
|
||||||
val notes = data["notes"]?.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<Tarball>(it) }
|
||||||
val dist = data.firstNotNullOfOrNull { (dist, tb) -> getTarballIfCompatible(dist, tb) } ?: return null
|
val dist = data.firstNotNullOfOrNull { (dist, tb) -> getTarballIfCompatible(dist, tb) }
|
||||||
|
?: return null
|
||||||
|
|
||||||
return ZigVersionInfo(date, docs, notes, src, dist)
|
|
||||||
|
return ZigVersionInfo(version, date, docs, notes, src, dist)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getTarballIfCompatible(dist: String, tb: JsonElement): Tarball? {
|
private fun getTarballIfCompatible(dist: String, tb: JsonElement): Tarball? {
|
||||||
|
@ -112,4 +164,4 @@ data class ZigVersionInfo(val date: String, val docs: String, val notes: String,
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Tarball(val tarball: String, val shasum: String, val size: Int)
|
data class Tarball(val tarball: String, val shasum: String, val size: Int)
|
||||||
|
|
||||||
private val tempPluginDir get(): Path = PathManager.getTempPath().toNioPathOrNull()!!.resolve("zigbrains")
|
private val tempPluginDir get(): File = PathManager.getTempPath().toNioPathOrNull()!!.resolve("zigbrains").toFile()
|
|
@ -24,49 +24,167 @@ package com.falsepattern.zigbrains.project.toolchain.ui
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
import com.falsepattern.zigbrains.project.toolchain.downloader.ZigVersionInfo
|
import com.falsepattern.zigbrains.project.toolchain.downloader.ZigVersionInfo
|
||||||
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
import com.falsepattern.zigbrains.shared.coroutine.asContextElement
|
||||||
import com.intellij.openapi.application.ModalityState
|
import com.falsepattern.zigbrains.shared.coroutine.runInterruptibleEDT
|
||||||
|
import com.intellij.icons.AllIcons
|
||||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||||
import com.intellij.openapi.ui.ComboBox
|
import com.intellij.openapi.ui.ComboBox
|
||||||
import com.intellij.openapi.ui.DialogBuilder
|
import com.intellij.openapi.ui.DialogBuilder
|
||||||
import com.intellij.openapi.util.Disposer
|
import com.intellij.openapi.util.Disposer
|
||||||
|
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||||
import com.intellij.platform.ide.progress.ModalTaskOwner
|
import com.intellij.platform.ide.progress.ModalTaskOwner
|
||||||
import com.intellij.platform.ide.progress.TaskCancellation
|
import com.intellij.platform.ide.progress.TaskCancellation
|
||||||
import com.intellij.platform.ide.progress.withModalProgress
|
import com.intellij.platform.ide.progress.withModalProgress
|
||||||
|
import com.intellij.ui.ColoredListCellRenderer
|
||||||
|
import com.intellij.ui.DocumentAdapter
|
||||||
|
import com.intellij.ui.components.JBLabel
|
||||||
import com.intellij.ui.components.textFieldWithBrowseButton
|
import com.intellij.ui.components.textFieldWithBrowseButton
|
||||||
import com.intellij.ui.dsl.builder.AlignX
|
import com.intellij.ui.dsl.builder.AlignX
|
||||||
import com.intellij.ui.dsl.builder.Cell
|
import com.intellij.ui.dsl.builder.Cell
|
||||||
import com.intellij.ui.dsl.builder.panel
|
import com.intellij.ui.dsl.builder.panel
|
||||||
|
import com.intellij.util.asSafely
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.awt.Component
|
import java.awt.Component
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.util.UUID
|
||||||
import javax.swing.DefaultComboBoxModel
|
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 {
|
object Downloader {
|
||||||
suspend fun openDownloadDialog(component: Component) {
|
suspend fun downloadToolchain(component: Component): UUID? {
|
||||||
val info = withModalProgress(
|
val info = withModalProgress(
|
||||||
component.let { ModalTaskOwner.component(it) },
|
ModalTaskOwner.component(component),
|
||||||
"Fetching zig version information",
|
"Fetching zig version information",
|
||||||
TaskCancellation.Companion.cancellable()) {
|
TaskCancellation.cancellable()) {
|
||||||
ZigVersionInfo.Companion.download()
|
withContext(Dispatchers.IO) {
|
||||||
|
ZigVersionInfo.downloadVersionList()
|
||||||
}
|
}
|
||||||
withEDTContext(ModalityState.stateForComponent(component)) {
|
}
|
||||||
|
val (downloadPath, version) = runInterruptibleEDT(component.asContextElement()) {
|
||||||
|
selectToolchain(info)
|
||||||
|
} ?: return null
|
||||||
|
withModalProgress(
|
||||||
|
ModalTaskOwner.component(component),
|
||||||
|
"Downloading zig tarball",
|
||||||
|
TaskCancellation.cancellable()) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun selectToolchain(info: List<ZigVersionInfo>): Pair<Path, ZigVersionInfo>? {
|
||||||
val dialog = DialogBuilder()
|
val dialog = DialogBuilder()
|
||||||
val theList = ComboBox<String>(DefaultComboBoxModel(info.map { it.first }.toTypedArray()))
|
val theList = ComboBox(DefaultComboBoxModel(info.toTypedArray()))
|
||||||
|
theList.renderer = object: ColoredListCellRenderer<ZigVersionInfo>() {
|
||||||
|
override fun customizeCellRenderer(
|
||||||
|
list: JList<out ZigVersionInfo>,
|
||||||
|
value: ZigVersionInfo?,
|
||||||
|
index: Int,
|
||||||
|
selected: Boolean,
|
||||||
|
hasFocus: Boolean
|
||||||
|
) {
|
||||||
|
value?.let { append(it.version.rawVersion) }
|
||||||
|
}
|
||||||
|
}
|
||||||
val outputPath = textFieldWithBrowseButton(
|
val outputPath = textFieldWithBrowseButton(
|
||||||
null,
|
null,
|
||||||
FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.zig-toolchain"))
|
FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.zig-toolchain"))
|
||||||
).also {
|
)
|
||||||
Disposer.register(dialog, it)
|
Disposer.register(dialog, outputPath)
|
||||||
|
outputPath.textField.columns = 50
|
||||||
|
|
||||||
|
lateinit var errorMessageBox: JBLabel
|
||||||
|
outputPath.addDocumentListener(object: DocumentAdapter() {
|
||||||
|
override fun textChanged(e: DocumentEvent) {
|
||||||
|
val path = outputPath.text.ifBlank { null }?.toNioPathOrNull()
|
||||||
|
val state = DirectoryState.determine(path)
|
||||||
|
if (state.isValid()) {
|
||||||
|
errorMessageBox.icon = AllIcons.General.Information
|
||||||
|
dialog.setOkActionEnabled(true)
|
||||||
|
} else {
|
||||||
|
errorMessageBox.icon = AllIcons.General.Error
|
||||||
|
dialog.setOkActionEnabled(false)
|
||||||
}
|
}
|
||||||
|
errorMessageBox.text = when(state) {
|
||||||
|
DirectoryState.Invalid -> "Invalid path"
|
||||||
|
DirectoryState.NotAbsolute -> "Must be an absolute path"
|
||||||
|
DirectoryState.NotDirectory -> "Path is not a directory"
|
||||||
|
DirectoryState.NotEmpty -> "Directory is not empty"
|
||||||
|
DirectoryState.CreateNew -> "Directory will be created"
|
||||||
|
DirectoryState.Ok -> "Directory OK"
|
||||||
|
}
|
||||||
|
dialog.window.repaint()
|
||||||
|
}
|
||||||
|
})
|
||||||
var archiveSizeCell: Cell<*>? = null
|
var archiveSizeCell: Cell<*>? = null
|
||||||
fun detect(item: String) {
|
fun detect(item: ZigVersionInfo) {
|
||||||
outputPath.text = System.getProperty("user.home") + "/.zig/" + item
|
outputPath.text = System.getProperty("user.home") + "/.zig/" + item.version
|
||||||
val data = info.firstOrNull { it.first == item } ?: return
|
val size = item.dist.size
|
||||||
val size = data.second.dist.size
|
|
||||||
val sizeMb = size / (1024f * 1024f)
|
val sizeMb = size / (1024f * 1024f)
|
||||||
archiveSizeCell?.comment?.text = "Archive size: %.2fMB".format(sizeMb)
|
archiveSizeCell?.comment?.text = "Archive size: %.2fMB".format(sizeMb)
|
||||||
}
|
}
|
||||||
theList.addItemListener {
|
theList.addItemListener {
|
||||||
detect(it.item as String)
|
detect(it.item as ZigVersionInfo)
|
||||||
}
|
}
|
||||||
val center = panel {
|
val center = panel {
|
||||||
row("Version:") {
|
row("Version:") {
|
||||||
|
@ -75,13 +193,31 @@ object Downloader {
|
||||||
row("Location:") {
|
row("Location:") {
|
||||||
cell(outputPath).resizableColumn().align(AlignX.FILL).apply { archiveSizeCell = comment("") }
|
cell(outputPath).resizableColumn().align(AlignX.FILL).apply { archiveSizeCell = comment("") }
|
||||||
}
|
}
|
||||||
|
row {
|
||||||
|
errorMessageBox = JBLabel()
|
||||||
|
cell(errorMessageBox)
|
||||||
}
|
}
|
||||||
detect(info[0].first)
|
}
|
||||||
|
detect(info[0])
|
||||||
dialog.centerPanel(center)
|
dialog.centerPanel(center)
|
||||||
dialog.setTitle("Version Selector")
|
dialog.setTitle("Version Selector")
|
||||||
dialog.addCancelAction()
|
dialog.addCancelAction()
|
||||||
dialog.showAndGet()
|
dialog.addOkAction().also { it.setText("Download") }
|
||||||
Disposer.dispose(dialog)
|
if (!dialog.showAndGet()) {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
val path = outputPath.text.ifBlank { null }?.toNioPathOrNull()
|
||||||
|
?: return null
|
||||||
|
if (!DirectoryState.determine(path).isValid()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val version = theList.selectedItem?.asSafely<ZigVersionInfo>()
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
return path to version
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun installToolchain(path: Path, version: ZigVersionInfo): Boolean {
|
||||||
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -72,19 +72,15 @@ class ZigToolchainEditor(private val project: Project): Configurable {
|
||||||
super.disposeUIResources()
|
super.disposeUIResources()
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class UI(): Disposable {
|
inner class UI(): Disposable, ZigToolchainListService.ToolchainListChangeListener {
|
||||||
private val toolchainBox: TCComboBox
|
private val toolchainBox: TCComboBox
|
||||||
private var oldSelectionIndex: Int = 0
|
private var oldSelectionIndex: Int = 0
|
||||||
|
private val model: TCModel
|
||||||
init {
|
init {
|
||||||
val modelList = ArrayList<TCListElemIn>()
|
model = TCModel(getModelList())
|
||||||
modelList.add(TCListElem.None)
|
toolchainBox = TCComboBox(model)
|
||||||
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)
|
toolchainBox.addItemListener(::itemStateChanged)
|
||||||
|
ZigToolchainListService.getInstance().addChangeListener(this)
|
||||||
reset()
|
reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,6 +103,16 @@ class ZigToolchainEditor(private val project: Project): Configurable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toolchainListChanged() {
|
||||||
|
val selected = model.selected
|
||||||
|
val list = getModelList()
|
||||||
|
model.updateContents(list)
|
||||||
|
if (selected != null && list.contains(selected)) {
|
||||||
|
model.selectedItem = selected
|
||||||
|
} else {
|
||||||
|
model.selectedItem = TCListElem.None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun attach(p: Panel): Unit = with(p) {
|
fun attach(p: Panel): Unit = with(p) {
|
||||||
row("Toolchain") {
|
row("Toolchain") {
|
||||||
|
@ -115,20 +121,32 @@ class ZigToolchainEditor(private val project: Project): Configurable {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isModified(): Boolean {
|
fun isModified(): Boolean {
|
||||||
return ZigToolchainService.Companion.getInstance(project).toolchainUUID != toolchainBox.selectedToolchain
|
return ZigToolchainService.getInstance(project).toolchainUUID != toolchainBox.selectedToolchain
|
||||||
}
|
}
|
||||||
|
|
||||||
fun apply() {
|
fun apply() {
|
||||||
ZigToolchainService.Companion.getInstance(project).toolchainUUID = toolchainBox.selectedToolchain
|
ZigToolchainService.getInstance(project).toolchainUUID = toolchainBox.selectedToolchain
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
toolchainBox.selectedToolchain = ZigToolchainService.Companion.getInstance(project).toolchainUUID
|
toolchainBox.selectedToolchain = ZigToolchainService.getInstance(project).toolchainUUID
|
||||||
oldSelectionIndex = toolchainBox.selectedIndex
|
oldSelectionIndex = toolchainBox.selectedIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
|
ZigToolchainListService.getInstance().removeChangeListener(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun getModelList(): List<TCListElemIn> {
|
||||||
|
val modelList = ArrayList<TCListElemIn>()
|
||||||
|
modelList.add(TCListElem.None)
|
||||||
|
modelList.addAll(ZigToolchainListService.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() })
|
||||||
|
return modelList
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
|
||||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||||
import com.falsepattern.zigbrains.project.toolchain.base.createNamedConfigurable
|
import com.falsepattern.zigbrains.project.toolchain.base.createNamedConfigurable
|
||||||
import com.falsepattern.zigbrains.project.toolchain.base.suggestZigToolchains
|
import com.falsepattern.zigbrains.project.toolchain.base.suggestZigToolchains
|
||||||
|
import com.falsepattern.zigbrains.shared.coroutine.asContextElement
|
||||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
import com.intellij.openapi.actionSystem.AnAction
|
import com.intellij.openapi.actionSystem.AnAction
|
||||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||||
|
@ -34,23 +35,25 @@ import com.intellij.openapi.actionSystem.Presentation
|
||||||
import com.intellij.openapi.project.DumbAwareAction
|
import com.intellij.openapi.project.DumbAwareAction
|
||||||
import com.intellij.openapi.ui.MasterDetailsComponent
|
import com.intellij.openapi.ui.MasterDetailsComponent
|
||||||
import com.intellij.util.IconUtil
|
import com.intellij.util.IconUtil
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.launch
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
import javax.swing.tree.DefaultTreeModel
|
import javax.swing.tree.DefaultTreeModel
|
||||||
|
|
||||||
class ZigToolchainListEditor() : MasterDetailsComponent() {
|
class ZigToolchainListEditor() : MasterDetailsComponent(), ZigToolchainListService.ToolchainListChangeListener {
|
||||||
private var isTreeInitialized = false
|
private var isTreeInitialized = false
|
||||||
private var myComponent: JComponent? = null
|
private var registered: Boolean = false
|
||||||
|
|
||||||
override fun createComponent(): JComponent {
|
override fun createComponent(): JComponent {
|
||||||
if (!isTreeInitialized) {
|
if (!isTreeInitialized) {
|
||||||
initTree()
|
initTree()
|
||||||
isTreeInitialized = true
|
isTreeInitialized = true
|
||||||
}
|
}
|
||||||
val comp = super.createComponent()
|
if (!registered) {
|
||||||
myComponent = comp
|
ZigToolchainListService.getInstance().addChangeListener(this)
|
||||||
return comp
|
registered = true
|
||||||
|
}
|
||||||
|
return super.createComponent()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun createActions(fromPopup: Boolean): List<AnAction> {
|
override fun createActions(fromPopup: Boolean): List<AnAction> {
|
||||||
|
@ -81,12 +84,10 @@ class ZigToolchainListEditor() : MasterDetailsComponent() {
|
||||||
is TCListElem.Toolchain -> {
|
is TCListElem.Toolchain -> {
|
||||||
val uuid = UUID.randomUUID()
|
val uuid = UUID.randomUUID()
|
||||||
ZigToolchainListService.getInstance().setToolchain(uuid, elem.toolchain)
|
ZigToolchainListService.getInstance().setToolchain(uuid, elem.toolchain)
|
||||||
addToolchain(uuid, elem.toolchain)
|
|
||||||
(myTree.model as DefaultTreeModel).reload()
|
|
||||||
}
|
}
|
||||||
is TCListElem.Download -> {
|
is TCListElem.Download -> {
|
||||||
zigCoroutineScope.async {
|
zigCoroutineScope.launch(myWholePanel.asContextElement()) {
|
||||||
Downloader.openDownloadDialog(myComponent!!)
|
Downloader.downloadToolchain(myWholePanel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is TCListElem.FromDisk -> {}
|
is TCListElem.FromDisk -> {}
|
||||||
|
@ -118,6 +119,12 @@ class ZigToolchainListEditor() : MasterDetailsComponent() {
|
||||||
|
|
||||||
override fun disposeUIResources() {
|
override fun disposeUIResources() {
|
||||||
super.disposeUIResources()
|
super.disposeUIResources()
|
||||||
myComponent = null
|
if (registered) {
|
||||||
|
ZigToolchainListService.getInstance().removeChangeListener(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toolchainListChanged() {
|
||||||
|
reloadTree()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -85,9 +85,15 @@ internal class TCComboBox(model: TCModel): ComboBox<TCListElem>(model) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class TCModel private constructor(elements: List<TCListElem>, private val separators: Map<TCListElem, Separator>) : CollectionComboBoxModel<TCListElem>(elements) {
|
internal class TCModel private constructor(elements: List<TCListElem>, private var separators: Map<TCListElem, Separator>) : CollectionComboBoxModel<TCListElem>(elements) {
|
||||||
companion object {
|
companion object {
|
||||||
operator fun invoke(input: List<TCListElemIn>): TCModel {
|
operator fun invoke(input: List<TCListElemIn>): TCModel {
|
||||||
|
val (elements, separators) = convert(input)
|
||||||
|
val model = TCModel(elements, separators)
|
||||||
|
return model
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convert(input: List<TCListElemIn>): Pair<List<TCListElem>, Map<TCListElem, Separator>> {
|
||||||
val separators = IdentityHashMap<TCListElem, Separator>()
|
val separators = IdentityHashMap<TCListElem, Separator>()
|
||||||
var lastSeparator: Separator? = null
|
var lastSeparator: Separator? = null
|
||||||
val elements = ArrayList<TCListElem>()
|
val elements = ArrayList<TCListElem>()
|
||||||
|
@ -104,12 +110,17 @@ internal class TCModel private constructor(elements: List<TCListElem>, private v
|
||||||
is Separator -> lastSeparator = it
|
is Separator -> lastSeparator = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val model = TCModel(elements, separators)
|
return elements to separators
|
||||||
return model
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun separatorAbove(elem: TCListElem) = separators[elem]
|
fun separatorAbove(elem: TCListElem) = separators[elem]
|
||||||
|
|
||||||
|
fun updateContents(input: List<TCListElemIn>) {
|
||||||
|
val (elements, separators) = convert(input)
|
||||||
|
this.separators = separators
|
||||||
|
replaceAll(elements)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class TCContext(private val project: Project?, private val model: TCModel) : ComboBoxPopup.Context<TCListElem> {
|
internal class TCContext(private val project: Project?, private val model: TCModel) : ComboBoxPopup.Context<TCListElem> {
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
/*
|
||||||
|
* 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.shared
|
||||||
|
|
||||||
|
import com.intellij.util.io.Decompressor
|
||||||
|
import kotlinx.coroutines.runInterruptible
|
||||||
|
import java.io.IOException
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.name
|
||||||
|
|
||||||
|
enum class Unarchiver {
|
||||||
|
ZIP {
|
||||||
|
override val extension = "zip"
|
||||||
|
override fun createDecompressor(file: Path) = Decompressor.Zip(file)
|
||||||
|
},
|
||||||
|
TAR_GZ {
|
||||||
|
override val extension = "tar.gz"
|
||||||
|
override fun createDecompressor(file: Path) = Decompressor.Tar(file)
|
||||||
|
},
|
||||||
|
TAR_XZ {
|
||||||
|
override val extension = "tar.xz"
|
||||||
|
override fun createDecompressor(file: Path) = Decompressor.Tar(file)
|
||||||
|
},
|
||||||
|
VSIX {
|
||||||
|
override val extension = "vsix"
|
||||||
|
override fun createDecompressor(file: Path) = Decompressor.Zip(file)
|
||||||
|
};
|
||||||
|
|
||||||
|
protected abstract val extension: String
|
||||||
|
protected abstract fun createDecompressor(file: Path): Decompressor
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Throws(IOException::class)
|
||||||
|
suspend fun unarchive(archivePath: Path, dst: Path, prefix: String? = null) {
|
||||||
|
runInterruptible {
|
||||||
|
val unarchiver = entries.find { archivePath.name.endsWith(it.extension) }
|
||||||
|
?: error("Unexpected archive type: $archivePath")
|
||||||
|
val dec = unarchiver.createDecompressor(archivePath)
|
||||||
|
if (prefix != null) {
|
||||||
|
dec.removePrefixPath(prefix)
|
||||||
|
}
|
||||||
|
dec.extract(dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,6 +30,8 @@ import com.intellij.platform.ide.progress.TaskCancellation
|
||||||
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
|
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
|
||||||
import com.intellij.util.application
|
import com.intellij.util.application
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import java.awt.Component
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
inline fun <T> runModalOrBlocking(taskOwnerFactory: () -> ModalTaskOwner, titleFactory: () -> String, cancellationFactory: () -> TaskCancellation = {TaskCancellation.cancellable()}, noinline action: suspend CoroutineScope.() -> T): T {
|
inline fun <T> runModalOrBlocking(taskOwnerFactory: () -> ModalTaskOwner, titleFactory: () -> String, cancellationFactory: () -> TaskCancellation = {TaskCancellation.cancellable()}, noinline action: suspend CoroutineScope.() -> T): T {
|
||||||
return if (application.isDispatchThread) {
|
return if (application.isDispatchThread) {
|
||||||
|
@ -40,7 +42,11 @@ inline fun <T> runModalOrBlocking(taskOwnerFactory: () -> ModalTaskOwner, titleF
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend inline fun <T> withEDTContext(state: ModalityState, noinline block: suspend CoroutineScope.() -> T): T {
|
suspend inline fun <T> withEDTContext(state: ModalityState, noinline block: suspend CoroutineScope.() -> T): T {
|
||||||
return withContext(Dispatchers.EDT + state.asContextElement(), block = block)
|
return withEDTContext(state.asContextElement(), block = block)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend inline fun <T> withEDTContext(context: CoroutineContext, noinline block: suspend CoroutineScope.() -> T): T {
|
||||||
|
return withContext(Dispatchers.EDT + context, block = block)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend inline fun <T> withCurrentEDTModalityContext(noinline block: suspend CoroutineScope.() -> T): T {
|
suspend inline fun <T> withCurrentEDTModalityContext(noinline block: suspend CoroutineScope.() -> T): T {
|
||||||
|
@ -49,16 +55,17 @@ suspend inline fun <T> withCurrentEDTModalityContext(noinline block: suspend Cor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T, R> T.letBlocking(targetAction: suspend CoroutineScope.(T) -> R): R {
|
|
||||||
return runBlocking {
|
|
||||||
targetAction(this@letBlocking)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend inline fun <T> runInterruptibleEDT(state: ModalityState, noinline targetAction: () -> T): T {
|
suspend inline fun <T> runInterruptibleEDT(state: ModalityState, noinline targetAction: () -> T): T {
|
||||||
return runInterruptible(Dispatchers.EDT + state.asContextElement(), block = targetAction)
|
return runInterruptibleEDT(state.asContextElement(), targetAction = targetAction)
|
||||||
|
}
|
||||||
|
suspend inline fun <T> runInterruptibleEDT(context: CoroutineContext, noinline targetAction: () -> T): T {
|
||||||
|
return runInterruptible(Dispatchers.EDT + context, block = targetAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CoroutineScope.launchWithEDT(state: ModalityState, block: suspend CoroutineScope.() -> Unit): Job {
|
fun CoroutineScope.launchWithEDT(state: ModalityState, block: suspend CoroutineScope.() -> Unit): Job {
|
||||||
return launch(Dispatchers.EDT + state.asContextElement(), block = block)
|
return launch(Dispatchers.EDT + state.asContextElement(), block = block)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Component.asContextElement(): CoroutineContext {
|
||||||
|
return ModalityState.stateForComponent(this).asContextElement()
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue