From 54cd5142493048c2e960d4d8259e046ea787535d Mon Sep 17 00:00:00 2001 From: FalsePattern Date: Wed, 9 Apr 2025 18:29:06 +0200 Subject: [PATCH] local zig toolchain name, edit button state, sorted recommends --- .../toolchain/base/ZigToolchainProvider.kt | 8 +-- .../toolchain/downloader/Downloader.kt | 2 +- .../toolchain/downloader/LocalSelector.kt | 67 +++++++++++++------ .../toolchain/local/LocalZigToolchain.kt | 18 +++-- .../local/LocalZigToolchainProvider.kt | 3 +- .../toolchain/ui/ZigToolchainEditor.kt | 20 ++++-- .../resources/zigbrains/Bundle.properties | 3 +- 7 files changed, 79 insertions(+), 42 deletions(-) diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/base/ZigToolchainProvider.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/base/ZigToolchainProvider.kt index 43f3df41..fb960bb0 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/base/ZigToolchainProvider.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/base/ZigToolchainProvider.kt @@ -25,9 +25,9 @@ package com.falsepattern.zigbrains.project.toolchain.base import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.NamedConfigurable import com.intellij.openapi.util.UserDataHolder import com.intellij.ui.SimpleColoredComponent +import com.intellij.util.text.SemVer import java.util.UUID private val EXTENSION_POINT_NAME = ExtensionPointName.create("com.falsepattern.zigbrains.toolchainProvider") @@ -41,7 +41,7 @@ internal interface ZigToolchainProvider { fun serialize(toolchain: ZigToolchain): Map fun matchesSuggestion(toolchain: ZigToolchain, suggestion: ZigToolchain): Boolean fun createConfigurable(uuid: UUID, toolchain: ZigToolchain): ZigToolchainConfigurable<*> - fun suggestToolchains(): List + fun suggestToolchains(): List> fun render(toolchain: ZigToolchain, component: SimpleColoredComponent, isSuggestion: Boolean) } @@ -71,8 +71,8 @@ fun suggestZigToolchains(): List { return EXTENSION_POINT_NAME.extensionList.flatMap { ext -> val compatibleExisting = existing.filter { ext.isCompatible(it) } val suggestions = ext.suggestToolchains() - suggestions.filter { suggestion -> compatibleExisting.none { existing -> ext.matchesSuggestion(existing, suggestion) } } - } + suggestions.filter { suggestion -> compatibleExisting.none { existing -> ext.matchesSuggestion(existing, suggestion.second) } } + }.sortedByDescending { it.first }.map { it.second } } fun ZigToolchain.render(component: SimpleColoredComponent, isSuggestion: Boolean) { diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/downloader/Downloader.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/downloader/Downloader.kt index 91513600..8bde14f2 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/downloader/Downloader.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/downloader/Downloader.kt @@ -74,7 +74,7 @@ object Downloader { ) { version.downloadAndUnpack(downloadPath) } - return LocalZigToolchain.tryFromPath(downloadPath) + return LocalZigToolchain.tryFromPath(downloadPath)?.second } @RequiresEdt diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/downloader/LocalSelector.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/downloader/LocalSelector.kt index 900006da..3ebea410 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/downloader/LocalSelector.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/downloader/LocalSelector.kt @@ -30,11 +30,13 @@ 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.FileChooser 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.JBTextField import com.intellij.ui.components.textFieldWithBrowseButton import com.intellij.ui.dsl.builder.AlignX import com.intellij.ui.dsl.builder.panel @@ -52,39 +54,50 @@ object LocalSelector { @RequiresEdt private fun doBrowseFromDisk(): ZigToolchain? { val dialog = DialogBuilder() + val name = JBTextField().also { it.columns = 25 } val path = textFieldWithBrowseButton( null, FileChooserDescriptorFactory.createSingleFolderDescriptor() .withTitle(ZigBrainsBundle.message("settings.toolchain.local-selector.chooser.title")) ) Disposer.register(dialog, path) - path.textField.columns = 50 lateinit var errorMessageBox: JBLabel + fun verify(path: String) { + val tc = LocalZigToolchain.tryFromPathString(path)?.second + if (tc == null) { + errorMessageBox.icon = AllIcons.General.Error + errorMessageBox.text = ZigBrainsBundle.message("settings.toolchain.local-selector.state.invalid") + 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 { ZigBrainsBundle.message("settings.toolchain.local-selector.state.already-exists-named", it) } + ?: ZigBrainsBundle.message("settings.toolchain.local-selector.state.already-exists-unnamed") + dialog.setOkActionEnabled(true) + } else { + errorMessageBox.icon = AllIcons.General.Information + errorMessageBox.text = ZigBrainsBundle.message("settings.toolchain.local-selector.state.ok") + dialog.setOkActionEnabled(true) + } + val prevNameDefault = name.emptyText.text.trim() == name.text.trim() || name.text.isBlank() + name.emptyText.text = tc?.name ?: "" + if (prevNameDefault) { + name.text = name.emptyText.text + } + } 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 = ZigBrainsBundle.message("settings.toolchain.local-selector.state.invalid") - 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 { ZigBrainsBundle.message("settings.toolchain.local-selector.state.already-exists-named", it) } - ?: ZigBrainsBundle.message("settings.toolchain.local-selector.state.already-exists-unnamed") - dialog.setOkActionEnabled(true) - } else { - errorMessageBox.icon = Icons.Zig - errorMessageBox.text = tc.name ?: ZigBrainsBundle.message("settings.toolchain.local-selector.state.ok") - dialog.setOkActionEnabled(true) - } + verify(path.text) } }) val center = panel { + row(ZigBrainsBundle.message("settings.toolchain.local-selector.name.label")) { + cell(name).resizableColumn().align(AlignX.FILL) + } row(ZigBrainsBundle.message("settings.toolchain.local-selector.path.label")) { cell(path).resizableColumn().align(AlignX.FILL) } @@ -97,9 +110,19 @@ object LocalSelector { dialog.setTitle(ZigBrainsBundle.message("settings.toolchain.local-selector.title")) dialog.addCancelAction() dialog.addOkAction().also { it.setText(ZigBrainsBundle.message("settings.toolchain.local-selector.ok-action")) } + val chosenFile = FileChooser.chooseFile( + FileChooserDescriptorFactory.createSingleFolderDescriptor() + .withTitle(ZigBrainsBundle.message("settings.toolchain.local-selector.chooser.title")), + null, + null + ) + if (chosenFile != null) { + verify(chosenFile.path) + path.text = chosenFile.path + } if (!dialog.showAndGet()) { return null } - return LocalZigToolchain.tryFromPathString(path.text) + return LocalZigToolchain.tryFromPathString(path.text)?.second?.also { it.copy(name = name.text.ifBlank { null } ?: it.name) } } } \ No newline at end of file diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/local/LocalZigToolchain.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/local/LocalZigToolchain.kt index b3073705..66a2a9a1 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/local/LocalZigToolchain.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/local/LocalZigToolchain.kt @@ -32,6 +32,7 @@ import com.intellij.openapi.util.KeyWithDefaultValue import com.intellij.openapi.util.SystemInfo import com.intellij.openapi.util.io.toNioPathOrNull import com.intellij.openapi.vfs.toNioPathOrNull +import com.intellij.util.text.SemVer import java.nio.file.Path @JvmRecord @@ -66,22 +67,27 @@ data class LocalZigToolchain(val location: Path, val std: Path? = null, override } } - fun tryFromPathString(pathStr: String?): LocalZigToolchain? { + fun tryFromPathString(pathStr: String?): Pair? { return pathStr?.ifBlank { null }?.toNioPathOrNull()?.let(::tryFromPath) } - fun tryFromPath(path: Path): LocalZigToolchain? { + fun tryFromPath(path: Path): Pair? { var tc = LocalZigToolchain(path) if (!tc.zig.fileValid()) { return null } - tc.zig + val versionStr = tc.zig .getEnvBlocking(null) .getOrNull() ?.version - ?.let { "Zig $it" } - ?.let { tc = tc.copy(name = it) } - return tc + val version: SemVer? + if (versionStr != null) { + version = SemVer.parseFromText(versionStr) + tc = tc.copy(name = "Zig $versionStr") + } else { + version = null + } + return version to tc } } } \ No newline at end of file diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/local/LocalZigToolchainProvider.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/local/LocalZigToolchainProvider.kt index e1ae5f80..dc6e54ce 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/local/LocalZigToolchainProvider.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/local/LocalZigToolchainProvider.kt @@ -37,6 +37,7 @@ import com.intellij.ui.SimpleColoredComponent import com.intellij.ui.SimpleTextAttributes import com.intellij.util.EnvironmentUtil import com.intellij.util.system.OS +import com.intellij.util.text.SemVer import java.io.File import java.nio.file.Files import java.nio.file.Path @@ -97,7 +98,7 @@ class LocalZigToolchainProvider: ZigToolchainProvider { return LocalZigToolchainConfigurable(uuid, toolchain) } - override fun suggestToolchains(): List { + override fun suggestToolchains(): List> { val res = HashSet() EnvironmentUtil.getValue("PATH")?.split(File.pathSeparatorChar)?.let { res.addAll(it.toList()) } val wellKnown = getWellKnown() diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ui/ZigToolchainEditor.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ui/ZigToolchainEditor.kt index e8add1f4..99c6bcd1 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ui/ZigToolchainEditor.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ui/ZigToolchainEditor.kt @@ -52,7 +52,7 @@ class ZigToolchainEditor(private val isForDefaultProject: Boolean = false): SubC private val toolchainBox: TCComboBox private var selectOnNextReload: UUID? = null private val model: TCModel - private lateinit var editButton: JButton + private var editButton: JButton? = null init { model = TCModel(getModelList()) toolchainBox = TCComboBox(model) @@ -60,15 +60,21 @@ class ZigToolchainEditor(private val isForDefaultProject: Boolean = false): SubC ZigToolchainListService.getInstance().addChangeListener(this) } + private fun refreshButtonState(item: Any?) { + editButton?.isEnabled = item is TCListElem.Toolchain.Actual + editButton?.repaint() + } + private fun itemStateChanged(event: ItemEvent) { if (event.stateChange != ItemEvent.SELECTED) { return } val item = event.item + refreshButtonState(item) if (item !is TCListElem.Pseudo) return zigCoroutineScope.launch(toolchainBox.asContextElement()) { - val uuid = ZigToolchainComboBoxHandler.onItemSelected(toolchainBox, item) + val uuid = runCatching { ZigToolchainComboBoxHandler.onItemSelected(toolchainBox, item) }.getOrNull() withEDTContext(toolchainBox.asContextElement()) { applyUUIDNowOrOnReload(uuid) } @@ -105,10 +111,6 @@ class ZigToolchainEditor(private val isForDefaultProject: Boolean = false): SubC } model.selectedItem = TCListElem.None } - withContext(Dispatchers.EDT + editButton.asContextElement()) { - editButton.isEnabled = model.selectedItem is TCListElem.Toolchain.Actual - editButton.repaint() - } } override fun attach(p: Panel): Unit = with(p) { @@ -129,7 +131,10 @@ class ZigToolchainEditor(private val isForDefaultProject: Boolean = false): SubC applyUUIDNowOrOnReload(selectedUUID) } } - }.component.let { editButton = it } + }.component.let { + editButton = it + refreshButtonState(toolchainBox.selectedItem) + } } } @@ -154,6 +159,7 @@ class ZigToolchainEditor(private val isForDefaultProject: Boolean = false): SubC override fun reset(context: Project?) { val project = context ?: ProjectManager.getInstance().defaultProject toolchainBox.selectedToolchain = ZigToolchainService.getInstance(project).toolchainUUID + refreshButtonState(toolchainBox.selectedItem) } override fun dispose() { diff --git a/core/src/main/resources/zigbrains/Bundle.properties b/core/src/main/resources/zigbrains/Bundle.properties index 00957a4b..0dfb36b3 100644 --- a/core/src/main/resources/zigbrains/Bundle.properties +++ b/core/src/main/resources/zigbrains/Bundle.properties @@ -143,10 +143,11 @@ settings.toolchain.downloader.archive-size.text=Archive size: {0} settings.toolchain.downloader.service.index=Zig version information settings.toolchain.downloader.service.tarball=Zig archive settings.toolchain.local-selector.title=Select Zig From Disk +settings.toolchain.local-selector.name.label=Name: settings.toolchain.local-selector.path.label=Path: settings.toolchain.local-selector.ok-action=Add settings.toolchain.local-selector.chooser.title=Existing Zig Install Directory settings.toolchain.local-selector.state.invalid=Invalid toolchain path settings.toolchain.local-selector.state.already-exists-unnamed=Toolchain already exists settings.toolchain.local-selector.state.already-exists-named=Toolchain already exists as "{0}" -settings.toolchain.local-selector.state.ok=OK +settings.toolchain.local-selector.state.ok=Toolchain path OK