local zig toolchain name, edit button state, sorted recommends

This commit is contained in:
FalsePattern 2025-04-09 18:29:06 +02:00
parent a8f97172d6
commit 54cd514249
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
7 changed files with 79 additions and 42 deletions

View file

@ -25,9 +25,9 @@ package com.falsepattern.zigbrains.project.toolchain.base
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.NamedConfigurable
import com.intellij.openapi.util.UserDataHolder import com.intellij.openapi.util.UserDataHolder
import com.intellij.ui.SimpleColoredComponent import com.intellij.ui.SimpleColoredComponent
import com.intellij.util.text.SemVer
import java.util.UUID import java.util.UUID
private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigToolchainProvider>("com.falsepattern.zigbrains.toolchainProvider") private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigToolchainProvider>("com.falsepattern.zigbrains.toolchainProvider")
@ -41,7 +41,7 @@ internal interface ZigToolchainProvider {
fun serialize(toolchain: ZigToolchain): Map<String, String> fun serialize(toolchain: ZigToolchain): Map<String, String>
fun matchesSuggestion(toolchain: ZigToolchain, suggestion: ZigToolchain): Boolean fun matchesSuggestion(toolchain: ZigToolchain, suggestion: ZigToolchain): Boolean
fun createConfigurable(uuid: UUID, toolchain: ZigToolchain): ZigToolchainConfigurable<*> fun createConfigurable(uuid: UUID, toolchain: ZigToolchain): ZigToolchainConfigurable<*>
fun suggestToolchains(): List<ZigToolchain> fun suggestToolchains(): List<Pair<SemVer?, ZigToolchain>>
fun render(toolchain: ZigToolchain, component: SimpleColoredComponent, isSuggestion: Boolean) fun render(toolchain: ZigToolchain, component: SimpleColoredComponent, isSuggestion: Boolean)
} }
@ -71,8 +71,8 @@ fun suggestZigToolchains(): List<ZigToolchain> {
return EXTENSION_POINT_NAME.extensionList.flatMap { ext -> return EXTENSION_POINT_NAME.extensionList.flatMap { ext ->
val compatibleExisting = existing.filter { ext.isCompatible(it) } val compatibleExisting = existing.filter { ext.isCompatible(it) }
val suggestions = ext.suggestToolchains() 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) { fun ZigToolchain.render(component: SimpleColoredComponent, isSuggestion: Boolean) {

View file

@ -74,7 +74,7 @@ object Downloader {
) { ) {
version.downloadAndUnpack(downloadPath) version.downloadAndUnpack(downloadPath)
} }
return LocalZigToolchain.tryFromPath(downloadPath) return LocalZigToolchain.tryFromPath(downloadPath)?.second
} }
@RequiresEdt @RequiresEdt

View file

@ -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.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.FileChooser
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
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.ui.DocumentAdapter import com.intellij.ui.DocumentAdapter
import com.intellij.ui.components.JBLabel import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBTextField
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.panel import com.intellij.ui.dsl.builder.panel
@ -52,17 +54,16 @@ object LocalSelector {
@RequiresEdt @RequiresEdt
private fun doBrowseFromDisk(): ZigToolchain? { private fun doBrowseFromDisk(): ZigToolchain? {
val dialog = DialogBuilder() val dialog = DialogBuilder()
val name = JBTextField().also { it.columns = 25 }
val path = textFieldWithBrowseButton( val path = textFieldWithBrowseButton(
null, null,
FileChooserDescriptorFactory.createSingleFolderDescriptor() FileChooserDescriptorFactory.createSingleFolderDescriptor()
.withTitle(ZigBrainsBundle.message("settings.toolchain.local-selector.chooser.title")) .withTitle(ZigBrainsBundle.message("settings.toolchain.local-selector.chooser.title"))
) )
Disposer.register(dialog, path) Disposer.register(dialog, path)
path.textField.columns = 50
lateinit var errorMessageBox: JBLabel lateinit var errorMessageBox: JBLabel
path.addDocumentListener(object: DocumentAdapter() { fun verify(path: String) {
override fun textChanged(e: DocumentEvent) { val tc = LocalZigToolchain.tryFromPathString(path)?.second
val tc = LocalZigToolchain.tryFromPathString(path.text)
if (tc == null) { if (tc == null) {
errorMessageBox.icon = AllIcons.General.Error errorMessageBox.icon = AllIcons.General.Error
errorMessageBox.text = ZigBrainsBundle.message("settings.toolchain.local-selector.state.invalid") errorMessageBox.text = ZigBrainsBundle.message("settings.toolchain.local-selector.state.invalid")
@ -78,13 +79,25 @@ object LocalSelector {
?: ZigBrainsBundle.message("settings.toolchain.local-selector.state.already-exists-unnamed") ?: ZigBrainsBundle.message("settings.toolchain.local-selector.state.already-exists-unnamed")
dialog.setOkActionEnabled(true) dialog.setOkActionEnabled(true)
} else { } else {
errorMessageBox.icon = Icons.Zig errorMessageBox.icon = AllIcons.General.Information
errorMessageBox.text = tc.name ?: ZigBrainsBundle.message("settings.toolchain.local-selector.state.ok") errorMessageBox.text = ZigBrainsBundle.message("settings.toolchain.local-selector.state.ok")
dialog.setOkActionEnabled(true) 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) {
verify(path.text)
} }
}) })
val center = panel { 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")) { row(ZigBrainsBundle.message("settings.toolchain.local-selector.path.label")) {
cell(path).resizableColumn().align(AlignX.FILL) cell(path).resizableColumn().align(AlignX.FILL)
} }
@ -97,9 +110,19 @@ object LocalSelector {
dialog.setTitle(ZigBrainsBundle.message("settings.toolchain.local-selector.title")) dialog.setTitle(ZigBrainsBundle.message("settings.toolchain.local-selector.title"))
dialog.addCancelAction() dialog.addCancelAction()
dialog.addOkAction().also { it.setText(ZigBrainsBundle.message("settings.toolchain.local-selector.ok-action")) } 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()) { if (!dialog.showAndGet()) {
return null return null
} }
return LocalZigToolchain.tryFromPathString(path.text) return LocalZigToolchain.tryFromPathString(path.text)?.second?.also { it.copy(name = name.text.ifBlank { null } ?: it.name) }
} }
} }

