modular project configurables

This commit is contained in:
FalsePattern 2025-04-09 23:20:40 +02:00
parent 12e5ffdea1
commit c7e33ea8de
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
9 changed files with 71 additions and 84 deletions

View file

@ -41,7 +41,7 @@ import javax.swing.ListSelectionModel
class ZigNewProjectPanel(private var handleGit: Boolean): Disposable { class ZigNewProjectPanel(private var handleGit: Boolean): Disposable {
private val git = JBCheckBox() private val git = JBCheckBox()
val panels = ZigProjectConfigurationProvider.createNewProjectSettingsPanels().onEach { Disposer.register(this, it) } val panels = ZigProjectConfigurationProvider.createPanels(null).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

View file

@ -22,23 +22,15 @@
package com.falsepattern.zigbrains.project.settings package com.falsepattern.zigbrains.project.settings
import com.falsepattern.zigbrains.project.toolchain.ui.ZigToolchainEditor import com.falsepattern.zigbrains.ZigBrainsBundle
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.util.NlsContexts
class ZigCoreProjectConfigurationProvider: ZigProjectConfigurationProvider { class ZigConfigurable(override val context: Project) : SubConfigurable.Adapter<Project>() {
override fun handleMainConfigChanged(project: Project) { override fun instantiate(): List<SubConfigurable<Project>> {
return ZigProjectConfigurationProvider.createPanels(context)
} }
override fun createConfigurable(project: Project): Configurable { override fun getDisplayName() = ZigBrainsBundle.message("settings.project.display-name")
return ZigToolchainEditor.Adapter(project)
}
override fun createNewProjectSettingsPanel(): SubConfigurable<Project> {
return ZigToolchainEditor().also { it.reset(null) }
}
override val priority: Int
get() = 0
} }

View file

@ -24,24 +24,15 @@ package com.falsepattern.zigbrains.project.settings
import com.falsepattern.zigbrains.shared.SubConfigurable import com.falsepattern.zigbrains.shared.SubConfigurable
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
interface ZigProjectConfigurationProvider { interface ZigProjectConfigurationProvider {
fun handleMainConfigChanged(project: Project) fun create(project: Project?): SubConfigurable<Project>?
fun createConfigurable(project: Project): Configurable val index: Int
fun createNewProjectSettingsPanel(): SubConfigurable<Project>?
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 createPanels(project: Project?): List<SubConfigurable<Project>> {
EXTENSION_POINT_NAME.extensionList.forEach { it.handleMainConfigChanged(project) } return EXTENSION_POINT_NAME.extensionList.sortedBy { it.index }.mapNotNull { it.create(project) }
}
fun createConfigurables(project: Project): List<Configurable> {
return EXTENSION_POINT_NAME.extensionList.sortedBy { it.priority }.map { it.createConfigurable(project) }
}
fun createNewProjectSettingsPanels(): List<SubConfigurable<Project>> {
return EXTENSION_POINT_NAME.extensionList.sortedBy { it.priority }.mapNotNull { it.createNewProjectSettingsPanel() }
} }
} }
} }

View file

