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 {
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 {
selectionMode = ListSelectionModel.SINGLE_SELECTION
selectedIndex = 0

View file

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

View file

@ -24,24 +24,15 @@ package com.falsepattern.zigbrains.project.settings
import com.falsepattern.zigbrains.shared.SubConfigurable
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.project.Project
interface ZigProjectConfigurationProvider {
fun handleMainConfigChanged(project: Project)
fun createConfigurable(project: Project): Configurable
fun createNewProjectSettingsPanel(): SubConfigurable<Project>?
val priority: Int
fun create(project: Project?): SubConfigurable<Project>?
val index: Int
companion object {
private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigProjectConfigurationProvider>("com.falsepattern.zigbrains.projectConfigProvider")
fun mainConfigChanged(project: Project) {
EXTENSION_POINT_NAME.extensionList.forEach { it.handleMainConfigChanged(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() }
fun createPanels(project: Project?): List<SubConfigurable<Project>> {
return EXTENSION_POINT_NAME.extensionList.sortedBy { it.index }.mapNotNull { it.create(project) }
}
}
}

View file

@ -150,6 +150,20 @@ fun getSuggestedLocalToolchainPath(): Path? {
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> {
val home = System.getProperty("user.home")?.toNioPathOrNull() ?: return emptyList()
val xdgDataHome = when(OS.CURRENT) {

View file

@ -23,6 +23,7 @@
package com.falsepattern.zigbrains.project.toolchain.ui
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.ZigToolchainListService
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
@ -169,9 +170,13 @@ class ZigToolchainEditor(private val isForDefaultProject: Boolean = false): SubC
override val newProjectBeforeInitSelector get() = true
class Adapter(override val context: Project): SubConfigurable.Adapter<Project>() {
override fun instantiate() = ZigToolchainEditor(context.isDefault)
override fun getDisplayName() = ZigBrainsBundle.message("settings.toolchain.editor.display-name")
class Provider: ZigProjectConfigurationProvider {
override fun create(project: Project?): SubConfigurable<Project>? {
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
import com.falsepattern.zigbrains.Icons
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.toolchain.ToolchainListChangeListener
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
import com.falsepattern.zigbrains.project.toolchain.base.createNamedConfigurable
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.withEDTContext
import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.icons.AllIcons
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
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.ui.DialogBuilder
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.asSafely
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.UUID
import javax.swing.JComponent
import javax.swing.event.DocumentEvent
import javax.swing.tree.DefaultTreeModel
class ZigToolchainListEditor : MasterDetailsComponent(), ToolchainListChangeListener {
private var isTreeInitialized = 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 {
if (!isTreeInitialized) {
@ -102,14 +75,6 @@ class ZigToolchainListEditor : MasterDetailsComponent(), ToolchainListChangeList
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?) {
if (item is UUID) {
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.ui.dsl.builder.Panel
import com.intellij.ui.dsl.builder.panel
import java.util.ArrayList
import javax.swing.JComponent
interface SubConfigurable<T>: Disposable {
@ -38,40 +39,56 @@ interface SubConfigurable<T>: Disposable {
val newProjectBeforeInitSelector: Boolean get() = false
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
override fun createComponent(): JComponent? {
if (myConfigurable != null) {
disposeUIResources()
val configurables: List<SubConfigurable<T>>
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 {
configurable.attach(this)
configurables.forEach { it.attach(this) }
}
}
override fun isModified(): Boolean {
return myConfigurable?.isModified(context) == true
synchronized(myConfigurables) {
return myConfigurables.any { it.isModified(context) }
}
}
override fun apply() {
myConfigurable?.apply(context)
synchronized(myConfigurables) {
myConfigurables.forEach { it.apply(context) }
}
}
override fun reset() {
myConfigurable?.reset(context)
synchronized(myConfigurables) {
myConfigurables.forEach { it.reset(context) }
}
}
override fun disposeUIResources() {
val configurable = myConfigurable
myConfigurable = null
configurable?.let { Disposer.dispose(it) }
synchronized(myConfigurables) {
disposeConfigurables()
}
super.disposeUIResources()
}
private fun disposeConfigurables() {
val configurables = ArrayList(myConfigurables)
myConfigurables.clear()
configurables.forEach { Disposer.dispose(it) }
}
}
}

View file

@ -140,15 +140,17 @@
/>
<projectConfigurable
parentId="language"
instance="com.falsepattern.zigbrains.project.toolchain.ui.ZigToolchainEditor$Adapter"
instance="com.falsepattern.zigbrains.project.settings.ZigConfigurable"
id="ZigConfigurable"
displayName="Zig"
bundle="zigbrains.Bundle"
key="settings.project.display-name"
/>
<applicationConfigurable
parentId="ZigConfigurable"
instance="com.falsepattern.zigbrains.project.toolchain.ui.ZigToolchainListEditor"
id="ZigToolchainConfigurable"
displayName="Toolchain"
bundle="zigbrains.Bundle"
key="settings.toolchain.list.title"
/>
<programRunner
@ -186,7 +188,7 @@
implementation="com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchainProvider"
/>
<projectConfigProvider
implementation="com.falsepattern.zigbrains.project.settings.ZigCoreProjectConfigurationProvider"
implementation="com.falsepattern.zigbrains.project.toolchain.ui.ZigToolchainEditor$Provider"
/>
</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.timeout=zig build -l timed out after {0} seconds.
zig=Zig
settings.project.display-name=Zig
settings.toolchain.base.name.label=Name
settings.toolchain.local.path.label=Toolchain location
settings.toolchain.local.version.label=Detected zig version