begin implementation of multi-toolchain management
This commit is contained in:
parent
3de2f92bf8
commit
f53b0e3283
12 changed files with 606 additions and 10 deletions
|
@ -27,6 +27,7 @@ import com.intellij.execution.configurations.GeneralCommandLine
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
|
||||||
abstract class AbstractZigToolchain {
|
abstract class AbstractZigToolchain {
|
||||||
val zig: ZigCompilerTool by lazy { ZigCompilerTool(this) }
|
val zig: ZigCompilerTool by lazy { ZigCompilerTool(this) }
|
||||||
|
|
||||||
|
|
|
@ -30,10 +30,11 @@ import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.project.guessProjectDir
|
import com.intellij.openapi.project.guessProjectDir
|
||||||
import com.intellij.openapi.util.KeyWithDefaultValue
|
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.vfs.toNioPathOrNull
|
import com.intellij.openapi.vfs.toNioPathOrNull
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
class LocalZigToolchain(val location: Path): AbstractZigToolchain() {
|
class LocalZigToolchain(var location: Path, var std: Path? = null, var name: String? = null): AbstractZigToolchain() {
|
||||||
override fun workingDirectory(project: Project?): Path? {
|
override fun workingDirectory(project: Project?): Path? {
|
||||||
return project?.guessProjectDir()?.toNioPathOrNull()
|
return project?.guessProjectDir()?.toNioPathOrNull()
|
||||||
}
|
}
|
||||||
|
@ -62,5 +63,17 @@ class LocalZigToolchain(val location: Path): AbstractZigToolchain() {
|
||||||
throw ExecutionException("The debugger only supports local zig toolchain")
|
throw ExecutionException("The debugger only supports local zig toolchain")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun tryFromPathString(pathStr: String): LocalZigToolchain? {
|
||||||
|
return pathStr.toNioPathOrNull()?.let(::tryFromPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tryFromPath(path: Path): LocalZigToolchain? {
|
||||||
|
val tc = LocalZigToolchain(path)
|
||||||
|
if (!tc.zig.fileValid()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return tc
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.ui.NamedConfigurable
|
||||||
|
import com.intellij.openapi.util.NlsContexts
|
||||||
|
import com.intellij.openapi.util.NlsSafe
|
||||||
|
import com.intellij.ui.dsl.builder.panel
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import javax.swing.JComponent
|
||||||
|
|
||||||
|
class LocalZigToolchainConfigurable(
|
||||||
|
val toolchain: LocalZigToolchain,
|
||||||
|
private val project: Project
|
||||||
|
): NamedConfigurable<LocalZigToolchain>() {
|
||||||
|
private var myView: LocalZigToolchainPanel? = null
|
||||||
|
override fun setDisplayName(name: @NlsSafe String?) {
|
||||||
|
toolchain.name = name
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getEditableObject(): LocalZigToolchain? {
|
||||||
|
return toolchain
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBannerSlogan(): @NlsContexts.DetailedDescription String? {
|
||||||
|
return displayName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createOptionsPanel(): JComponent? {
|
||||||
|
var view = myView
|
||||||
|
if (view == null) {
|
||||||
|
view = LocalZigToolchainPanel()
|
||||||
|
view.reset(this)
|
||||||
|
myView = view
|
||||||
|
}
|
||||||
|
return panel {
|
||||||
|
view.attach(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDisplayName(): @NlsContexts.ConfigurableName String? {
|
||||||
|
var theName = toolchain.name
|
||||||
|
if (theName == null) {
|
||||||
|
val version = toolchain.zig.let { runBlocking { it.getEnv(project) } }.getOrNull()?.version
|
||||||
|
if (version != null) {
|
||||||
|
theName = "Zig $version"
|
||||||
|
toolchain.name = theName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return theName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isModified(): Boolean {
|
||||||
|
return myView?.isModified(this) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apply() {
|
||||||
|
myView?.apply(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reset() {
|
||||||
|
myView?.reset(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun disposeUIResources() {
|
||||||
|
super.disposeUIResources()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,176 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
|
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||||
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
|
import com.intellij.openapi.Disposable
|
||||||
|
import com.intellij.openapi.application.ModalityState
|
||||||
|
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||||
|
import com.intellij.openapi.util.Disposer
|
||||||
|
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||||
|
import com.intellij.ui.DocumentAdapter
|
||||||
|
import com.intellij.ui.JBColor
|
||||||
|
import com.intellij.ui.components.JBCheckBox
|
||||||
|
import com.intellij.ui.components.JBTextArea
|
||||||
|
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
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.swing.event.DocumentEvent
|
||||||
|
import kotlin.io.path.pathString
|
||||||
|
|
||||||
|
class LocalZigToolchainPanel() : Disposable {
|
||||||
|
private val nameField = JBTextField()
|
||||||
|
private val pathToToolchain = textFieldWithBrowseButton(
|
||||||
|
null,
|
||||||
|
FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.zig-toolchain"))
|
||||||
|
).also {
|
||||||
|
it.textField.document.addDocumentListener(object : DocumentAdapter() {
|
||||||
|
override fun textChanged(e: DocumentEvent) {
|
||||||
|
dispatchUpdateUI()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Disposer.register(this, it)
|
||||||
|
}
|
||||||
|
private val toolchainVersion = JBTextArea().also { it.isEditable = false }
|
||||||
|
private val stdFieldOverride = JBCheckBox(ZigBrainsBundle.message("settings.project.label.override-std")).apply {
|
||||||
|
addChangeListener {
|
||||||
|
if (isSelected) {
|
||||||
|
pathToStd.isEnabled = true
|
||||||
|
} else {
|
||||||
|
pathToStd.isEnabled = false
|
||||||
|
updateUI()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val pathToStd = textFieldWithBrowseButton(
|
||||||
|
null,
|
||||||
|
FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.zig-std"))
|
||||||
|
).also { Disposer.register(this, it) }
|
||||||
|
private var debounce: Job? = null
|
||||||
|
|
||||||
|
fun attach(p: Panel): Unit = with(p) {
|
||||||
|
row("Name") {
|
||||||
|
cell(nameField).resizableColumn().align(AlignX.FILL)
|
||||||
|
}
|
||||||
|
separator()
|
||||||
|
row(ZigBrainsBundle.message("settings.project.label.toolchain")) {
|
||||||
|
cell(pathToToolchain).resizableColumn().align(AlignX.FILL)
|
||||||
|
}
|
||||||
|
row(ZigBrainsBundle.message("settings.project.label.toolchain-version")) {
|
||||||
|
cell(toolchainVersion)
|
||||||
|
}
|
||||||
|
row(ZigBrainsBundle.message("settings.project.label.std-location")) {
|
||||||
|
cell(pathToStd).resizableColumn().align(AlignX.FILL)
|
||||||
|
cell(stdFieldOverride)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isModified(cfg: LocalZigToolchainConfigurable): Boolean {
|
||||||
|
val name = nameField.text.ifBlank { null } ?: return false
|
||||||
|
val tc = cfg.toolchain
|
||||||
|
val location = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull() ?: return false
|
||||||
|
val std = if (stdFieldOverride.isSelected) pathToStd.text.ifBlank { null }?.toNioPathOrNull() else null
|
||||||
|
return name != cfg.displayName || tc.location != location || tc.std != std
|
||||||
|
}
|
||||||
|
|
||||||
|
fun apply(cfg: LocalZigToolchainConfigurable): Boolean {
|
||||||
|
cfg.displayName = nameField.text
|
||||||
|
val tc = cfg.toolchain
|
||||||
|
val location = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull() ?: return false
|
||||||
|
val std = if (stdFieldOverride.isSelected) pathToStd.text.ifBlank { null }?.toNioPathOrNull() else null
|
||||||
|
tc.location = location
|
||||||
|
tc.std = std
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reset(cfg: LocalZigToolchainConfigurable) {
|
||||||
|
nameField.text = cfg.displayName ?: ""
|
||||||
|
val tc = cfg.toolchain
|
||||||
|
this.pathToToolchain.text = tc.location.pathString
|
||||||
|
val std = tc.std
|
||||||
|
if (std != null) {
|
||||||
|
stdFieldOverride.isSelected = true
|
||||||
|
pathToStd.text = std.pathString
|
||||||
|
pathToStd.isEnabled = true
|
||||||
|
} else {
|
||||||
|
stdFieldOverride.isSelected = false
|
||||||
|
pathToStd.text = ""
|
||||||
|
pathToStd.isEnabled = false
|
||||||
|
dispatchUpdateUI()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dispatchUpdateUI() {
|
||||||
|
debounce?.cancel("New debounce")
|
||||||
|
debounce = zigCoroutineScope.launch {
|
||||||
|
updateUI()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun updateUI() {
|
||||||
|
delay(200)
|
||||||
|
val pathToToolchain = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull()
|
||||||
|
if (pathToToolchain == null) {
|
||||||
|
withEDTContext(ModalityState.any()) {
|
||||||
|
toolchainVersion.text = "[toolchain path empty or invalid]"
|
||||||
|
if (!stdFieldOverride.isSelected) {
|
||||||
|
pathToStd.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val toolchain = LocalZigToolchain(pathToToolchain)
|
||||||
|
val zig = toolchain.zig
|
||||||
|
val env = zig.getEnv(null).getOrElse { throwable ->
|
||||||
|
throwable.printStackTrace()
|
||||||
|
withEDTContext(ModalityState.any()) {
|
||||||
|
toolchainVersion.text = "[failed to run \"zig env\"]\n${throwable.message}"
|
||||||
|
if (!stdFieldOverride.isSelected) {
|
||||||
|
pathToStd.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val version = env.version
|
||||||
|
val stdPath = env.stdPath(toolchain, null)
|
||||||
|
|
||||||
|
withEDTContext(ModalityState.any()) {
|
||||||
|
toolchainVersion.text = version
|
||||||
|
toolchainVersion.foreground = JBColor.foreground()
|
||||||
|
if (!stdFieldOverride.isSelected) {
|
||||||
|
pathToStd.text = stdPath?.pathString ?: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
debounce?.cancel("Disposed")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.direnv.emptyEnv
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.projectRoots.AdditionalDataConfigurable
|
||||||
|
import com.intellij.openapi.projectRoots.SdkAdditionalData
|
||||||
|
import com.intellij.openapi.projectRoots.SdkModel
|
||||||
|
import com.intellij.openapi.projectRoots.SdkModificator
|
||||||
|
import com.intellij.openapi.projectRoots.SdkType
|
||||||
|
import com.intellij.openapi.util.UserDataHolderBase
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import com.intellij.util.EnvironmentUtil
|
||||||
|
import com.intellij.util.asSafely
|
||||||
|
import com.intellij.util.system.OS
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.jdom.Element
|
||||||
|
import org.jetbrains.annotations.Nls
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.io.path.pathString
|
||||||
|
|
||||||
|
class ZigSDKType: SdkType("Zig") {
|
||||||
|
override fun suggestHomePath(): String? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPathEnv(path: String): ZigToolchainEnvironmentSerializable? {
|
||||||
|
return LocalZigToolchain.tryFromPathString(path)?.zig?.let { runBlocking { it.getEnv(null) } }?.getOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isValidSdkHome(path: String): Boolean {
|
||||||
|
return LocalZigToolchain.tryFromPathString(path) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun suggestSdkName(currentSdkName: String?, sdkHome: String): String {
|
||||||
|
return getVersionString(sdkHome)?.let { "Zig $it" } ?: currentSdkName ?: "Zig"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getVersionString(sdkHome: String): String? {
|
||||||
|
return getPathEnv(sdkHome)?.version
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun suggestHomePaths(): Collection<String?> {
|
||||||
|
val res = HashSet<String>()
|
||||||
|
EnvironmentUtil.getValue("PATH")?.split(File.pathSeparatorChar)?.let { res.addAll(it.toList()) }
|
||||||
|
if (OS.CURRENT != OS.Windows) {
|
||||||
|
EnvironmentUtil.getValue("HOME")?.let { res.add("$it/.local/share/zigup") }
|
||||||
|
EnvironmentUtil.getValue("XDG_DATA_HOME")?.let { res.add("$it/zigup") }
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createAdditionalDataConfigurable(
|
||||||
|
sdkModel: SdkModel,
|
||||||
|
sdkModificator: SdkModificator
|
||||||
|
): AdditionalDataConfigurable? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPresentableName(): @Nls(capitalization = Nls.Capitalization.Title) String {
|
||||||
|
return "Zig"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveAdditionalData(additionalData: SdkAdditionalData, additional: Element) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isRelevantForFile(project: Project, file: VirtualFile): Boolean {
|
||||||
|
return file.extension == "zig" || file.extension == "zon"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import com.intellij.util.xmlb.annotations.OptionTag
|
||||||
|
|
||||||
|
data class ZigToolchainList(
|
||||||
|
@get:OptionTag(converter = ZigToolchainListConverter::class)
|
||||||
|
var toolchains: List<AbstractZigToolchain> = emptyList(),
|
||||||
|
)
|
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
|
import com.intellij.openapi.actionSystem.AnAction
|
||||||
|
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||||
|
import com.intellij.openapi.actionSystem.Presentation
|
||||||
|
import com.intellij.openapi.project.DumbAwareAction
|
||||||
|
import com.intellij.openapi.project.ProjectManager
|
||||||
|
import com.intellij.openapi.roots.ui.configuration.SdkPopupFactory
|
||||||
|
import com.intellij.openapi.ui.MasterDetailsComponent
|
||||||
|
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||||
|
import com.intellij.util.IconUtil
|
||||||
|
import javax.swing.JComponent
|
||||||
|
import javax.swing.tree.DefaultTreeModel
|
||||||
|
|
||||||
|
class ZigToolchainListEditor(): MasterDetailsComponent() {
|
||||||
|
private var isTreeInitialized = false
|
||||||
|
|
||||||
|
override fun createComponent(): JComponent {
|
||||||
|
if (!isTreeInitialized) {
|
||||||
|
initTree()
|
||||||
|
isTreeInitialized = true
|
||||||
|
}
|
||||||
|
return super.createComponent()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createActions(fromPopup: Boolean): List<AnAction?>? {
|
||||||
|
val add = object : DumbAwareAction({"lmaoo"}, Presentation.NULL_STRING, IconUtil.addIcon) {
|
||||||
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
|
SdkPopupFactory
|
||||||
|
.newBuilder()
|
||||||
|
.withSdkTypeFilter { it is ZigSDKType }
|
||||||
|
.onSdkSelected {
|
||||||
|
val path = it.homePath?.toNioPathOrNull() ?: return@onSdkSelected
|
||||||
|
val toolchain = LocalZigToolchain(path)
|
||||||
|
zigToolchainList.state = ZigToolchainList(zigToolchainList.state.toolchains + listOf(toolchain))
|
||||||
|
addLocalToolchain(toolchain)
|
||||||
|
(myTree.model as DefaultTreeModel).reload()
|
||||||
|
}
|
||||||
|
.buildPopup()
|
||||||
|
.showPopup(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return listOf(add, MyDeleteAction())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemDeleted(item: Any?) {
|
||||||
|
if (item is AbstractZigToolchain) {
|
||||||
|
zigToolchainList.state = ZigToolchainList(zigToolchainList.state.toolchains.filter { it != item })
|
||||||
|
}
|
||||||
|
super.onItemDeleted(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reset() {
|
||||||
|
reloadTree()
|
||||||
|
super.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getEmptySelectionString() = ZigBrainsBundle.message("settings.toolchains.empty")
|
||||||
|
|
||||||
|
override fun getDisplayName() = ZigBrainsBundle.message("settings.toolchains.title")
|
||||||
|
|
||||||
|
private fun addLocalToolchain(toolchain: LocalZigToolchain) {
|
||||||
|
val node = MyNode(LocalZigToolchainConfigurable(toolchain, ProjectManager.getInstance().defaultProject))
|
||||||
|
addNode(node, myRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reloadTree() {
|
||||||
|
myRoot.removeAllChildren()
|
||||||
|
zigToolchainList.state.toolchains.forEach { toolchain ->
|
||||||
|
if (toolchain is LocalZigToolchain) {
|
||||||
|
addLocalToolchain(toolchain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(myTree.model as DefaultTreeModel).reload()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.project.settings.ZigProjectSettings
|
||||||
|
import com.falsepattern.zigbrains.project.toolchain.stdlib.ZigSyntheticLibrary
|
||||||
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
|
import com.intellij.openapi.components.*
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Service(Service.Level.APP)
|
||||||
|
@State(
|
||||||
|
name = "ZigProjectSettings",
|
||||||
|
storages = [Storage("zigbrains.xml")]
|
||||||
|
)
|
||||||
|
class ZigToolchainListService(): PersistentStateComponent<ZigToolchainList> {
|
||||||
|
@Volatile
|
||||||
|
private var state = ZigToolchainList()
|
||||||
|
|
||||||
|
override fun getState(): ZigToolchainList {
|
||||||
|
return state.copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setState(value: ZigToolchainList) {
|
||||||
|
this.state = value
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadState(state: ZigToolchainList) {
|
||||||
|
setState(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isModified(otherData: ZigToolchainList): Boolean {
|
||||||
|
return state != otherData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val zigToolchainList get() = service<ZigToolchainListService>()
|
|
@ -26,6 +26,7 @@ import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider.Compani
|
||||||
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.util.UserDataHolder
|
import com.intellij.openapi.util.UserDataHolder
|
||||||
|
import com.intellij.util.asSafely
|
||||||
import com.intellij.util.xmlb.Converter
|
import com.intellij.util.xmlb.Converter
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
|
|
||||||
|
@ -43,6 +44,21 @@ sealed interface ZigToolchainProvider<in T: AbstractZigToolchain> {
|
||||||
suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): AbstractZigToolchain? {
|
suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): AbstractZigToolchain? {
|
||||||
return EXTENSION_POINT_NAME.extensionList.firstNotNullOfOrNull { it.suggestToolchain(project, extraData) }
|
return EXTENSION_POINT_NAME.extensionList.firstNotNullOfOrNull { it.suggestToolchain(project, extraData) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun fromJson(json: JsonObject): AbstractZigToolchain? {
|
||||||
|
val marker = (json["marker"] as? JsonPrimitive)?.contentOrNull ?: return null
|
||||||
|
val data = json["data"] ?: return null
|
||||||
|
val provider = EXTENSION_POINT_NAME.extensionList.find { it.serialMarker == marker } ?: return null
|
||||||
|
return provider.deserialize(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toJson(tc: AbstractZigToolchain): JsonObject? {
|
||||||
|
val provider = EXTENSION_POINT_NAME.extensionList.find { it.canSerialize(tc) } ?: return null
|
||||||
|
return buildJsonObject {
|
||||||
|
put("marker", provider.serialMarker)
|
||||||
|
put("data", provider.serialize(tc))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,18 +68,25 @@ private fun <T: AbstractZigToolchain> ZigToolchainProvider<T>.serialize(toolchai
|
||||||
class ZigToolchainConverter: Converter<AbstractZigToolchain>() {
|
class ZigToolchainConverter: Converter<AbstractZigToolchain>() {
|
||||||
override fun fromString(value: String): AbstractZigToolchain? {
|
override fun fromString(value: String): AbstractZigToolchain? {
|
||||||
val json = Json.parseToJsonElement(value) as? JsonObject ?: return null
|
val json = Json.parseToJsonElement(value) as? JsonObject ?: return null
|
||||||
val marker = (json["marker"] as? JsonPrimitive)?.contentOrNull ?: return null
|
return ZigToolchainProvider.fromJson(json)
|
||||||
val data = json["data"] ?: return null
|
|
||||||
val provider = EXTENSION_POINT_NAME.extensionList.find { it.serialMarker == marker } ?: return null
|
|
||||||
return provider.deserialize(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(value: AbstractZigToolchain): String? {
|
override fun toString(value: AbstractZigToolchain): String? {
|
||||||
val provider = EXTENSION_POINT_NAME.extensionList.find { it.canSerialize(value) } ?: return null
|
return ZigToolchainProvider.toJson(value)?.toString()
|
||||||
return buildJsonObject {
|
}
|
||||||
put("marker", provider.serialMarker)
|
|
||||||
put("data", provider.serialize(value))
|
|
||||||
}.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ZigToolchainListConverter: Converter<List<AbstractZigToolchain>>() {
|
||||||
|
override fun fromString(value: String): List<AbstractZigToolchain> {
|
||||||
|
val json = Json.parseToJsonElement(value) as? JsonArray ?: return emptyList()
|
||||||
|
return json.mapNotNull { it.asSafely<JsonObject>()?.let { ZigToolchainProvider.fromJson(it) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(value: List<AbstractZigToolchain>): String {
|
||||||
|
return buildJsonArray {
|
||||||
|
value.mapNotNull { ZigToolchainProvider.toJson(it) }.forEach {
|
||||||
|
add(it)
|
||||||
|
}
|
||||||
|
}.toString()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -29,6 +29,7 @@ import com.intellij.execution.configurations.GeneralCommandLine
|
||||||
import com.intellij.execution.process.ProcessOutput
|
import com.intellij.execution.process.ProcessOutput
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.isRegularFile
|
||||||
|
|
||||||
abstract class ZigTool(val toolchain: AbstractZigToolchain) {
|
abstract class ZigTool(val toolchain: AbstractZigToolchain) {
|
||||||
abstract val toolName: String
|
abstract val toolName: String
|
||||||
|
@ -38,6 +39,11 @@ abstract class ZigTool(val toolchain: AbstractZigToolchain) {
|
||||||
return cli.call(timeoutMillis, ipcProject = ipcProject)
|
return cli.call(timeoutMillis, ipcProject = ipcProject)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun fileValid(): Boolean {
|
||||||
|
val exe = toolchain.pathToExecutable(toolName)
|
||||||
|
return exe.isRegularFile()
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun createBaseCommandLine(
|
private suspend fun createBaseCommandLine(
|
||||||
workingDirectory: Path?,
|
workingDirectory: Path?,
|
||||||
vararg parameters: String
|
vararg parameters: String
|
||||||
|
|
|
@ -144,6 +144,13 @@
|
||||||
id="ZigConfigurable"
|
id="ZigConfigurable"
|
||||||
displayName="Zig"
|
displayName="Zig"
|
||||||
/>
|
/>
|
||||||
|
<applicationConfigurable
|
||||||
|
parentId="ZigConfigurable"
|
||||||
|
instance="com.falsepattern.zigbrains.project.toolchain.ZigToolchainListEditor"
|
||||||
|
id="ZigToolchainConfigurable"
|
||||||
|
displayName="Toolchain"
|
||||||
|
/>
|
||||||
|
<sdkType implementation="com.falsepattern.zigbrains.project.toolchain.ZigSDKType"/>
|
||||||
|
|
||||||
<programRunner
|
<programRunner
|
||||||
implementation="com.falsepattern.zigbrains.project.run.ZigRegularRunner"
|
implementation="com.falsepattern.zigbrains.project.run.ZigRegularRunner"
|
||||||
|
|
|
@ -98,6 +98,8 @@ settings.project.label.toolchain=Toolchain location
|
||||||
settings.project.label.toolchain-version=Detected zig version
|
settings.project.label.toolchain-version=Detected zig version
|
||||||
settings.project.label.override-std=Override standard library
|
settings.project.label.override-std=Override standard library
|
||||||
settings.project.label.std-location=Standard library location
|
settings.project.label.std-location=Standard library location
|
||||||
|
settings.toolchains.empty=Select a toolchain to view or edit its details here
|
||||||
|
settings.toolchains.title=Toolchains
|
||||||
toolwindow.stripe.zigbrains.build=Zig
|
toolwindow.stripe.zigbrains.build=Zig
|
||||||
build.tool.window.tree.steps.label=Steps
|
build.tool.window.tree.steps.label=Steps
|
||||||
build.tool.window.tree.build.label=Active builds
|
build.tool.window.tree.build.label=Active builds
|
||||||
|
|
Loading…
Add table
Reference in a new issue