View file

@ -32,6 +32,7 @@ import com.intellij.openapi.util.KeyWithDefaultValue
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.openapi.vfs.toNioPathOrNull import com.intellij.openapi.vfs.toNioPathOrNull
import com.intellij.util.text.SemVer
import java.nio.file.Path import java.nio.file.Path
@JvmRecord @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<SemVer?, LocalZigToolchain>? {
return pathStr?.ifBlank { null }?.toNioPathOrNull()?.let(::tryFromPath) return pathStr?.ifBlank { null }?.toNioPathOrNull()?.let(::tryFromPath)
} }
fun tryFromPath(path: Path): LocalZigToolchain? { fun tryFromPath(path: Path): Pair<SemVer?, LocalZigToolchain>? {
var tc = LocalZigToolchain(path) var tc = LocalZigToolchain(path)
if (!tc.zig.fileValid()) { if (!tc.zig.fileValid()) {
return null return null
} }
tc.zig val versionStr = tc.zig
.getEnvBlocking(null) .getEnvBlocking(null)
.getOrNull() .getOrNull()
?.version ?.version
?.let { "Zig $it" } val version: SemVer?
?.let { tc = tc.copy(name = it) } if (versionStr != null) {
return tc version = SemVer.parseFromText(versionStr)
tc = tc.copy(name = "Zig $versionStr")
} else {
version = null
}
return version to tc
} }
} }
} }

View file

