ported core to new toolchain api

direnv and zls regressed for now
This commit is contained in:
FalsePattern 2025-04-09 01:08:43 +02:00
parent b485c1e48c
commit a8f97172d6
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
35 changed files with 350 additions and 789 deletions

View file

@ -83,7 +83,7 @@ allprojects {
} }
} }
filter { filter {
includeModule("com.redhat.devtools.intellij", "lsp4ij") // includeModule("com.redhat.devtools.intellij", "lsp4ij")
} }
} }
mavenCentral() mavenCentral()
@ -104,12 +104,12 @@ dependencies {
pluginVerifier() pluginVerifier()
zipSigner() zipSigner()
plugin(lsp4ijPluginString) // plugin(lsp4ijPluginString)
} }
runtimeOnly(project(":core")) runtimeOnly(project(":core"))
runtimeOnly(project(":cidr")) runtimeOnly(project(":cidr"))
runtimeOnly(project(":lsp")) // runtimeOnly(project(":lsp"))
} }
intellijPlatform { intellijPlatform {

View file

@ -23,7 +23,6 @@
package com.falsepattern.zigbrains package com.falsepattern.zigbrains
import com.falsepattern.zigbrains.direnv.DirenvCmd import com.falsepattern.zigbrains.direnv.DirenvCmd
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
import com.falsepattern.zigbrains.project.toolchain.base.suggestZigToolchain import com.falsepattern.zigbrains.project.toolchain.base.suggestZigToolchain
import com.intellij.ide.BrowserUtil import com.intellij.ide.BrowserUtil
@ -73,19 +72,6 @@ class ZBStartup: ProjectActivity {
notif.notify(null) notif.notify(null)
} }
} }
//Autodetection
val zigProjectState = project.zigProjectSettings.state
if (zigProjectState.toolchainPath.isNullOrBlank()) {
val data = UserDataHolderBase()
data.putUserData(LocalZigToolchain.DIRENV_KEY,
DirenvCmd.direnvInstalled() && !project.isDefault && zigProjectState.direnv
)
val tc = project.suggestZigToolchain(data) ?: return
if (tc is LocalZigToolchain) {
zigProjectState.toolchainPath = tc.location.pathString
project.zigProjectSettings.state = zigProjectState
}
}
} }
} }

View file

@ -24,15 +24,12 @@ package com.falsepattern.zigbrains.project.execution.base
import com.falsepattern.zigbrains.ZigBrainsBundle import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.execution.base.ZigConfigurable.ZigConfigModule import com.falsepattern.zigbrains.project.execution.base.ZigConfigurable.ZigConfigModule
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.falsepattern.zigbrains.shared.cli.translateCommandline import com.falsepattern.zigbrains.shared.cli.translateCommandline
import com.falsepattern.zigbrains.shared.element.* import com.falsepattern.zigbrains.shared.element.*
import com.intellij.openapi.Disposable import com.intellij.openapi.Disposable
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.options.SettingsEditor import com.intellij.openapi.options.SettingsEditor
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.ComboBox import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.TextBrowseFolderListener
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.toNioPathOrNull import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.ui.components.JBCheckBox import com.intellij.ui.components.JBCheckBox

View file