@ -150,6 +150,20 @@ fun getSuggestedLocalToolchainPath(): Path? {
return getWellKnown().getOrNull(0) return getWellKnown().getOrNull(0)
} }
/**
* Returns the paths to the following list of folders:
*
* 1. DATA/zig
* 2. DATA/zigup
* 3. HOME/.zig
*
* Where DATA is:
* - ~/Library on macOS
* - %LOCALAPPDATA% on Windows
* - $XDG_DATA_HOME (or ~/.local/share if not set) on other OSes
*
* and HOME is the user home path
*/
private fun getWellKnown(): List<Path> { private fun getWellKnown(): List<Path> {
val home = System.getProperty("user.home")?.toNioPathOrNull() ?: return emptyList() val home = System.getProperty("user.home")?.toNioPathOrNull() ?: return emptyList()
val xdgDataHome = when(OS.CURRENT) { val xdgDataHome = when(OS.CURRENT) {

View file

@ -23,6 +23,7 @@
package com.falsepattern.zigbrains.project.toolchain.ui package com.falsepattern.zigbrains.project.toolchain.ui
import com.falsepattern.zigbrains.ZigBrainsBundle import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
import com.falsepattern.zigbrains.project.toolchain.ToolchainListChangeListener import com.falsepattern.zigbrains.project.toolchain.ToolchainListChangeListener
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
@ -169,9 +170,13 @@ class ZigToolchainEditor(private val isForDefaultProject: Boolean = false): SubC
override val newProjectBeforeInitSelector get() = true override val newProjectBeforeInitSelector get() = true
class Adapter(override val context: Project): SubConfigurable.Adapter<Project>() { class Provider: ZigProjectConfigurationProvider {
override fun instantiate() = ZigToolchainEditor(context.isDefault) override fun create(project: Project?): SubConfigurable<Project>? {
override fun getDisplayName() = ZigBrainsBundle.message("settings.toolchain.editor.display-name") return ZigToolchainEditor(project?.isDefault ?: false).also { it.reset(project) }
}
override val index: Int get() = 0
} }
} }

View file

@ -22,57 +22,30 @@
package com.falsepattern.zigbrains.project.toolchain.ui package com.falsepattern.zigbrains.project.toolchain.ui
import com.falsepattern.zigbrains.Icons
import com.falsepattern.zigbrains.ZigBrainsBundle import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.toolchain.ToolchainListChangeListener import com.falsepattern.zigbrains.project.toolchain.ToolchainListChangeListener
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
import com.falsepattern.zigbrains.project.toolchain.base.createNamedConfigurable import com.falsepattern.zigbrains.project.toolchain.base.createNamedConfigurable
import com.falsepattern.zigbrains.project.toolchain.base.suggestZigToolchains import com.falsepattern.zigbrains.project.toolchain.base.suggestZigToolchains
import com.falsepattern.zigbrains.project.toolchain.downloader.Downloader
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
import com.falsepattern.zigbrains.shared.coroutine.asContextElement import com.falsepattern.zigbrains.shared.coroutine.asContextElement
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
import com.falsepattern.zigbrains.shared.zigCoroutineScope import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.Presentation import com.intellij.openapi.actionSystem.Presentation
import com.intellij.openapi.application.EDT
import com.intellij.openapi.components.Service
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.ui.DialogBuilder
import com.intellij.openapi.ui.MasterDetailsComponent import com.intellij.openapi.ui.MasterDetailsComponent
import com.intellij.openapi.ui.NamedConfigurable
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.ui.DocumentAdapter
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.textFieldWithBrowseButton
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.panel
import com.intellij.util.Consumer
import com.intellij.util.IconUtil import com.intellij.util.IconUtil
import com.intellij.util.asSafely import com.intellij.util.asSafely
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.UUID import java.util.UUID
import javax.swing.JComponent import javax.swing.JComponent
import javax.swing.event.DocumentEvent
import javax.swing.tree.DefaultTreeModel import javax.swing.tree.DefaultTreeModel
class ZigToolchainListEditor : MasterDetailsComponent(), ToolchainListChangeListener { class ZigToolchainListEditor : MasterDetailsComponent(), ToolchainListChangeListener {
private var isTreeInitialized = false private var isTreeInitialized = false
private var registered: Boolean = false private var registered: Boolean = false
private var itemSelectedListeners = ArrayList<Consumer<UUID?>>()
fun addItemSelectedListener(c: Consumer<UUID?>) {
synchronized(itemSelectedListeners) {
itemSelectedListeners.add(c)
}
}
override fun createComponent(): JComponent { override fun createComponent(): JComponent {
if (!isTreeInitialized) { if (!isTreeInitialized) {
@ -102,14 +75,6 @@ class ZigToolchainListEditor : MasterDetailsComponent(), ToolchainListChangeList
return listOf(add, MyDeleteAction()) return listOf(add, MyDeleteAction())
} }
override fun updateSelection(configurable: NamedConfigurable<*>?) {
super.updateSelection(configurable)
val uuid = configurable?.editableObject as? UUID
synchronized(itemSelectedListeners) {
itemSelectedListeners.forEach { it.consume(uuid) }
}
}
override fun onItemDeleted(item: Any?) { override fun onItemDeleted(item: Any?) {
if (item is UUID) { if (item is UUID) {
ZigToolchainListService.getInstance().removeToolchain(item) ZigToolchainListService.getInstance().removeToolchain(item)

View file

@ -27,6 +27,7 @@ import com.intellij.openapi.options.Configurable
import com.intellij.openapi.util.Disposer 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 com.intellij.ui.dsl.builder.panel
import java.util.ArrayList
import javax.swing.JComponent import javax.swing.JComponent
interface SubConfigurable<T>: Disposable { interface SubConfigurable<T>: Disposable {
@ -38,40 +39,56 @@ interface SubConfigurable<T>: Disposable {
val newProjectBeforeInitSelector: Boolean get() = false val newProjectBeforeInitSelector: Boolean get() = false
abstract class Adapter<T>: Configurable { abstract class Adapter<T>: Configurable {
private var myConfigurable: SubConfigurable<T>? = null private val myConfigurables: MutableList<SubConfigurable<T>> = ArrayList()
abstract fun instantiate(): SubConfigurable<T> abstract fun instantiate(): List<SubConfigurable<T>>
protected abstract val context: T protected abstract val context: T
override fun createComponent(): JComponent? { override fun createComponent(): JComponent? {
if (myConfigurable != null) { val configurables: List<SubConfigurable<T>>
disposeUIResources() synchronized(myConfigurables) {
if (myConfigurables.isEmpty()) {
disposeConfigurables()
}
configurables = instantiate()
configurables.forEach { it.reset(context) }
myConfigurables.clear()
myConfigurables.addAll(configurables)
} }
val configurable = instantiate()
configurable.reset(context)
myConfigurable = configurable
return panel { return panel {
configurable.attach(this) configurables.forEach { it.attach(this) }
} }
} }
override fun isModified(): Boolean { override fun isModified(): Boolean {
return myConfigurable?.isModified(context) == true synchronized(myConfigurables) {
return myConfigurables.any { it.isModified(context) }
}
} }
override fun apply() { override fun apply() {
myConfigurable?.apply(context) synchronized(myConfigurables) {
myConfigurables.forEach { it.apply(context) }
}
} }
override fun reset() { override fun reset() {
myConfigurable?.reset(context) synchronized(myConfigurables) {
myConfigurables.forEach { it.reset(context) }
}
} }
override fun disposeUIResources() { override fun disposeUIResources() {
val configurable = myConfigurable synchronized(myConfigurables) {
myConfigurable = null disposeConfigurables()
configurable?.let { Disposer.dispose(it) } }
super.disposeUIResources() super.disposeUIResources()
} }
private fun disposeConfigurables() {
val configurables = ArrayList(myConfigurables)
myConfigurables.clear()
configurables.forEach { Disposer.dispose(it) }
}
} }
} }

View file

@ -140,15 +140,17 @@
/> />
<projectConfigurable <projectConfigurable
parentId="language" parentId="language"
instance="com.falsepattern.zigbrains.project.toolchain.ui.ZigToolchainEditor$Adapter" instance="com.falsepattern.zigbrains.project.settings.ZigConfigurable"
id="ZigConfigurable" id="ZigConfigurable"
displayName="Zig" bundle="zigbrains.Bundle"
key="settings.project.display-name"
/> />
<applicationConfigurable <applicationConfigurable
parentId="ZigConfigurable" parentId="ZigConfigurable"
instance="com.falsepattern.zigbrains.project.toolchain.ui.ZigToolchainListEditor" instance="com.falsepattern.zigbrains.project.toolchain.ui.ZigToolchainListEditor"
id="ZigToolchainConfigurable" id="ZigToolchainConfigurable"
displayName="Toolchain" bundle="zigbrains.Bundle"
key="settings.toolchain.list.title"
/> />
<programRunner <programRunner
@ -186,7 +188,7 @@
implementation="com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchainProvider" implementation="com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchainProvider"
/> />
<projectConfigProvider <projectConfigProvider
implementation="com.falsepattern.zigbrains.project.settings.ZigCoreProjectConfigurationProvider" implementation="com.falsepattern.zigbrains.project.toolchain.ui.ZigToolchainEditor$Provider"
/> />
</extensions> </extensions>

View file

@ -110,6 +110,7 @@ build.tool.window.status.error.general=Error while running zig build -l
build.tool.window.status.no-builds=No builds currently in progress build.tool.window.status.no-builds=No builds currently in progress
build.tool.window.status.timeout=zig build -l timed out after {0} seconds. build.tool.window.status.timeout=zig build -l timed out after {0} seconds.
zig=Zig zig=Zig
settings.project.display-name=Zig
settings.toolchain.base.name.label=Name settings.toolchain.base.name.label=Name
settings.toolchain.local.path.label=Toolchain location settings.toolchain.local.path.label=Toolchain location
settings.toolchain.local.version.label=Detected zig version settings.toolchain.local.version.label=Detected zig version