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.ui.DialogBuilder
|
||||||
import com.intellij.openapi.util.SystemInfo
|
import com.intellij.openapi.util.SystemInfo
|
||||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||||
|
import com.intellij.platform.util.progress.reportSequentialProgress
|
||||||
import com.intellij.ui.BrowserHyperlinkListener
|
import com.intellij.ui.BrowserHyperlinkListener
|
||||||
import com.intellij.ui.HyperlinkLabel
|
import com.intellij.ui.HyperlinkLabel
|
||||||
import com.intellij.ui.components.JBPanel
|
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.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
|
||||||
|
@ -167,7 +169,9 @@ class ZigDebuggerToolchainService {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
downloadAndUnArchive(baseDir, downloadableBinaries)
|
withContext(Dispatchers.IO) {
|
||||||
|
downloadAndUnArchive(baseDir, downloadableBinaries)
|
||||||
|
}
|
||||||
return DownloadResult.Ok(baseDir)
|
return DownloadResult.Ok(baseDir)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
//TODO logging
|
//TODO logging
|
||||||
|
@ -206,34 +210,40 @@ class ZigDebuggerToolchainService {
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
@RequiresEdt
|
@RequiresEdt
|
||||||
private suspend fun downloadAndUnArchive(baseDir: Path, binariesToDownload: List<DownloadableDebuggerBinary>) {
|
private suspend fun downloadAndUnArchive(baseDir: Path, binariesToDownload: List<DownloadableDebuggerBinary>) {
|
||||||
val service = DownloadableFileService.getInstance()
|
reportSequentialProgress { reporter ->
|
||||||
|
val service = DownloadableFileService.getInstance()
|
||||||
|
|
||||||
val downloadDir = baseDir.toFile()
|
val downloadDir = baseDir.toFile()
|
||||||
downloadDir.deleteRecursively()
|
downloadDir.deleteRecursively()
|
||||||
|
|
||||||
val descriptions = binariesToDownload.map {
|
val descriptions = binariesToDownload.map {
|
||||||
service.createFileDescription(it.url, fileName(it.url))
|
service.createFileDescription(it.url, fileName(it.url))
|
||||||
}
|
|
||||||
|
|
||||||
val downloader = service.createDownloader(descriptions, "Debugger downloading")
|
|
||||||
val downloadDirectory = downloadPath().toFile()
|
|
||||||
val downloadResults = withContext(Dispatchers.IO) {
|
|
||||||
coroutineToIndicator {
|
|
||||||
downloader.download(downloadDirectory)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
val versions = Properties()
|
|
||||||
for (result in downloadResults) {
|
|
||||||
val downloadUrl = result.second.downloadUrl
|
|
||||||
val binaryToDownload = binariesToDownload.first { it.url == downloadUrl }
|
|
||||||
val propertyName = binaryToDownload.propertyName
|
|
||||||
val archiveFile = result.first
|
|
||||||
Unarchiver.unarchive(archiveFile.toPath(), baseDir, binaryToDownload.prefix)
|
|
||||||
archiveFile.delete()
|
|
||||||
versions[propertyName] = binaryToDownload.version
|
|
||||||
}
|
|
||||||
|
|
||||||
saveVersionsFile(baseDir, versions)
|
val downloader = service.createDownloader(descriptions, "Debugger downloading")
|
||||||
|
val downloadDirectory = downloadPath().toFile()
|
||||||
|
val downloadResults = reporter.sizedStep(100) {
|
||||||
|
coroutineToIndicator {
|
||||||
|
downloader.download(downloadDirectory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val versions = Properties()
|
||||||
|
for (result in downloadResults) {
|
||||||
|
val downloadUrl = result.second.downloadUrl
|
||||||
|
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>? {
|
private fun lldbUrls(): Pair<URL, URL>? {
|
||||||
|
|
|
@ -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.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.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
import com.intellij.openapi.components.*
|
import com.intellij.openapi.components.*
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.lang.ref.WeakReference
|
import java.lang.ref.WeakReference
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
@ -37,19 +39,43 @@ import java.util.UUID
|
||||||
class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchainListService.State>(State()) {
|
class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchainListService.State>(State()) {
|
||||||
private val changeListeners = ArrayList<WeakReference<ToolchainListChangeListener>>()
|
private val changeListeners = ArrayList<WeakReference<ToolchainListChangeListener>>()
|
||||||
fun setToolchain(uuid: UUID, toolchain: ZigToolchain) {
|
fun setToolchain(uuid: UUID, toolchain: ZigToolchain) {
|
||||||
|
val str = uuid.toString()
|
||||||
|
val ref = toolchain.toRef()
|
||||||
updateState {
|
updateState {
|
||||||
val newMap = HashMap<String, ZigToolchain.Ref>()
|
val newMap = HashMap<String, ZigToolchain.Ref>()
|
||||||
newMap.putAll(it.toolchains)
|
newMap.putAll(it.toolchains)
|
||||||
newMap[uuid.toString()] = toolchain.toRef()
|
newMap[str] = ref
|
||||||
it.copy(toolchains = newMap)
|
it.copy(toolchains = newMap)
|
||||||
}
|
}
|
||||||
notifyChanged()
|
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? {
|
fun getToolchain(uuid: UUID): ZigToolchain? {
|
||||||
return state.toolchains[uuid.toString()]?.resolve()
|
return state.toolchains[uuid.toString()]?.resolve()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasToolchain(uuid: UUID): Boolean {
|
||||||
|
return state.toolchains.containsKey(uuid.toString())
|
||||||
|
}
|
||||||
|
|
||||||
fun removeToolchain(uuid: UUID) {
|
fun removeToolchain(uuid: UUID) {
|
||||||
val str = uuid.toString()
|
val str = uuid.toString()
|
||||||
updateState {
|
updateState {
|
||||||
|
@ -67,7 +93,9 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
|
||||||
changeListeners.removeAt(i)
|
changeListeners.removeAt(i)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
v.toolchainListChanged()
|
zigCoroutineScope.launch {
|
||||||
|
v.toolchainListChanged()
|
||||||
|
}
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,6 +116,7 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val toolchains: Sequence<Pair<UUID, ZigToolchain>>
|
val toolchains: Sequence<Pair<UUID, ZigToolchain>>
|
||||||
get() = state.toolchains
|
get() = state.toolchains
|
||||||
.asSequence()
|
.asSequence()
|
||||||
|
@ -109,6 +138,6 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
interface ToolchainListChangeListener {
|
interface ToolchainListChangeListener {
|
||||||
fun toolchainListChanged()
|
suspend fun toolchainListChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,16 @@ import java.util.UUID
|
||||||
)
|
)
|
||||||
class ZigToolchainService: SerializablePersistentStateComponent<ZigToolchainService.State>(State()) {
|
class ZigToolchainService: SerializablePersistentStateComponent<ZigToolchainService.State>(State()) {
|
||||||
var toolchainUUID: UUID?
|
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) {
|
set(value) {
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(toolchain = value?.toString() ?: "")
|
it.copy(toolchain = value?.toString() ?: "")
|
||||||
|
@ -49,11 +58,10 @@ class ZigToolchainService: SerializablePersistentStateComponent<ZigToolchainServ
|
||||||
val toolchain: ZigToolchain?
|
val toolchain: ZigToolchain?
|
||||||
get() = toolchainUUID?.let { ZigToolchainListService.getInstance().getToolchain(it) }
|
get() = toolchainUUID?.let { ZigToolchainListService.getInstance().getToolchain(it) }
|
||||||
|
|
||||||
@JvmRecord
|
|
||||||
data class State(
|
data class State(
|
||||||
@JvmField
|
@JvmField
|
||||||
@Attribute
|
@Attribute
|
||||||
val toolchain: String = ""
|
var toolchain: String = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
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/>.
|
* 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.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.asContextElement
|
||||||
import com.falsepattern.zigbrains.shared.coroutine.runInterruptibleEDT
|
import com.falsepattern.zigbrains.shared.coroutine.runInterruptibleEDT
|
||||||
import com.intellij.icons.AllIcons
|
import com.intellij.icons.AllIcons
|
||||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||||
|
import com.intellij.openapi.observable.util.whenFocusGained
|
||||||
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
|
||||||
|
@ -43,95 +45,38 @@ 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 com.intellij.util.asSafely
|
||||||
import kotlinx.coroutines.Dispatchers
|
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.awt.Component
|
import java.awt.Component
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.UUID
|
import java.util.*
|
||||||
import javax.swing.DefaultComboBoxModel
|
import javax.swing.DefaultComboBoxModel
|
||||||
import javax.swing.JList
|
import javax.swing.JList
|
||||||
import javax.swing.event.DocumentEvent
|
import javax.swing.event.DocumentEvent
|
||||||
import kotlin.contracts.ExperimentalContracts
|
|
||||||
import kotlin.io.path.exists
|
|
||||||
import kotlin.io.path.isDirectory
|
|
||||||
|
|
||||||
//TODO lang
|
//TODO lang
|
||||||
object Downloader {
|
object Downloader {
|
||||||
suspend fun downloadToolchain(component: Component): UUID? {
|
suspend fun downloadToolchain(component: Component): ZigToolchain? {
|
||||||
val info = withModalProgress(
|
val info = withModalProgress(
|
||||||
ModalTaskOwner.component(component),
|
ModalTaskOwner.component(component),
|
||||||
"Fetching zig version information",
|
"Fetching zig version information",
|
||||||
TaskCancellation.cancellable()) {
|
TaskCancellation.cancellable()
|
||||||
withContext(Dispatchers.IO) {
|
) {
|
||||||
ZigVersionInfo.downloadVersionList()
|
ZigVersionInfo.downloadVersionList()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
val (downloadPath, version) = runInterruptibleEDT(component.asContextElement()) {
|
val (downloadPath, version) = runInterruptibleEDT(component.asContextElement()) {
|
||||||
selectToolchain(info)
|
selectToolchain(info)
|
||||||
} ?: return null
|
} ?: return null
|
||||||
withModalProgress(
|
withModalProgress(
|
||||||
ModalTaskOwner.component(component),
|
ModalTaskOwner.component(component),
|
||||||
"Downloading zig tarball",
|
"Installing Zig ${version.version}",
|
||||||
TaskCancellation.cancellable()) {
|
TaskCancellation.cancellable()
|
||||||
withContext(Dispatchers.IO) {
|
) {
|
||||||
version.downloadAndUnpack(downloadPath)
|
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>? {
|
private fun selectToolchain(info: List<ZigVersionInfo>): Pair<Path, ZigVersionInfo>? {
|
||||||
val dialog = DialogBuilder()
|
val dialog = DialogBuilder()
|
||||||
val theList = ComboBox(DefaultComboBoxModel(info.toTypedArray()))
|
val theList = ComboBox(DefaultComboBoxModel(info.toTypedArray()))
|
||||||
|
@ -148,32 +93,39 @@ object Downloader {
|
||||||
}
|
}
|
||||||
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"))
|
||||||
)
|
)
|
||||||
Disposer.register(dialog, outputPath)
|
Disposer.register(dialog, outputPath)
|
||||||
outputPath.textField.columns = 50
|
outputPath.textField.columns = 50
|
||||||
|
|
||||||
lateinit var errorMessageBox: JBLabel
|
lateinit var errorMessageBox: JBLabel
|
||||||
|
fun onChanged() {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
outputPath.whenFocusGained {
|
||||||
|
onChanged()
|
||||||
|
}
|
||||||
outputPath.addDocumentListener(object: DocumentAdapter() {
|
outputPath.addDocumentListener(object: DocumentAdapter() {
|
||||||
override fun textChanged(e: DocumentEvent) {
|
override fun textChanged(e: DocumentEvent) {
|
||||||
val path = outputPath.text.ifBlank { null }?.toNioPathOrNull()
|
onChanged()
|
||||||
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
|
||||||
|
@ -200,7 +152,7 @@ object Downloader {
|
||||||
}
|
}
|
||||||
detect(info[0])
|
detect(info[0])
|
||||||
dialog.centerPanel(center)
|
dialog.centerPanel(center)
|
||||||
dialog.setTitle("Version Selector")
|
dialog.setTitle("Zig Downloader")
|
||||||
dialog.addCancelAction()
|
dialog.addCancelAction()
|
||||||
dialog.addOkAction().also { it.setText("Download") }
|
dialog.addOkAction().also { it.setText("Download") }
|
||||||
if (!dialog.showAndGet()) {
|
if (!dialog.showAndGet()) {
|
||||||
|
@ -216,8 +168,4 @@ object Downloader {
|
||||||
|
|
||||||
return path to version
|
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.falsepattern.zigbrains.shared.Unarchiver
|
||||||
import com.intellij.openapi.application.PathManager
|
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.progress.coroutineToIndicator
|
||||||
import com.intellij.openapi.util.io.FileUtil
|
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.*
|
||||||
import com.intellij.platform.util.progress.reportSequentialProgress
|
|
||||||
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.createDirectories
|
||||||
|
@ -38,6 +38,8 @@ 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 com.intellij.util.text.SemVer
|
import com.intellij.util.text.SemVer
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
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
|
||||||
|
@ -47,9 +49,14 @@ 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.io.File
|
||||||
|
import java.lang.IllegalStateException
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
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.isDirectory
|
||||||
|
import kotlin.io.path.name
|
||||||
|
|
||||||
@JvmRecord
|
@JvmRecord
|
||||||
data class ZigVersionInfo(
|
data class ZigVersionInfo(
|
||||||
|
@ -60,108 +67,145 @@ data class ZigVersionInfo(
|
||||||
val src: Tarball?,
|
val src: Tarball?,
|
||||||
val dist: Tarball
|
val dist: Tarball
|
||||||
) {
|
) {
|
||||||
suspend fun downloadAndUnpack(into: Path): Boolean {
|
@Throws(Exception::class)
|
||||||
return reportProgress { reporter ->
|
suspend fun downloadAndUnpack(into: Path) {
|
||||||
try {
|
reportProgress { reporter ->
|
||||||
into.createDirectories()
|
into.createDirectories()
|
||||||
} catch (e: Exception) {
|
val tarball = downloadTarball(dist, into, reporter)
|
||||||
return@reportProgress false
|
unpackTarball(tarball, into, reporter)
|
||||||
}
|
tarball.delete()
|
||||||
val service = DownloadableFileService.getInstance()
|
flattenDownloadDir(into, reporter)
|
||||||
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 {
|
companion object {
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
suspend fun downloadVersionList(): List<ZigVersionInfo> {
|
suspend fun downloadVersionList(): List<ZigVersionInfo> {
|
||||||
val service = DownloadableFileService.getInstance()
|
return withContext(Dispatchers.IO) {
|
||||||
val tempFile = FileUtil.createTempFile(tempPluginDir, "index", ".json", false, false)
|
val service = DownloadableFileService.getInstance()
|
||||||
val desc = service.createFileDescription("https://ziglang.org/download/index.json", tempFile.name)
|
val tempFile = FileUtil.createTempFile(tempPluginDir, "index", ".json", false, false)
|
||||||
val downloader = service.createDownloader(listOf(desc), "Zig version information downloading")
|
val desc = service.createFileDescription("https://ziglang.org/download/index.json", tempFile.name)
|
||||||
val downloadResults = coroutineToIndicator {
|
val downloader = service.createDownloader(listOf(desc), "Zig version information")
|
||||||
downloader.download(tempPluginDir)
|
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()
|
||||||
}
|
}
|
||||||
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? {
|
@JvmRecord
|
||||||
if (data !is JsonObject)
|
@Serializable
|
||||||
return null
|
data class Tarball(val tarball: String, val shasum: String, val size: Int)
|
||||||
|
}
|
||||||
|
|
||||||
val versionTag = data["version"]?.asSafely<JsonPrimitive>()?.content
|
private suspend fun downloadTarball(dist: ZigVersionInfo.Tarball, into: Path, reporter: ProgressReporter): Path {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
val version = SemVer.parseFromText(versionTag) ?: SemVer.parseFromText(versionKey)
|
val service = DownloadableFileService.getInstance()
|
||||||
?: return null
|
val fileName = dist.tarball.substringAfterLast('/')
|
||||||
val date = data["date"]?.asSafely<JsonPrimitive>()?.content ?: ""
|
val tempFile = FileUtil.createTempFile(into.toFile(), "tarball", fileName, false, false)
|
||||||
val docs = data["docs"]?.asSafely<JsonPrimitive>()?.content ?: ""
|
val desc = service.createFileDescription(dist.tarball, tempFile.name)
|
||||||
val notes = data["notes"]?.asSafely<JsonPrimitive>()?.content?: ""
|
val downloader = service.createDownloader(listOf(desc), "Zig tarball")
|
||||||
val src = data["src"]?.asSafely<JsonObject>()?.let { Json.decodeFromJsonElement<Tarball>(it) }
|
val downloadResults = reporter.sizedStep(100) {
|
||||||
val dist = data.firstNotNullOfOrNull { (dist, tb) -> getTarballIfCompatible(dist, tb) }
|
coroutineToIndicator {
|
||||||
?: return null
|
downloader.download(into.toFile())
|
||||||
|
}
|
||||||
|
|
||||||
return ZigVersionInfo(version, date, docs, notes, src, dist)
|
|
||||||
}
|
}
|
||||||
|
if (downloadResults.isEmpty())
|
||||||
|
throw IllegalStateException("No file downloaded")
|
||||||
|
return@withContext downloadResults[0].first.toPath()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getTarballIfCompatible(dist: String, tb: JsonElement): Tarball? {
|
private suspend fun flattenDownloadDir(dir: Path, reporter: ProgressReporter) {
|
||||||
if (!dist.contains('-'))
|
withContext(Dispatchers.IO) {
|
||||||
return null
|
val contents = Files.newDirectoryStream(dir).use { it.toList() }
|
||||||
val (arch, os) = dist.split('-', limit = 2)
|
if (contents.size == 1 && contents[0].isDirectory()) {
|
||||||
val theArch = when (arch) {
|
val src = contents[0]
|
||||||
"x86_64" -> CpuArch.X86_64
|
reporter.indeterminateStep {
|
||||||
"i386" -> CpuArch.X86
|
coroutineToIndicator {
|
||||||
"armv7a" -> CpuArch.ARM32
|
val indicator = ProgressManager.getInstance().progressIndicator ?: EmptyProgressIndicator()
|
||||||
"aarch64" -> CpuArch.ARM64
|
indicator.isIndeterminate = true
|
||||||
else -> return null
|
indicator.text = "Flattening directory"
|
||||||
|
Files.newDirectoryStream(src).use { stream ->
|
||||||
|
stream.forEach {
|
||||||
|
indicator.text2 = it.name
|
||||||
|
it.move(dir.resolve(src.relativize(it)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val theOS = when (os) {
|
src.delete()
|
||||||
"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
|
@OptIn(ExperimentalPathApi::class)
|
||||||
@Serializable
|
private suspend fun unpackTarball(tarball: Path, into: Path, reporter: ProgressReporter) {
|
||||||
data class Tarball(val tarball: String, val shasum: String, val size: Int)
|
withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
reporter.indeterminateStep {
|
||||||
|
coroutineToIndicator {
|
||||||
|
Unarchiver.unarchive(tarball, into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseVersion(versionKey: String, data: JsonElement): ZigVersionInfo? {
|
||||||
|
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 docs = data["docs"]?.asSafely<JsonPrimitive>()?.content ?: ""
|
||||||
|
val notes = data["notes"]?.asSafely<JsonPrimitive>()?.content ?: ""
|
||||||
|
val src = data["src"]?.asSafely<JsonObject>()?.let { Json.decodeFromJsonElement<ZigVersionInfo.Tarball>(it) }
|
||||||
|
val dist = data.firstNotNullOfOrNull { (dist, tb) -> getTarballIfCompatible(dist, tb) }
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
|
||||||
|
return ZigVersionInfo(version, date, docs, notes, src, dist)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getTarballIfCompatible(dist: String, tb: JsonElement): ZigVersionInfo.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<ZigVersionInfo.Tarball>(tb)
|
||||||
|
}
|
||||||
|
|
||||||
private val tempPluginDir get(): File = PathManager.getTempPath().toNioPathOrNull()!!.resolve("zigbrains").toFile()
|
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? {
|
fun tryFromPathString(pathStr: String?): LocalZigToolchain? {
|
||||||
return pathStr.toNioPathOrNull()?.let(::tryFromPath)
|
return pathStr?.ifBlank { null }?.toNioPathOrNull()?.let(::tryFromPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun tryFromPath(path: Path): LocalZigToolchain? {
|
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.ZigToolchainListService
|
||||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
|
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
|
||||||
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.coroutine.launchWithEDT
|
||||||
|
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||||
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
import com.intellij.openapi.Disposable
|
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.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.project.Project
|
||||||
|
import com.intellij.openapi.ui.DialogWrapper
|
||||||
import com.intellij.openapi.util.Disposer
|
import com.intellij.openapi.util.Disposer
|
||||||
import com.intellij.openapi.util.NlsContexts
|
import com.intellij.openapi.util.NlsContexts
|
||||||
import com.intellij.ui.dsl.builder.AlignX
|
import com.intellij.ui.dsl.builder.AlignX
|
||||||
import com.intellij.ui.dsl.builder.Panel
|
import com.intellij.ui.dsl.builder.Panel
|
||||||
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.awt.event.ItemEvent
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
import java.util.UUID
|
||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
import kotlin.collections.addAll
|
import kotlin.collections.addAll
|
||||||
|
|
||||||
|
@ -74,7 +92,7 @@ class ZigToolchainEditor(private val project: Project): Configurable {
|
||||||
|
|
||||||
inner class UI(): Disposable, ZigToolchainListService.ToolchainListChangeListener {
|
inner class UI(): Disposable, ZigToolchainListService.ToolchainListChangeListener {
|
||||||
private val toolchainBox: TCComboBox
|
private val toolchainBox: TCComboBox
|
||||||
private var oldSelectionIndex: Int = 0
|
private var selectOnNextReload: UUID? = null
|
||||||
private val model: TCModel
|
private val model: TCModel
|
||||||
init {
|
init {
|
||||||
model = TCModel(getModelList())
|
model = TCModel(getModelList())
|
||||||
|
@ -89,27 +107,44 @@ class ZigToolchainEditor(private val project: Project): Configurable {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val item = event.item
|
val item = event.item
|
||||||
if (item !is TCListElem) {
|
if (item !is TCListElem.Pseudo)
|
||||||
toolchainBox.selectedIndex = oldSelectionIndex
|
|
||||||
return
|
return
|
||||||
}
|
zigCoroutineScope.launch(toolchainBox.asContextElement()) {
|
||||||
when(item) {
|
val uuid = ZigToolchainComboBoxHandler.onItemSelected(toolchainBox, item)
|
||||||
is TCListElem.None, is TCListElem.Toolchain.Actual -> {
|
withEDTContext(toolchainBox.asContextElement()) {
|
||||||
oldSelectionIndex = toolchainBox.selectedIndex
|
applyUUIDNowOrOnReload(uuid)
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
toolchainBox.selectedIndex = oldSelectionIndex
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toolchainListChanged() {
|
override suspend fun toolchainListChanged() {
|
||||||
val selected = model.selected
|
withContext(Dispatchers.EDT + toolchainBox.asContextElement()) {
|
||||||
val list = getModelList()
|
val list = getModelList()
|
||||||
model.updateContents(list)
|
model.updateContents(list)
|
||||||
if (selected != null && list.contains(selected)) {
|
val onReload = selectOnNextReload
|
||||||
model.selectedItem = selected
|
selectOnNextReload = null
|
||||||
} else {
|
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
|
||||||
|
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
|
model.selectedItem = TCListElem.None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,6 +152,35 @@ class ZigToolchainEditor(private val project: Project): Configurable {
|
||||||
fun attach(p: Panel): Unit = with(p) {
|
fun attach(p: Panel): Unit = with(p) {
|
||||||
row("Toolchain") {
|
row("Toolchain") {
|
||||||
cell(toolchainBox).resizableColumn().align(AlignX.FILL)
|
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() {
|
fun reset() {
|
||||||
toolchainBox.selectedToolchain = ZigToolchainService.getInstance(project).toolchainUUID
|
toolchainBox.selectedToolchain = ZigToolchainService.getInstance(project).toolchainUUID
|
||||||
oldSelectionIndex = toolchainBox.selectedIndex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
ZigToolchainListService.getInstance().removeChangeListener(this)
|
ZigToolchainListService.getInstance().removeChangeListener(this)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,27 +22,56 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.project.toolchain.ui
|
package com.falsepattern.zigbrains.project.toolchain.ui
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.Icons
|
||||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
|
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.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.asContextElement
|
||||||
|
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
|
import com.intellij.icons.AllIcons
|
||||||
import com.intellij.openapi.actionSystem.AnAction
|
import com.intellij.openapi.actionSystem.AnAction
|
||||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||||
import com.intellij.openapi.actionSystem.Presentation
|
import com.intellij.openapi.actionSystem.Presentation
|
||||||
|
import com.intellij.openapi.application.EDT
|
||||||
|
import com.intellij.openapi.components.Service
|
||||||
|
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||||
import com.intellij.openapi.project.DumbAwareAction
|
import com.intellij.openapi.project.DumbAwareAction
|
||||||
|
import com.intellij.openapi.ui.DialogBuilder
|
||||||
import com.intellij.openapi.ui.MasterDetailsComponent
|
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.IconUtil
|
||||||
|
import com.intellij.util.asSafely
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
|
import javax.swing.event.DocumentEvent
|
||||||
import javax.swing.tree.DefaultTreeModel
|
import javax.swing.tree.DefaultTreeModel
|
||||||
|
|
||||||
class ZigToolchainListEditor() : MasterDetailsComponent(), ZigToolchainListService.ToolchainListChangeListener {
|
class ZigToolchainListEditor : MasterDetailsComponent(), ZigToolchainListService.ToolchainListChangeListener {
|
||||||
private var isTreeInitialized = false
|
private var isTreeInitialized = false
|
||||||
private var registered: Boolean = 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 {
|
override fun createComponent(): JComponent {
|
||||||
if (!isTreeInitialized) {
|
if (!isTreeInitialized) {
|
||||||
|
@ -72,6 +101,14 @@ class ZigToolchainListEditor() : MasterDetailsComponent(), ZigToolchainListServi
|
||||||
return listOf(add, MyDeleteAction())
|
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?) {
|
override fun onItemDeleted(item: Any?) {
|
||||||
if (item is UUID) {
|
if (item is UUID) {
|
||||||
ZigToolchainListService.getInstance().removeToolchain(item)
|
ZigToolchainListService.getInstance().removeToolchain(item)
|
||||||
|
@ -80,18 +117,15 @@ class ZigToolchainListEditor() : MasterDetailsComponent(), ZigToolchainListServi
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onItemSelected(elem: TCListElem) {
|
private fun onItemSelected(elem: TCListElem) {
|
||||||
when (elem) {
|
if (elem !is TCListElem.Pseudo)
|
||||||
is TCListElem.Toolchain -> {
|
return
|
||||||
val uuid = UUID.randomUUID()
|
zigCoroutineScope.launch(myWholePanel.asContextElement()) {
|
||||||
ZigToolchainListService.getInstance().setToolchain(uuid, elem.toolchain)
|
val uuid = ZigToolchainComboBoxHandler.onItemSelected(myWholePanel, elem)
|
||||||
}
|
if (uuid != null) {
|
||||||
is TCListElem.Download -> {
|
withEDTContext(myWholePanel.asContextElement()) {
|
||||||
zigCoroutineScope.launch(myWholePanel.asContextElement()) {
|
selectNodeInTree(uuid)
|
||||||
Downloader.downloadToolchain(myWholePanel)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is TCListElem.FromDisk -> {}
|
|
||||||
is TCListElem.None -> {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,11 +144,15 @@ class ZigToolchainListEditor() : MasterDetailsComponent(), ZigToolchainListServi
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun reloadTree() {
|
private fun reloadTree() {
|
||||||
|
val currentSelection = selectedObject?.asSafely<UUID>()
|
||||||
myRoot.removeAllChildren()
|
myRoot.removeAllChildren()
|
||||||
ZigToolchainListService.getInstance().toolchains.forEach { (uuid, toolchain) ->
|
ZigToolchainListService.getInstance().toolchains.forEach { (uuid, toolchain) ->
|
||||||
addToolchain(uuid, toolchain)
|
addToolchain(uuid, toolchain)
|
||||||
}
|
}
|
||||||
(myTree.model as DefaultTreeModel).reload()
|
(myTree.model as DefaultTreeModel).reload()
|
||||||
|
currentSelection?.let {
|
||||||
|
selectNodeInTree(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun disposeUIResources() {
|
override fun disposeUIResources() {
|
||||||
|
@ -124,7 +162,9 @@ class ZigToolchainListEditor() : MasterDetailsComponent(), ZigToolchainListServi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toolchainListChanged() {
|
override suspend fun toolchainListChanged() {
|
||||||
reloadTree()
|
withEDTContext(myWholePanel.asContextElement()) {
|
||||||
|
reloadTree()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -27,20 +27,20 @@ import java.util.UUID
|
||||||
|
|
||||||
|
|
||||||
internal sealed interface TCListElemIn
|
internal sealed interface TCListElemIn
|
||||||
|
|
||||||
internal sealed interface TCListElem : TCListElemIn {
|
internal sealed interface TCListElem : TCListElemIn {
|
||||||
|
sealed interface Pseudo: TCListElem
|
||||||
sealed interface Toolchain : TCListElem {
|
sealed interface Toolchain : TCListElem {
|
||||||
val toolchain: ZigToolchain
|
val toolchain: ZigToolchain
|
||||||
|
|
||||||
@JvmRecord
|
@JvmRecord
|
||||||
data class Suggested(override val toolchain: ZigToolchain): Toolchain
|
data class Suggested(override val toolchain: ZigToolchain): Toolchain, Pseudo
|
||||||
|
|
||||||
@JvmRecord
|
@JvmRecord
|
||||||
data class Actual(val uuid: UUID, override val toolchain: ZigToolchain): Toolchain
|
data class Actual(val uuid: UUID, override val toolchain: ZigToolchain): Toolchain
|
||||||
}
|
}
|
||||||
object None: TCListElem
|
object None: TCListElem
|
||||||
object Download : TCListElem
|
object Download : TCListElem, Pseudo
|
||||||
object FromDisk : TCListElem
|
object FromDisk : TCListElem, Pseudo
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val fetchGroup get() = listOf(Download, FromDisk)
|
val fetchGroup get() = listOf(Download, FromDisk)
|
||||||
|
|
|
@ -22,8 +22,9 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.shared
|
package com.falsepattern.zigbrains.shared
|
||||||
|
|
||||||
|
import com.intellij.openapi.progress.EmptyProgressIndicator
|
||||||
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
import com.intellij.util.io.Decompressor
|
import com.intellij.util.io.Decompressor
|
||||||
import kotlinx.coroutines.runInterruptible
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.name
|
import kotlin.io.path.name
|
||||||
|
@ -51,16 +52,22 @@ enum class Unarchiver {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
suspend fun unarchive(archivePath: Path, dst: Path, prefix: String? = null) {
|
fun unarchive(archivePath: Path, dst: Path, prefix: String? = null) {
|
||||||
runInterruptible {
|
val unarchiver = entries.find { archivePath.name.endsWith(it.extension) }
|
||||||
val unarchiver = entries.find { archivePath.name.endsWith(it.extension) }
|
?: error("Unexpected archive type: $archivePath")
|
||||||
?: error("Unexpected archive type: $archivePath")
|
val dec = unarchiver.createDecompressor(archivePath)
|
||||||
val dec = unarchiver.createDecompressor(archivePath)
|
val indicator = ProgressManager.getInstance().progressIndicator ?: EmptyProgressIndicator()
|
||||||
if (prefix != null) {
|
indicator.isIndeterminate = true
|
||||||
dec.removePrefixPath(prefix)
|
indicator.text = "Extracting archive"
|
||||||
}
|
dec.filter {
|
||||||
dec.extract(dst)
|
indicator.text2 = it
|
||||||
|
indicator.checkCanceled()
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
if (prefix != null) {
|
||||||
|
dec.removePrefixPath(prefix)
|
||||||
|
}
|
||||||
|
dec.extract(dst)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -63,7 +63,10 @@ suspend inline fun <T> runInterruptibleEDT(context: CoroutineContext, noinline t
|
||||||
}
|
}
|
||||||
|
|
||||||
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 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 {
|
fun Component.asContextElement(): CoroutineContext {
|
||||||
|
|
Loading…
Add table
Reference in a new issue