@ -22,9 +22,7 @@
package com.falsepattern.zigbrains.project.execution.base package com.falsepattern.zigbrains.project.execution.base
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.direnv.DirenvCmd import com.falsepattern.zigbrains.direnv.DirenvCmd
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.intellij.execution.ExecutionException import com.intellij.execution.ExecutionException
import com.intellij.execution.Executor import com.intellij.execution.Executor
import com.intellij.execution.configurations.ConfigurationFactory import com.intellij.execution.configurations.ConfigurationFactory
@ -65,9 +63,10 @@ abstract class ZigExecConfig<T: ZigExecConfig<T>>(project: Project, factory: Con
suspend fun patchCommandLine(commandLine: GeneralCommandLine): GeneralCommandLine { suspend fun patchCommandLine(commandLine: GeneralCommandLine): GeneralCommandLine {
if (project.zigProjectSettings.state.direnv) { // TODO direnv
commandLine.withEnvironment(DirenvCmd.importDirenv(project).env) // if (project.zigProjectSettings.state.direnv) {
} // commandLine.withEnvironment(DirenvCmd.importDirenv(project).env)
// }
return commandLine return commandLine
} }

View file

@ -24,7 +24,7 @@ package com.falsepattern.zigbrains.project.execution.base
import com.falsepattern.zigbrains.ZigBrainsBundle import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.execution.ZigConsoleBuilder import com.falsepattern.zigbrains.project.execution.ZigConsoleBuilder
import com.falsepattern.zigbrains.project.settings.zigProjectSettings import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
import com.falsepattern.zigbrains.shared.cli.startIPCAwareProcess import com.falsepattern.zigbrains.shared.cli.startIPCAwareProcess
import com.falsepattern.zigbrains.shared.coroutine.runModalOrBlocking import com.falsepattern.zigbrains.shared.coroutine.runModalOrBlocking
@ -55,7 +55,7 @@ abstract class ZigProfileState<T: ZigExecConfig<T>> (
@Throws(ExecutionException::class) @Throws(ExecutionException::class)
suspend fun startProcessSuspend(): ProcessHandler { suspend fun startProcessSuspend(): ProcessHandler {
val toolchain = environment.project.zigProjectSettings.state.toolchain ?: throw ExecutionException(ZigBrainsBundle.message("exception.zig-profile-state.start-process.no-toolchain")) val toolchain = ZigToolchainService.getInstance(environment.project).toolchain ?: throw ExecutionException(ZigBrainsBundle.message("exception.zig-profile-state.start-process.no-toolchain"))
return getCommandLine(toolchain, false).startIPCAwareProcess(environment.project, emulateTerminal = true) return getCommandLine(toolchain, false).startIPCAwareProcess(environment.project, emulateTerminal = true)
} }

View file

@ -50,7 +50,7 @@ class ZigModuleBuilder: ModuleBuilder() {
override fun getCustomOptionsStep(context: WizardContext?, parentDisposable: Disposable?): ModuleWizardStep? { override fun getCustomOptionsStep(context: WizardContext?, parentDisposable: Disposable?): ModuleWizardStep? {
val step = ZigModuleWizardStep(parentDisposable) val step = ZigModuleWizardStep(parentDisposable)
parentDisposable?.let { Disposer.register(it, step.peer) } parentDisposable?.let { Disposer.register(it) { step.peer.dispose() } }
return step return step
} }
@ -65,14 +65,14 @@ class ZigModuleBuilder: ModuleBuilder() {
} }
inner class ZigModuleWizardStep(parent: Disposable?): ModuleWizardStep() { inner class ZigModuleWizardStep(parent: Disposable?): ModuleWizardStep() {
internal val peer = ZigProjectGeneratorPeer(true).also { Disposer.register(parent ?: return@also, it) } internal val peer = ZigProjectGeneratorPeer(true).also { Disposer.register(parent ?: return@also) {it.dispose()} }
override fun getComponent(): JComponent { override fun getComponent(): JComponent {
return peer.myComponent.withBorder() return peer.myComponent.withBorder()
} }
override fun disposeUIResources() { override fun disposeUIResources() {
Disposer.dispose(peer) Disposer.dispose(peer.newProjectPanel)
} }
override fun updateDataModel() { override fun updateDataModel() {

View file

@ -39,9 +39,9 @@ import com.intellij.util.ui.JBUI
import javax.swing.JList import javax.swing.JList
import javax.swing.ListSelectionModel import javax.swing.ListSelectionModel
class ZigNewProjectPanel(private var handleGit: Boolean): Disposable, ZigProjectConfigurationProvider.SettingsPanelHolder { class ZigNewProjectPanel(private var handleGit: Boolean): Disposable {
private val git = JBCheckBox() private val git = JBCheckBox()
override val panels = ZigProjectConfigurationProvider.createNewProjectSettingsPanels(this).onEach { Disposer.register(this, it) } val panels = ZigProjectConfigurationProvider.createNewProjectSettingsPanels().onEach { Disposer.register(this, it) }
private val templateList = JBList(JBList.createDefaultListModel(defaultTemplates)).apply { private val templateList = JBList(JBList.createDefaultListModel(defaultTemplates)).apply {
selectionMode = ListSelectionModel.SINGLE_SELECTION selectionMode = ListSelectionModel.SINGLE_SELECTION
selectedIndex = 0 selectedIndex = 0
@ -64,7 +64,7 @@ class ZigNewProjectPanel(private var handleGit: Boolean): Disposable, ZigProject
fun getData(): ZigProjectConfigurationData { fun getData(): ZigProjectConfigurationData {
val selectedTemplate = templateList.selectedValue val selectedTemplate = templateList.selectedValue
return ZigProjectConfigurationData(handleGit && git.isSelected, panels.map { it.data }, selectedTemplate) return ZigProjectConfigurationData(handleGit && git.isSelected, panels, selectedTemplate)
} }
fun attach(p: Panel): Unit = with(p) { fun attach(p: Panel): Unit = with(p) {
@ -73,6 +73,7 @@ class ZigNewProjectPanel(private var handleGit: Boolean): Disposable, ZigProject
cell(git) cell(git)
} }
} }
panels.filter { it.newProjectBeforeInitSelector }.forEach { it.attach(p) }
group("Zig Project Template") { group("Zig Project Template") {
row { row {
resizableRow() resizableRow()
@ -81,7 +82,7 @@ class ZigNewProjectPanel(private var handleGit: Boolean): Disposable, ZigProject
.align(AlignY.FILL) .align(AlignY.FILL)
} }
} }
panels.forEach { it.attach(p) } panels.filter { !it.newProjectBeforeInitSelector }.forEach { it.attach(p) }
} }
override fun dispose() { override fun dispose() {

View file

@ -22,9 +22,10 @@
package com.falsepattern.zigbrains.project.newproject package com.falsepattern.zigbrains.project.newproject
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
import com.falsepattern.zigbrains.project.template.ZigInitTemplate import com.falsepattern.zigbrains.project.template.ZigInitTemplate
import com.falsepattern.zigbrains.project.template.ZigProjectTemplate import com.falsepattern.zigbrains.project.template.ZigProjectTemplate
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
import com.falsepattern.zigbrains.shared.SubConfigurable
import com.falsepattern.zigbrains.shared.zigCoroutineScope import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.notification.Notification import com.intellij.notification.Notification
import com.intellij.notification.NotificationType import com.intellij.notification.NotificationType
@ -42,7 +43,7 @@ import kotlinx.coroutines.launch
@JvmRecord @JvmRecord
data class ZigProjectConfigurationData( data class ZigProjectConfigurationData(
val git: Boolean, val git: Boolean,
val conf: List<ZigProjectConfigurationProvider.Settings>, val conf: List<SubConfigurable<Project>>,
val selectedTemplate: ZigProjectTemplate val selectedTemplate: ZigProjectTemplate
) { ) {
@RequiresBackgroundThread @RequiresBackgroundThread
@ -54,9 +55,7 @@ data class ZigProjectConfigurationData(
if (!reporter.indeterminateStep("Initializing project") { if (!reporter.indeterminateStep("Initializing project") {
if (template is ZigInitTemplate) { if (template is ZigInitTemplate) {
val toolchain = conf val toolchain = ZigToolchainService.getInstance(project).toolchain ?: run {
.mapNotNull { it as? ZigProjectConfigurationProvider.ToolchainProvider }
.firstNotNullOfOrNull { it.toolchain } ?: run {
Notification( Notification(
"zigbrains", "zigbrains",
"Tried to generate project with zig init, but zig toolchain is invalid", "Tried to generate project with zig init, but zig toolchain is invalid",

View file

@ -31,9 +31,9 @@ import com.intellij.platform.ProjectGeneratorPeer
import com.intellij.ui.dsl.builder.panel import com.intellij.ui.dsl.builder.panel
import javax.swing.JComponent import javax.swing.JComponent
class ZigProjectGeneratorPeer(var handleGit: Boolean): ProjectGeneratorPeer<ZigProjectConfigurationData>, Disposable { class ZigProjectGeneratorPeer(var handleGit: Boolean): ProjectGeneratorPeer<ZigProjectConfigurationData> {
private val newProjectPanel by lazy { val newProjectPanel by lazy {
ZigNewProjectPanel(handleGit).also { Disposer.register(this, it) } ZigNewProjectPanel(handleGit)
} }
val myComponent: JComponent by lazy { val myComponent: JComponent by lazy {
panel { panel {
@ -61,6 +61,7 @@ class ZigProjectGeneratorPeer(var handleGit: Boolean): ProjectGeneratorPeer<ZigP
return false return false
} }
override fun dispose() { fun dispose() {
newProjectPanel.dispose()
} }
} }

View file

@ -23,7 +23,7 @@
package com.falsepattern.zigbrains.project.run package com.falsepattern.zigbrains.project.run
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
import com.falsepattern.zigbrains.project.settings.zigProjectSettings import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
import com.falsepattern.zigbrains.shared.zigCoroutineScope import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.execution.ExecutionException import com.intellij.execution.ExecutionException
@ -61,7 +61,7 @@ abstract class ZigProgramRunner<ProfileState : ZigProfileState<*>>(protected val
val state = castProfileState(baseState) ?: return null val state = castProfileState(baseState) ?: return null
val toolchain = environment.project.zigProjectSettings.state.toolchain ?: run { val toolchain = ZigToolchainService.getInstance(environment.project).toolchain ?: run {
Notification( Notification(
"zigbrains", "zigbrains",
"Zig project toolchain not set, cannot execute program! Please configure it in [Settings | Languages & Frameworks | Zig]", "Zig project toolchain not set, cannot execute program! Please configure it in [Settings | Languages & Frameworks | Zig]",

View file

@ -1,32 +0,0 @@
/*
* 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.settings
import com.falsepattern.zigbrains.shared.MultiConfigurable
import com.intellij.openapi.project.Project
class ZigConfigurable(project: Project): MultiConfigurable(ZigProjectConfigurationProvider.createConfigurables(project)) {
override fun getDisplayName(): String {
return "Zig"
}
}

View file

@ -22,20 +22,21 @@
package com.falsepattern.zigbrains.project.settings package com.falsepattern.zigbrains.project.settings
import com.falsepattern.zigbrains.project.toolchain.ui.ZigToolchainEditor
import com.falsepattern.zigbrains.shared.SubConfigurable import com.falsepattern.zigbrains.shared.SubConfigurable
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
class ZigCoreProjectConfigurationProvider: ZigProjectConfigurationProvider { class ZigCoreProjectConfigurationProvider: ZigProjectConfigurationProvider {
override fun handleMainConfigChanged(project: Project) { override fun handleMainConfigChanged(project: Project) {
} }
override fun createConfigurable(project: Project): SubConfigurable { override fun createConfigurable(project: Project): Configurable {
return ZigProjectConfigurable(project) return ZigToolchainEditor.Adapter(project)
} }
override fun createNewProjectSettingsPanel(holder: ZigProjectConfigurationProvider.SettingsPanelHolder): ZigProjectConfigurationProvider.SettingsPanel { override fun createNewProjectSettingsPanel(): SubConfigurable<Project> {
return ZigProjectSettingsPanel(holder, ProjectManager.getInstance().defaultProject) return ZigToolchainEditor().also { it.reset(null) }
} }
override val priority: Int override val priority: Int

View file

@ -1,60 +0,0 @@
/*
* 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.settings
import com.falsepattern.zigbrains.shared.SubConfigurable
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.ui.dsl.builder.Panel
class ZigProjectConfigurable(private val project: Project): SubConfigurable {
private var settingsPanel: ZigProjectSettingsPanel? = null
override fun createComponent(holder: ZigProjectConfigurationProvider.SettingsPanelHolder, panel: Panel): ZigProjectConfigurationProvider.SettingsPanel {
settingsPanel?.let { Disposer.dispose(it) }
val sp = ZigProjectSettingsPanel(holder, project).apply { attach(panel) }.also { Disposer.register(this, it) }
settingsPanel = sp
return sp
}
override fun isModified(): Boolean {
return project.zigProjectSettings.isModified(settingsPanel?.data ?: return false)
}
override fun apply() {
val service = project.zigProjectSettings
val data = settingsPanel?.data ?: return
val modified = service.isModified(data)
service.state = data
if (modified) {
ZigProjectConfigurationProvider.mainConfigChanged(project)
}
}
override fun reset() {
settingsPanel?.data = project.zigProjectSettings.state
}
override fun dispose() {
settingsPanel = null
}
}

View file

@ -22,42 +22,26 @@
package com.falsepattern.zigbrains.project.settings package com.falsepattern.zigbrains.project.settings
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
import com.falsepattern.zigbrains.shared.SubConfigurable import com.falsepattern.zigbrains.shared.SubConfigurable
import com.intellij.openapi.Disposable
import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.ui.dsl.builder.Panel
interface ZigProjectConfigurationProvider { interface ZigProjectConfigurationProvider {
fun handleMainConfigChanged(project: Project) fun handleMainConfigChanged(project: Project)
fun createConfigurable(project: Project): SubConfigurable fun createConfigurable(project: Project): Configurable
fun createNewProjectSettingsPanel(holder: SettingsPanelHolder): SettingsPanel? fun createNewProjectSettingsPanel(): SubConfigurable<Project>?
val priority: Int val priority: Int
companion object { companion object {
private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigProjectConfigurationProvider>("com.falsepattern.zigbrains.projectConfigProvider") private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigProjectConfigurationProvider>("com.falsepattern.zigbrains.projectConfigProvider")
fun mainConfigChanged(project: Project) { fun mainConfigChanged(project: Project) {
EXTENSION_POINT_NAME.extensionList.forEach { it.handleMainConfigChanged(project) } EXTENSION_POINT_NAME.extensionList.forEach { it.handleMainConfigChanged(project) }
} }
fun createConfigurables(project: Project): List<SubConfigurable> { fun createConfigurables(project: Project): List<Configurable> {
return EXTENSION_POINT_NAME.extensionList.sortedBy { it.priority }.map { it.createConfigurable(project) } return EXTENSION_POINT_NAME.extensionList.sortedBy { it.priority }.map { it.createConfigurable(project) }
} }
fun createNewProjectSettingsPanels(holder: SettingsPanelHolder): List<SettingsPanel> { fun createNewProjectSettingsPanels(): List<SubConfigurable<Project>> {
return EXTENSION_POINT_NAME.extensionList.sortedBy { it.priority }.mapNotNull { it.createNewProjectSettingsPanel(holder) } return EXTENSION_POINT_NAME.extensionList.sortedBy { it.priority }.mapNotNull { it.createNewProjectSettingsPanel() }
} }
} }
interface SettingsPanel: Disposable {
val data: Settings
fun attach(p: Panel)
fun direnvChanged(state: Boolean)
}
interface SettingsPanelHolder {
val panels: List<SettingsPanel>
}
interface Settings {
fun apply(project: Project)
}
interface ToolchainProvider {
val toolchain: ZigToolchain?
}
} }

View file

@ -1,55 +0,0 @@
/*
* 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.settings
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.util.xmlb.annotations.Transient
import kotlin.io.path.isDirectory
import kotlin.io.path.pathString
data class ZigProjectSettings(
var direnv: Boolean = false,
var overrideStdPath: Boolean = false,
var explicitPathToStd: String? = null,
var toolchainPath: String? = null
): ZigProjectConfigurationProvider.Settings, ZigProjectConfigurationProvider.ToolchainProvider {
override fun apply(project: Project) {
project.zigProjectSettings.loadState(this)
}
@get:Transient
@set:Transient
override var toolchain: LocalZigToolchain?
get() {
val nioPath = toolchainPath?.toNioPathOrNull() ?: return null
if (!nioPath.isDirectory()) {
return null
}
return LocalZigToolchain(nioPath)
}
set(value) {
toolchainPath = value?.location?.pathString
}
}

View file

@ -1,211 +0,0 @@
/*
* 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.settings
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.direnv.DirenvCmd
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
import com.falsepattern.zigbrains.project.toolchain.base.suggestZigToolchain
import com.falsepattern.zigbrains.shared.coroutine.launchWithEDT
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.platform.ide.progress.ModalTaskOwner
import com.intellij.platform.ide.progress.TaskCancellation
import com.intellij.platform.ide.progress.withModalProgress
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.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 ZigProjectSettingsPanel(private val holder: ZigProjectConfigurationProvider.SettingsPanelHolder, private val project: Project) : ZigProjectConfigurationProvider.SettingsPanel {
private val direnv = JBCheckBox(ZigBrainsBundle.message("settings.project.label.direnv")).apply { addActionListener {
dispatchDirenvUpdate()
} }
private val pathToToolchain = textFieldWithBrowseButton(
project,
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(
project,
FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.zig-std"))
).also { Disposer.register(this, it) }
private var debounce: Job? = null
private fun dispatchDirenvUpdate() {
holder.panels.forEach {
it.direnvChanged(direnv.isSelected)
}
}
override fun direnvChanged(state: Boolean) {
dispatchAutodetect(true)
}
private fun dispatchAutodetect(force: Boolean) {
project.zigCoroutineScope.launchWithEDT(ModalityState.defaultModalityState()) {
withModalProgress(ModalTaskOwner.component(pathToToolchain), "Detecting Zig...", TaskCancellation.cancellable()) {
autodetect(force)
}
}
}
suspend fun autodetect(force: Boolean) {
if (!force && pathToToolchain.text.isNotBlank())
return
val data = UserDataHolderBase()
data.putUserData(LocalZigToolchain.DIRENV_KEY, !project.isDefault && direnv.isSelected && DirenvCmd.direnvInstalled())
val tc = project.suggestZigToolchain(project) ?: return
if (tc !is LocalZigToolchain) {
TODO("Implement non-local zig toolchain in config")
}
if (force || pathToToolchain.text.isBlank()) {
pathToToolchain.text = tc.location.pathString
dispatchUpdateUI()
}
}
override var data
get() = ZigProjectSettings(
direnv.isSelected,
stdFieldOverride.isSelected,
pathToStd.text.ifBlank { null },
pathToToolchain.text.ifBlank { null }
)
set(value) {
direnv.isSelected = value.direnv
pathToToolchain.text = value.toolchainPath ?: ""
stdFieldOverride.isSelected = value.overrideStdPath
pathToStd.text = value.explicitPathToStd ?: ""
pathToStd.isEnabled = value.overrideStdPath
dispatchUpdateUI()
}
override fun attach(p: Panel): Unit = with(p) {
data = project.zigProjectSettings.state
if (project.isDefault) {
row(ZigBrainsBundle.message("settings.project.label.toolchain")) {
cell(pathToToolchain).resizableColumn().align(AlignX.FILL)
}
row(ZigBrainsBundle.message("settings.project.label.toolchain-version")) {
cell(toolchainVersion)
}
} else {
group(ZigBrainsBundle.message("settings.project.group.title")) {
row(ZigBrainsBundle.message("settings.project.label.toolchain")) {
cell(pathToToolchain).resizableColumn().align(AlignX.FILL)
if (DirenvCmd.direnvInstalled()) {
cell(direnv)
}
}
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)
}
}
}
dispatchAutodetect(false)
}
private fun dispatchUpdateUI() {
debounce?.cancel("New debounce")
debounce = project.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(project).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, project)
withEDTContext(ModalityState.any()) {
toolchainVersion.text = version
toolchainVersion.foreground = JBColor.foreground()
if (!stdFieldOverride.isSelected) {
pathToStd.text = stdPath?.pathString ?: ""
}
}
}
override fun dispose() {
debounce?.cancel("Disposed")
}
}

View file

@ -1,60 +0,0 @@
/*
* 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.settings
import com.falsepattern.zigbrains.project.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.PROJECT)
@State(
name = "ZigProjectSettings",
storages = [Storage("zigbrains.xml")]
)
class ZigProjectSettingsService(val project: Project): PersistentStateComponent<ZigProjectSettings> {
@Volatile
private var state = ZigProjectSettings()
override fun getState(): ZigProjectSettings {
return state.copy()
}
fun setState(value: ZigProjectSettings) {
this.state = value
zigCoroutineScope.launch {
ZigSyntheticLibrary.reload(project, value)
}
}
override fun loadState(state: ZigProjectSettings) {
setState(state)
}
fun isModified(otherData: ZigProjectSettings): Boolean {
return state != otherData
}
}
val Project.zigProjectSettings get() = service<ZigProjectSettingsService>()

View file

@ -23,13 +23,13 @@
package com.falsepattern.zigbrains.project.stdlib package com.falsepattern.zigbrains.project.stdlib
import com.falsepattern.zigbrains.Icons import com.falsepattern.zigbrains.Icons
import com.falsepattern.zigbrains.project.settings.ZigProjectSettings import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
import com.falsepattern.zigbrains.project.settings.zigProjectSettings import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
import com.intellij.navigation.ItemPresentation import com.intellij.navigation.ItemPresentation
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.roots.SyntheticLibrary import com.intellij.openapi.roots.SyntheticLibrary
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.refreshAndFindVirtualDirectory import com.intellij.openapi.vfs.refreshAndFindVirtualDirectory
import com.intellij.platform.backend.workspace.WorkspaceModel import com.intellij.platform.backend.workspace.WorkspaceModel
@ -41,20 +41,20 @@ import java.util.*
import javax.swing.Icon import javax.swing.Icon
class ZigSyntheticLibrary(val project: Project) : SyntheticLibrary(), ItemPresentation { class ZigSyntheticLibrary(val project: Project) : SyntheticLibrary(), ItemPresentation {
private var state: ZigProjectSettings = project.zigProjectSettings.state.copy() private var toolchain: ZigToolchain? = ZigToolchainService.getInstance(project).toolchain
private val roots by lazy { private val roots by lazy {
runBlocking {getRoot(state, project)}?.let { setOf(it) } ?: emptySet() runBlocking {getRoot(toolchain, project)}?.let { setOf(it) } ?: emptySet()
} }
private val name by lazy { private val name by lazy {
getName(state, project) getName(toolchain, project)
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (other !is ZigSyntheticLibrary) if (other !is ZigSyntheticLibrary)
return false return false
return state == other.state return toolchain == other.toolchain
} }
override fun hashCode(): Int { override fun hashCode(): Int {
@ -76,10 +76,10 @@ class ZigSyntheticLibrary(val project: Project) : SyntheticLibrary(), ItemPresen
companion object { companion object {
private const val ZIG_LIBRARY_ID = "Zig SDK" private const val ZIG_LIBRARY_ID = "Zig SDK"
private const val ZIG_MODULE_ID = "Zig" private const val ZIG_MODULE_ID = "Zig"
suspend fun reload(project: Project, state: ZigProjectSettings) { suspend fun reload(project: Project, toolchain: ZigToolchain?) {
val moduleId = ModuleId(ZIG_MODULE_ID) val moduleId = ModuleId(ZIG_MODULE_ID)
val workspaceModel = WorkspaceModel.getInstance(project) val workspaceModel = WorkspaceModel.getInstance(project)
val root = getRoot(state, project) ?: return val root = getRoot(toolchain, project) ?: return
val libRoot = LibraryRoot(root.toVirtualFileUrl(workspaceModel.getVirtualFileUrlManager()), LibraryRootTypeId.SOURCES) val libRoot = LibraryRoot(root.toVirtualFileUrl(workspaceModel.getVirtualFileUrlManager()), LibraryRootTypeId.SOURCES)
val libraryTableId = LibraryTableId.ProjectLibraryTableId val libraryTableId = LibraryTableId.ProjectLibraryTableId
val libraryId = LibraryId(ZIG_LIBRARY_ID, libraryTableId) val libraryId = LibraryId(ZIG_LIBRARY_ID, libraryTableId)
@ -118,37 +118,39 @@ class ZigSyntheticLibrary(val project: Project) : SyntheticLibrary(), ItemPresen
} }
private fun getName( private fun getName(
state: ZigProjectSettings, toolchain: ZigToolchain?,
project: Project project: Project
): String { ): String {
val tc = state.toolchain ?: return "Zig" val tc = toolchain ?: return "Zig"
val version = runBlocking { tc.zig.getEnv(project) }.mapCatching { it.version }.getOrElse { return "Zig" } toolchain.name?.let { return it }
return "Zig $version" runBlocking { tc.zig.getEnv(project) }
.mapCatching { it.version }
.getOrNull()
?.let { return "Zig $it" }
return "Zig"
} }
suspend fun getRoot( suspend fun getRoot(
state: ZigProjectSettings, toolchain: ZigToolchain?,
project: Project project: Project
): VirtualFile? { ): VirtualFile? {
val toolchain = state.toolchain //TODO universal
if (state.overrideStdPath) run { if (toolchain !is LocalZigToolchain) {
val ePathStr = state.explicitPathToStd ?: return@run return null
val ePath = ePathStr.toNioPathOrNull() ?: return@run }
if (toolchain.std != null) run {
val ePath = toolchain.std
if (ePath.isAbsolute) { if (ePath.isAbsolute) {
val roots = ePath.refreshAndFindVirtualDirectory() ?: return@run val roots = ePath.refreshAndFindVirtualDirectory() ?: return@run
return roots return roots
} else if (toolchain != null) { }
val stdPath = toolchain.location.resolve(ePath) val stdPath = toolchain.location.resolve(ePath)
if (stdPath.isAbsolute) { if (stdPath.isAbsolute) {
val roots = stdPath.refreshAndFindVirtualDirectory() ?: return@run val roots = stdPath.refreshAndFindVirtualDirectory() ?: return@run
return roots return roots
}
} }
} }
if (toolchain != null) { val stdPath = toolchain.zig.getEnv(project).mapCatching { it.stdPath(toolchain, project) }.getOrNull() ?: return null
val stdPath = toolchain.zig.getEnv(project).mapCatching { it.stdPath(toolchain, project) }.getOrNull() ?: return null val roots = stdPath.refreshAndFindVirtualDirectory() ?: return null
val roots = stdPath.refreshAndFindVirtualDirectory() ?: return null return roots
return roots
}
return null
} }

View file

@ -22,8 +22,8 @@
package com.falsepattern.zigbrains.project.steps.discovery package com.falsepattern.zigbrains.project.steps.discovery
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.falsepattern.zigbrains.project.steps.discovery.ZigStepDiscoveryListener.ErrorType import com.falsepattern.zigbrains.project.steps.discovery.ZigStepDiscoveryListener.ErrorType
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
import com.falsepattern.zigbrains.shared.zigCoroutineScope import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.openapi.Disposable import com.intellij.openapi.Disposable
@ -76,7 +76,7 @@ class ZigStepDiscoveryService(private val project: Project) {
private tailrec suspend fun doReload() { private tailrec suspend fun doReload() {
preReload() preReload()
val toolchain = project.zigProjectSettings.state.toolchain ?: run { val toolchain = ZigToolchainService.getInstance(project).toolchain ?: run {
errorReload(ErrorType.MissingToolchain) errorReload(ErrorType.MissingToolchain)
return return
} }

View file

@ -36,9 +36,19 @@ import java.util.UUID
name = "ZigToolchainList", name = "ZigToolchainList",
storages = [Storage("zigbrains.xml")] storages = [Storage("zigbrains.xml")]
) )
class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchainListService.State>(State()) { class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchainListService.State>(State()), ZigToolchainListService.IService {
private val changeListeners = ArrayList<WeakReference<ToolchainListChangeListener>>() private val changeListeners = ArrayList<WeakReference<ToolchainListChangeListener>>()
fun setToolchain(uuid: UUID, toolchain: ZigToolchain) {
override val toolchains: Sequence<Pair<UUID, ZigToolchain>>
get() = state.toolchains
.asSequence()
.mapNotNull {
val uuid = UUID.fromString(it.key) ?: return@mapNotNull null
val tc = it.value.resolve() ?: return@mapNotNull null
uuid to tc
}
override fun setToolchain(uuid: UUID, toolchain: ZigToolchain) {
val str = uuid.toString() val str = uuid.toString()
val ref = toolchain.toRef() val ref = toolchain.toRef()
updateState { updateState {
@ -50,7 +60,7 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
notifyChanged() notifyChanged()
} }
fun registerNewToolchain(toolchain: ZigToolchain): UUID { override fun registerNewToolchain(toolchain: ZigToolchain): UUID {
val ref = toolchain.toRef() val ref = toolchain.toRef()
var uuid = UUID.randomUUID() var uuid = UUID.randomUUID()
updateState { updateState {
@ -68,15 +78,15 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
return uuid return uuid
} }
fun getToolchain(uuid: UUID): ZigToolchain? { override fun getToolchain(uuid: UUID): ZigToolchain? {
return state.toolchains[uuid.toString()]?.resolve() return state.toolchains[uuid.toString()]?.resolve()
} }
fun hasToolchain(uuid: UUID): Boolean { override fun hasToolchain(uuid: UUID): Boolean {
return state.toolchains.containsKey(uuid.toString()) return state.toolchains.containsKey(uuid.toString())
} }
fun removeToolchain(uuid: UUID) { override fun removeToolchain(uuid: UUID) {
val str = uuid.toString() val str = uuid.toString()
updateState { updateState {
it.copy(toolchains = it.toolchains.filter { it.key != str }) it.copy(toolchains = it.toolchains.filter { it.key != str })
@ -84,6 +94,21 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
notifyChanged() notifyChanged()
} }
override fun addChangeListener(listener: ToolchainListChangeListener) {
synchronized(changeListeners) {
changeListeners.add(WeakReference(listener))
}
}
override fun removeChangeListener(listener: ToolchainListChangeListener) {
synchronized(changeListeners) {
changeListeners.removeIf {
val v = it.get()
v == null || v === listener
}
}
}
private fun notifyChanged() { private fun notifyChanged() {
synchronized(changeListeners) { synchronized(changeListeners) {
var i = 0 var i = 0
@ -101,31 +126,6 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
} }
} }
fun addChangeListener(listener: ToolchainListChangeListener) {
synchronized(changeListeners) {
changeListeners.add(WeakReference(listener))
}
}
fun removeChangeListener(listener: ToolchainListChangeListener) {
synchronized(changeListeners) {
changeListeners.removeIf {
val v = it.get()
v == null || v === listener
}
}
}
val toolchains: Sequence<Pair<UUID, ZigToolchain>>
get() = state.toolchains
.asSequence()
.mapNotNull {
val uuid = UUID.fromString(it.key) ?: return@mapNotNull null
val tc = it.value.resolve() ?: return@mapNotNull null
uuid to tc
}
data class State( data class State(
@JvmField @JvmField
val toolchains: Map<String, ZigToolchain.Ref> = emptyMap(), val toolchains: Map<String, ZigToolchain.Ref> = emptyMap(),
@ -133,7 +133,18 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
companion object { companion object {
@JvmStatic @JvmStatic
fun getInstance(): ZigToolchainListService = service() fun getInstance(): IService = service<ZigToolchainListService>()
}
sealed interface IService {
val toolchains: Sequence<Pair<UUID, ZigToolchain>>
fun setToolchain(uuid: UUID, toolchain: ZigToolchain)
fun registerNewToolchain(toolchain: ZigToolchain): UUID
fun getToolchain(uuid: UUID): ZigToolchain?
fun hasToolchain(uuid: UUID): Boolean
fun removeToolchain(uuid: UUID)
fun addChangeListener(listener: ToolchainListChangeListener)
fun removeChangeListener(listener: ToolchainListChangeListener)
} }
@FunctionalInterface @FunctionalInterface

View file

@ -37,8 +37,8 @@ import java.util.UUID
name = "ZigToolchain", name = "ZigToolchain",
storages = [Storage("zigbrains.xml")] storages = [Storage("zigbrains.xml")]
) )
class ZigToolchainService: SerializablePersistentStateComponent<ZigToolchainService.State>(State()) { class ZigToolchainService: SerializablePersistentStateComponent<ZigToolchainService.State>(State()), ZigToolchainService.IService {
var toolchainUUID: UUID? override var toolchainUUID: UUID?
get() = state.toolchain.ifBlank { null }?.let { UUID.fromString(it) }?.takeIf { get() = state.toolchain.ifBlank { null }?.let { UUID.fromString(it) }?.takeIf {
if (ZigToolchainListService.getInstance().hasToolchain(it)) { if (ZigToolchainListService.getInstance().hasToolchain(it)) {
true true
@ -55,7 +55,7 @@ class ZigToolchainService: SerializablePersistentStateComponent<ZigToolchainServ
} }
} }
val toolchain: ZigToolchain? override val toolchain: ZigToolchain?
get() = toolchainUUID?.let { ZigToolchainListService.getInstance().getToolchain(it) } get() = toolchainUUID?.let { ZigToolchainListService.getInstance().getToolchain(it) }
data class State( data class State(
@ -66,6 +66,11 @@ class ZigToolchainService: SerializablePersistentStateComponent<ZigToolchainServ
companion object { companion object {
@JvmStatic @JvmStatic
fun getInstance(project: Project): ZigToolchainService = project.service() fun getInstance(project: Project): IService = project.service<ZigToolchainService>()
}
sealed interface IService {
var toolchainUUID: UUID?
val toolchain: ZigToolchain?
} }
} }

View file

@ -25,21 +25,23 @@ package com.falsepattern.zigbrains.project.toolchain.base
import com.falsepattern.zigbrains.project.toolchain.tools.ZigCompilerTool import com.falsepattern.zigbrains.project.toolchain.tools.ZigCompilerTool
import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.util.xmlb.annotations.Attribute import com.intellij.util.xmlb.annotations.Attribute
import com.intellij.util.xmlb.annotations.MapAnnotation import com.intellij.util.xmlb.annotations.MapAnnotation
import java.nio.file.Path import java.nio.file.Path
abstract class ZigToolchain: UserDataHolderBase() { /**
val zig: ZigCompilerTool by lazy { ZigCompilerTool(this) } * These MUST be stateless and interchangeable! (e.g., immutable data class)
*/
interface ZigToolchain {
val zig: ZigCompilerTool get() = ZigCompilerTool(this)
abstract val name: String? val name: String?
abstract fun workingDirectory(project: Project? = null): Path? fun workingDirectory(project: Project? = null): Path?
abstract suspend fun patchCommandLine(commandLine: GeneralCommandLine, project: Project? = null): GeneralCommandLine suspend fun patchCommandLine(commandLine: GeneralCommandLine, project: Project? = null): GeneralCommandLine
abstract fun pathToExecutable(toolName: String, project: Project? = null): Path fun pathToExecutable(toolName: String, project: Project? = null): Path
data class Ref( data class Ref(
@JvmField @JvmField

View file

@ -26,6 +26,7 @@ import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
import com.intellij.openapi.ui.NamedConfigurable import com.intellij.openapi.ui.NamedConfigurable
import com.intellij.openapi.util.NlsContexts import com.intellij.openapi.util.NlsContexts
import com.intellij.ui.dsl.builder.panel import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.util.minimumWidth
import java.util.UUID import java.util.UUID
import javax.swing.JComponent import javax.swing.JComponent
@ -40,6 +41,8 @@ abstract class ZigToolchainConfigurable<T: ZigToolchain>(
} }
private var myView: ZigToolchainPanel<T>? = null private var myView: ZigToolchainPanel<T>? = null
var floating: Boolean = false
abstract fun createPanel(): ZigToolchainPanel<T> abstract fun createPanel(): ZigToolchainPanel<T>
override fun createOptionsPanel(): JComponent? { override fun createOptionsPanel(): JComponent? {
@ -51,7 +54,7 @@ abstract class ZigToolchainConfigurable<T: ZigToolchain>(
} }
return panel { return panel {
view.attach(this) view.attach(this)
} }.withMinimumWidth(20)
} }
override fun getEditableObject(): UUID? { override fun getEditableObject(): UUID? {

View file

@ -27,9 +27,11 @@ import com.intellij.openapi.Disposable
import com.intellij.ui.components.JBTextField import com.intellij.ui.components.JBTextField
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.util.preferredHeight
import java.awt.Dimension
abstract class ZigToolchainPanel<T: ZigToolchain>: Disposable { abstract class ZigToolchainPanel<T: ZigToolchain>: Disposable {
private val nameField = JBTextField() private val nameField = JBTextField(25)
protected var nameFieldValue: String? protected var nameFieldValue: String?
get() = nameField.text.ifBlank { null } get() = nameField.text.ifBlank { null }

View file

@ -25,6 +25,7 @@ package com.falsepattern.zigbrains.project.toolchain.downloader
import com.falsepattern.zigbrains.ZigBrainsBundle import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
import com.falsepattern.zigbrains.project.toolchain.local.getSuggestedLocalToolchainPath
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
@ -52,6 +53,7 @@ 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.io.path.pathString
object Downloader { object Downloader {
suspend fun downloadToolchain(component: Component): ZigToolchain? { suspend fun downloadToolchain(component: Component): ZigToolchain? {
@ -129,7 +131,7 @@ object Downloader {
}) })
var archiveSizeCell: Cell<*>? = null var archiveSizeCell: Cell<*>? = null
fun detect(item: ZigVersionInfo) { fun detect(item: ZigVersionInfo) {
outputPath.text = System.getProperty("user.home") + "/.zig/" + item.version outputPath.text = getSuggestedLocalToolchainPath()?.resolve(item.version.rawVersion)?.pathString ?: ""
val size = item.dist.size val size = item.dist.size
val sizeMb = size / (1024f * 1024f) val sizeMb = size / (1024f * 1024f)
archiveSizeCell?.comment?.text = ZigBrainsBundle.message("settings.toolchain.downloader.archive-size.text", "%.2fMB".format(sizeMb)) archiveSizeCell?.comment?.text = ZigBrainsBundle.message("settings.toolchain.downloader.archive-size.text", "%.2fMB".format(sizeMb))

View file

@ -23,7 +23,6 @@
package com.falsepattern.zigbrains.project.toolchain.local package com.falsepattern.zigbrains.project.toolchain.local
import com.falsepattern.zigbrains.direnv.DirenvCmd import com.falsepattern.zigbrains.direnv.DirenvCmd
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
import com.intellij.execution.ExecutionException import com.intellij.execution.ExecutionException
import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.execution.configurations.GeneralCommandLine
@ -35,15 +34,17 @@ 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
data class LocalZigToolchain(val location: Path, val std: Path? = null, override val name: String? = null): ZigToolchain() { @JvmRecord
data class LocalZigToolchain(val location: Path, val std: Path? = null, override val name: String? = null): ZigToolchain {
override fun workingDirectory(project: Project?): Path? { override fun workingDirectory(project: Project?): Path? {
return project?.guessProjectDir()?.toNioPathOrNull() return project?.guessProjectDir()?.toNioPathOrNull()
} }
override suspend fun patchCommandLine(commandLine: GeneralCommandLine, project: Project?): GeneralCommandLine { override suspend fun patchCommandLine(commandLine: GeneralCommandLine, project: Project?): GeneralCommandLine {
if (project != null && (commandLine.getUserData(DIRENV_KEY) ?: project.zigProjectSettings.state.direnv)) { //TODO direnv
commandLine.withEnvironment(DirenvCmd.importDirenv(project).env) // if (project != null && (commandLine.getUserData(DIRENV_KEY) ?: project.zigProjectSettings.state.direnv)) {
} // commandLine.withEnvironment(DirenvCmd.importDirenv(project).env)
// }
return commandLine return commandLine
} }

View file

@ -59,7 +59,7 @@ class LocalZigToolchainPanel() : ZigToolchainPanel<LocalZigToolchain>() {
Disposer.register(this, it) Disposer.register(this, it)
} }
private val toolchainVersion = JBTextArea().also { it.isEditable = false } private val toolchainVersion = JBTextArea().also { it.isEditable = false }
private val stdFieldOverride = JBCheckBox(ZigBrainsBundle.message("settings.project.label.override-std")).apply { private val stdFieldOverride = JBCheckBox().apply {
addChangeListener { addChangeListener {
if (isSelected) { if (isSelected) {
pathToStd.isEnabled = true pathToStd.isEnabled = true
@ -84,8 +84,8 @@ class LocalZigToolchainPanel() : ZigToolchainPanel<LocalZigToolchain>() {
cell(toolchainVersion) cell(toolchainVersion)
} }
row(ZigBrainsBundle.message("settings.toolchain.local.std.label")) { row(ZigBrainsBundle.message("settings.toolchain.local.std.label")) {
cell(pathToStd).resizableColumn().align(AlignX.FILL)
cell(stdFieldOverride) cell(stdFieldOverride)
cell(pathToStd).resizableColumn().align(AlignX.FILL)
} }
} }

View file

@ -24,7 +24,6 @@ package com.falsepattern.zigbrains.project.toolchain.local
import com.falsepattern.zigbrains.direnv.DirenvCmd import com.falsepattern.zigbrains.direnv.DirenvCmd
import com.falsepattern.zigbrains.direnv.emptyEnv import com.falsepattern.zigbrains.direnv.emptyEnv
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainConfigurable import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainConfigurable
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainProvider import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainProvider
@ -37,17 +36,23 @@ import com.intellij.openapi.util.text.StringUtil
import com.intellij.ui.SimpleColoredComponent 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 java.io.File import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.util.UUID import java.util.UUID
import kotlin.io.path.isDirectory
import kotlin.io.path.pathString import kotlin.io.path.pathString
class LocalZigToolchainProvider: ZigToolchainProvider { class LocalZigToolchainProvider: ZigToolchainProvider {
override suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): LocalZigToolchain? { override suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): LocalZigToolchain? {
val env = if (project != null && (extraData.getUserData(LocalZigToolchain.DIRENV_KEY) ?: project.zigProjectSettings.state.direnv)) { //TODO direnv
DirenvCmd.importDirenv(project) // val env = if (project != null && (extraData.getUserData(LocalZigToolchain.DIRENV_KEY) ?: project.zigProjectSettings.state.direnv)) {
} else { // DirenvCmd.importDirenv(project)
emptyEnv // } else {
} // emptyEnv
// }
val env = emptyEnv
val zigExePath = env.findExecutableOnPATH("zig") ?: return null val zigExePath = env.findExecutableOnPATH("zig") ?: return null
return LocalZigToolchain(zigExePath.parent) return LocalZigToolchain(zigExePath.parent)
} }
@ -95,6 +100,18 @@ class LocalZigToolchainProvider: ZigToolchainProvider {
override fun suggestToolchains(): List<ZigToolchain> { override fun suggestToolchains(): List<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()
wellKnown.forEach { dir ->
if (!dir.isDirectory())
return@forEach
runCatching {
Files.newDirectoryStream(dir).use { stream ->
stream.forEach { subDir ->
res.add(subDir.pathString)
}
}
}
}
return res.mapNotNull { LocalZigToolchain.tryFromPathString(it) } return res.mapNotNull { LocalZigToolchain.tryFromPathString(it) }
} }
@ -119,6 +136,25 @@ class LocalZigToolchainProvider: ZigToolchainProvider {
} }
} }
fun getSuggestedLocalToolchainPath(): Path? {
return getWellKnown().getOrNull(0)
}
private fun getWellKnown(): List<Path> {
val home = System.getProperty("user.home")?.toNioPathOrNull() ?: return emptyList()
val xdgDataHome = when(OS.CURRENT) {
OS.macOS -> home.resolve("Library")
OS.Windows -> System.getenv("LOCALAPPDATA")?.toNioPathOrNull()
else -> System.getenv("XDG_DATA_HOME")?.toNioPathOrNull() ?: home.resolve(Path.of(".local", "share"))
}
val res = ArrayList<Path>()
if (xdgDataHome != null && xdgDataHome.isDirectory()) {
res.add(xdgDataHome.resolve("zig"))
res.add(xdgDataHome.resolve("zigup"))
}
res.add(home.resolve(".zig"))
return res
}
private fun presentDetectedPath(home: String, maxLength: Int = 50, suffixLength: Int = 30): String { private fun presentDetectedPath(home: String, maxLength: Int = 50, suffixLength: Int = 30): String {
//for macOS, let's try removing Bundle internals //for macOS, let's try removing Bundle internals

View file

@ -25,183 +25,146 @@ package com.falsepattern.zigbrains.project.toolchain.ui
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.ZigToolchainService import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
import com.falsepattern.zigbrains.project.toolchain.base.createNamedConfigurable
import com.falsepattern.zigbrains.project.toolchain.base.suggestZigToolchains import com.falsepattern.zigbrains.project.toolchain.base.suggestZigToolchains
import com.falsepattern.zigbrains.shared.SubConfigurable
import com.falsepattern.zigbrains.shared.coroutine.asContextElement import com.falsepattern.zigbrains.shared.coroutine.asContextElement
import com.falsepattern.zigbrains.shared.coroutine.launchWithEDT import com.falsepattern.zigbrains.shared.coroutine.launchWithEDT
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
import com.falsepattern.zigbrains.shared.zigCoroutineScope import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.EDT import com.intellij.openapi.application.EDT
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.options.ShowSettingsUtil 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.project.ProjectManager
import com.intellij.openapi.ui.DialogWrapper import com.intellij.openapi.ui.DialogWrapper
import com.intellij.openapi.util.Disposer
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.util.concurrency.annotations.RequiresEdt import com.intellij.util.concurrency.annotations.RequiresEdt
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext 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 java.util.UUID
import javax.swing.JComponent import javax.swing.JButton
import kotlin.collections.addAll import kotlin.collections.addAll
class ZigToolchainEditor(private val project: Project): Configurable { class ZigToolchainEditor(private val isForDefaultProject: Boolean = false): SubConfigurable<Project>, ZigToolchainListService.ToolchainListChangeListener {
private var myUi: UI? = null private val toolchainBox: TCComboBox
override fun getDisplayName(): @NlsContexts.ConfigurableName String? { private var selectOnNextReload: UUID? = null
return ZigBrainsBundle.message("settings.toolchain.editor.display-name") private val model: TCModel
private lateinit var editButton: JButton
init {
model = TCModel(getModelList())
toolchainBox = TCComboBox(model)
toolchainBox.addItemListener(::itemStateChanged)
ZigToolchainListService.getInstance().addChangeListener(this)
} }
override fun createComponent(): JComponent? { private fun itemStateChanged(event: ItemEvent) {
if (myUi != null) { if (event.stateChange != ItemEvent.SELECTED) {
disposeUIResources() return
} }
val ui = UI() val item = event.item
myUi = ui if (item !is TCListElem.Pseudo)
return panel { return
ui.attach(this) zigCoroutineScope.launch(toolchainBox.asContextElement()) {
} val uuid = ZigToolchainComboBoxHandler.onItemSelected(toolchainBox, item)
} withEDTContext(toolchainBox.asContextElement()) {
applyUUIDNowOrOnReload(uuid)
override fun isModified(): Boolean {
return myUi?.isModified() == true
}
override fun apply() {
myUi?.apply()
}
override fun reset() {
myUi?.reset()
}
override fun disposeUIResources() {
myUi?.let { Disposer.dispose(it) }
myUi = null
super.disposeUIResources()
}
inner class UI(): Disposable, ZigToolchainListService.ToolchainListChangeListener {
private val toolchainBox: TCComboBox
private var selectOnNextReload: UUID? = null
private val model: TCModel
init {
model = TCModel(getModelList())
toolchainBox = TCComboBox(model)
toolchainBox.addItemListener(::itemStateChanged)
ZigToolchainListService.getInstance().addChangeListener(this)
reset()
}
private fun itemStateChanged(event: ItemEvent) {
if (event.stateChange != ItemEvent.SELECTED) {
return
}
val item = event.item
if (item !is TCListElem.Pseudo)
return
zigCoroutineScope.launch(toolchainBox.asContextElement()) {
val uuid = ZigToolchainComboBoxHandler.onItemSelected(toolchainBox, item)
withEDTContext(toolchainBox.asContextElement()) {
applyUUIDNowOrOnReload(uuid)
}
} }
} }
}
override suspend fun toolchainListChanged() { override suspend fun toolchainListChanged() {
withContext(Dispatchers.EDT + toolchainBox.asContextElement()) { withContext(Dispatchers.EDT + toolchainBox.asContextElement()) {
val list = getModelList() val list = getModelList()
model.updateContents(list) model.updateContents(list)
val onReload = selectOnNextReload val onReload = selectOnNextReload
selectOnNextReload = null selectOnNextReload = null
if (onReload != null) { if (onReload != null) {
val element = list.firstOrNull { when(it) { val element = list.firstOrNull { when(it) {
is TCListElem.Toolchain.Actual -> it.uuid == onReload is TCListElem.Toolchain.Actual -> it.uuid == onReload
else -> false else -> false
} } } }
model.selectedItem = element model.selectedItem = element
return@withContext 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
} }
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
} }
withContext(Dispatchers.EDT + editButton.asContextElement()) {
editButton.isEnabled = model.selectedItem is TCListElem.Toolchain.Actual
editButton.repaint()
}
}
fun attach(p: Panel): Unit = with(p) { override fun attach(p: Panel): Unit = with(p) {
row(ZigBrainsBundle.message("settings.toolchain.editor.toolchain.label")) { row(ZigBrainsBundle.message(
cell(toolchainBox).resizableColumn().align(AlignX.FILL) if (isForDefaultProject)
button(ZigBrainsBundle.message("settings.toolchain.editor.toolchain.edit-button.name")) { e -> "settings.toolchain.editor.toolchain-default.label"
zigCoroutineScope.launchWithEDT(toolchainBox.asContextElement()) { else
val config = ZigToolchainListEditor() "settings.toolchain.editor.toolchain.label")
var inited = false ) {
var selectedUUID: UUID? = toolchainBox.selectedToolchain cell(toolchainBox).resizableColumn().align(AlignX.FILL)
config.addItemSelectedListener { button(ZigBrainsBundle.message("settings.toolchain.editor.toolchain.edit-button.name")) { e ->
if (inited) { zigCoroutineScope.launchWithEDT(toolchainBox.asContextElement()) {
selectedUUID = it var selectedUUID = toolchainBox.selectedToolchain ?: return@launchWithEDT
} val toolchain = ZigToolchainListService.getInstance().getToolchain(selectedUUID) ?: return@launchWithEDT
} val config = toolchain.createNamedConfigurable(selectedUUID)
val apply = ShowSettingsUtil.getInstance().editConfigurable(DialogWrapper.findInstance(toolchainBox)?.contentPane, config) { val apply = ShowSettingsUtil.getInstance().editConfigurable(DialogWrapper.findInstance(toolchainBox)?.contentPane, config)
config.selectNodeInTree(selectedUUID) if (apply) {
inited = true applyUUIDNowOrOnReload(selectedUUID)
}
if (apply) {
applyUUIDNowOrOnReload(selectedUUID)
}
} }
} }
} }.component.let { editButton = it }
} }
}
@RequiresEdt @RequiresEdt
private fun applyUUIDNowOrOnReload(uuid: UUID?) { private fun applyUUIDNowOrOnReload(uuid: UUID?) {
toolchainBox.selectedToolchain = uuid toolchainBox.selectedToolchain = uuid
if (uuid != null && toolchainBox.selectedToolchain == null) { if (uuid != null && toolchainBox.selectedToolchain == null) {
selectOnNextReload = uuid selectOnNextReload = uuid
} else { } else {
selectOnNextReload = null selectOnNextReload = null
}
} }
}
fun isModified(): Boolean { override fun isModified(context: Project): Boolean {
return ZigToolchainService.getInstance(project).toolchainUUID != toolchainBox.selectedToolchain return ZigToolchainService.getInstance(context).toolchainUUID != toolchainBox.selectedToolchain
} }
fun apply() { override fun apply(context: Project) {
ZigToolchainService.getInstance(project).toolchainUUID = toolchainBox.selectedToolchain ZigToolchainService.getInstance(context).toolchainUUID = toolchainBox.selectedToolchain
} }
fun reset() { override fun reset(context: Project?) {
toolchainBox.selectedToolchain = ZigToolchainService.getInstance(project).toolchainUUID val project = context ?: ProjectManager.getInstance().defaultProject
} toolchainBox.selectedToolchain = ZigToolchainService.getInstance(project).toolchainUUID
}
override fun dispose() {
ZigToolchainListService.getInstance().removeChangeListener(this)
}
override val newProjectBeforeInitSelector get() = true
override fun dispose() { class Adapter(override val context: Project): SubConfigurable.Adapter<Project>() {
ZigToolchainListService.getInstance().removeChangeListener(this) override fun instantiate() = ZigToolchainEditor(context.isDefault)
} override fun getDisplayName() = ZigBrainsBundle.message("settings.toolchain.editor.display-name")
} }
} }

View file

@ -1,57 +0,0 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2025 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.shared
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.util.Disposer
import com.intellij.ui.dsl.builder.panel
import javax.swing.JComponent
abstract class MultiConfigurable(val configurables: List<SubConfigurable>): Configurable, ZigProjectConfigurationProvider.SettingsPanelHolder {
final override var panels: List<ZigProjectConfigurationProvider.SettingsPanel> = emptyList()
private set
override fun createComponent(): JComponent? {
return panel {
panels = configurables.map { it.createComponent(this@MultiConfigurable, this@panel) }
}
}
override fun isModified(): Boolean {
return configurables.any { it.isModified() }
}
override fun apply() {
configurables.forEach { it.apply() }
}
override fun reset() {
configurables.forEach { it.reset() }
}
override fun disposeUIResources() {
configurables.forEach { Disposer.dispose(it) }
panels = emptyList()
}
}

View file

@ -22,16 +22,56 @@
package com.falsepattern.zigbrains.shared package com.falsepattern.zigbrains.shared
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider.SettingsPanelHolder
import com.intellij.openapi.Disposable import com.intellij.openapi.Disposable
import com.intellij.openapi.options.ConfigurationException import com.intellij.openapi.options.Configurable
import com.intellij.openapi.util.Disposer
import com.intellij.ui.dsl.builder.Panel import com.intellij.ui.dsl.builder.Panel
import com.intellij.ui.dsl.builder.panel
import javax.swing.JComponent
interface SubConfigurable: Disposable { interface SubConfigurable<T>: Disposable {
fun createComponent(holder: SettingsPanelHolder, panel: Panel): ZigProjectConfigurationProvider.SettingsPanel fun attach(panel: Panel)
fun isModified(): Boolean fun isModified(context: T): Boolean
@Throws(ConfigurationException::class) fun apply(context: T)
fun apply() fun reset(context: T?)
fun reset()
val newProjectBeforeInitSelector: Boolean get() = false
abstract class Adapter<T>: Configurable {
private var myConfigurable: SubConfigurable<T>? = null
abstract fun instantiate(): SubConfigurable<T>
protected abstract val context: T
override fun createComponent(): JComponent? {
if (myConfigurable != null) {
disposeUIResources()
}
val configurable = instantiate()
configurable.reset(context)
myConfigurable = configurable
return panel {
configurable.attach(this)
}
}
override fun isModified(): Boolean {
return myConfigurable?.isModified(context) == true
}
override fun apply() {
myConfigurable?.apply(context)
}
override fun reset() {
myConfigurable?.reset(context)
}
override fun disposeUIResources() {
val configurable = myConfigurable
myConfigurable = null
configurable?.let { Disposer.dispose(it) }
super.disposeUIResources()
}
}
} }

View file

@ -140,7 +140,7 @@
/> />
<projectConfigurable <projectConfigurable
parentId="language" parentId="language"
instance="com.falsepattern.zigbrains.project.toolchain.ui.ZigToolchainEditor" instance="com.falsepattern.zigbrains.project.toolchain.ui.ZigToolchainEditor$Adapter"
id="ZigConfigurable" id="ZigConfigurable"
displayName="Zig" displayName="Zig"
/> />

View file

@ -116,6 +116,7 @@ settings.toolchain.local.version.label=Detected zig version
settings.toolchain.local.std.label=Override standard library settings.toolchain.local.std.label=Override standard library
settings.toolchain.editor.display-name=Zig settings.toolchain.editor.display-name=Zig
settings.toolchain.editor.toolchain.label=Toolchain settings.toolchain.editor.toolchain.label=Toolchain
settings.toolchain.editor.toolchain-default.label=Default toolchain
settings.toolchain.editor.toolchain.edit-button.name=Edit settings.toolchain.editor.toolchain.edit-button.name=Edit
settings.toolchain.model.detected.separator=Detected toolchains settings.toolchain.model.detected.separator=Detected toolchains
settings.toolchain.model.none.text=<No Toolchain> settings.toolchain.model.none.text=<No Toolchain>

View file

@ -34,7 +34,7 @@ class ZLSProjectConfigurationProvider: ZigProjectConfigurationProvider {
startLSP(project, true) startLSP(project, true)
} }
override fun createConfigurable(project: Project): SubConfigurable { override fun createConfigurable(project: Project): SubConfigurable<Project> {
return ZLSSettingsConfigurable(project) return ZLSSettingsConfigurable(project)
} }

View file

@ -4,7 +4,7 @@
<vendor>FalsePattern</vendor> <vendor>FalsePattern</vendor>
<depends config-file="zigbrains-core.xml">com.intellij.modules.platform</depends> <depends config-file="zigbrains-core.xml">com.intellij.modules.platform</depends>
<depends config-file="zigbrains-lsp.xml">com.redhat.devtools.lsp4ij</depends> <depends config-file="zigbrains-lsp.xml" optional="true">com.redhat.devtools.lsp4ij</depends>
<depends config-file="zigbrains-debugger.xml" optional="true">com.intellij.modules.cidr.debugger</depends> <depends config-file="zigbrains-debugger.xml" optional="true">com.intellij.modules.cidr.debugger</depends>
<depends config-file="zigbrains-cidr.xml" optional="true">com.intellij.cidr.base</depends> <depends config-file="zigbrains-cidr.xml" optional="true">com.intellij.cidr.base</depends>
<depends config-file="zigbrains-clion.xml" optional="true">com.intellij.clion</depends> <depends config-file="zigbrains-clion.xml" optional="true">com.intellij.clion</depends>