@ -37,6 +37,7 @@ import com.intellij.ui.SimpleColoredComponent
import com.intellij.ui.SimpleTextAttributes import com.intellij.ui.SimpleTextAttributes
import com.intellij.util.EnvironmentUtil import com.intellij.util.EnvironmentUtil
import com.intellij.util.system.OS import com.intellij.util.system.OS
import com.intellij.util.text.SemVer
import java.io.File import java.io.File
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
@ -97,7 +98,7 @@ class LocalZigToolchainProvider: ZigToolchainProvider {
return LocalZigToolchainConfigurable(uuid, toolchain) return LocalZigToolchainConfigurable(uuid, toolchain)
} }
override fun suggestToolchains(): List<ZigToolchain> { override fun suggestToolchains(): List<Pair<SemVer?, ZigToolchain>> {
val res = HashSet<String>() val res = HashSet<String>()
EnvironmentUtil.getValue("PATH")?.split(File.pathSeparatorChar)?.let { res.addAll(it.toList()) } EnvironmentUtil.getValue("PATH")?.split(File.pathSeparatorChar)?.let { res.addAll(it.toList()) }
val wellKnown = getWellKnown() val wellKnown = getWellKnown()

View file

@ -52,7 +52,7 @@ class ZigToolchainEditor(private val isForDefaultProject: Boolean = false): SubC
private val toolchainBox: TCComboBox private val toolchainBox: TCComboBox
private var selectOnNextReload: UUID? = null private var selectOnNextReload: UUID? = null
private val model: TCModel private val model: TCModel
private lateinit var editButton: JButton private var editButton: JButton? = null
init { init {
model = TCModel(getModelList()) model = TCModel(getModelList())
toolchainBox = TCComboBox(model) toolchainBox = TCComboBox(model)
@ -60,15 +60,21 @@ class ZigToolchainEditor(private val isForDefaultProject: Boolean = false): SubC
ZigToolchainListService.getInstance().addChangeListener(this) ZigToolchainListService.getInstance().addChangeListener(this)
} }
private fun refreshButtonState(item: Any?) {
editButton?.isEnabled = item is TCListElem.Toolchain.Actual
editButton?.repaint()
}
private fun itemStateChanged(event: ItemEvent) { private fun itemStateChanged(event: ItemEvent) {
if (event.stateChange != ItemEvent.SELECTED) { if (event.stateChange != ItemEvent.SELECTED) {
return return
} }
val item = event.item val item = event.item
refreshButtonState(item)
if (item !is TCListElem.Pseudo) if (item !is TCListElem.Pseudo)
return return
zigCoroutineScope.launch(toolchainBox.asContextElement()) { zigCoroutineScope.launch(toolchainBox.asContextElement()) {
val uuid = ZigToolchainComboBoxHandler.onItemSelected(toolchainBox, item) val uuid = runCatching { ZigToolchainComboBoxHandler.onItemSelected(toolchainBox, item) }.getOrNull()
withEDTContext(toolchainBox.asContextElement()) { withEDTContext(toolchainBox.asContextElement()) {
applyUUIDNowOrOnReload(uuid) applyUUIDNowOrOnReload(uuid)
} }
@ -105,10 +111,6 @@ class ZigToolchainEditor(private val isForDefaultProject: Boolean = false): SubC
} }
model.selectedItem = TCListElem.None 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) { override fun attach(p: Panel): Unit = with(p) {
@ -129,7 +131,10 @@ class ZigToolchainEditor(private val isForDefaultProject: Boolean = false): SubC
applyUUIDNowOrOnReload(selectedUUID) 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?) { override fun reset(context: Project?) {
val project = context ?: ProjectManager.getInstance().defaultProject val project = context ?: ProjectManager.getInstance().defaultProject
toolchainBox.selectedToolchain = ZigToolchainService.getInstance(project).toolchainUUID toolchainBox.selectedToolchain = ZigToolchainService.getInstance(project).toolchainUUID
refreshButtonState(toolchainBox.selectedItem)
} }
override fun dispose() { override fun dispose() {

View file

@ -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.index=Zig version information
settings.toolchain.downloader.service.tarball=Zig archive settings.toolchain.downloader.service.tarball=Zig archive
settings.toolchain.local-selector.title=Select Zig From Disk 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.path.label=Path:
settings.toolchain.local-selector.ok-action=Add settings.toolchain.local-selector.ok-action=Add
settings.toolchain.local-selector.chooser.title=Existing Zig Install Directory settings.toolchain.local-selector.chooser.title=Existing Zig Install Directory
settings.toolchain.local-selector.state.invalid=Invalid toolchain path 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-unnamed=Toolchain already exists
settings.toolchain.local-selector.state.already-exists-named=Toolchain already exists as "{0}" 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