abstract away UUID storage for LSP code sharing
This commit is contained in:
parent
1725b189a4
commit
ab20a57e9e
12 changed files with 323 additions and 163 deletions
|
@ -22,125 +22,31 @@
|
|||
|
||||
package com.falsepattern.zigbrains.project.toolchain
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService.MyState
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.resolve
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.toRef
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.falsepattern.zigbrains.shared.AccessibleStorage
|
||||
import com.falsepattern.zigbrains.shared.ChangeTrackingStorage
|
||||
import com.falsepattern.zigbrains.shared.IterableStorage
|
||||
import com.falsepattern.zigbrains.shared.UUIDMapSerializable
|
||||
import com.falsepattern.zigbrains.shared.UUIDStorage
|
||||
import com.intellij.openapi.components.*
|
||||
import kotlinx.coroutines.launch
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.UUID
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
@State(
|
||||
name = "ZigToolchainList",
|
||||
storages = [Storage("zigbrains.xml")]
|
||||
)
|
||||
class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchainListService.State>(State()), IZigToolchainListService {
|
||||
private val changeListeners = ArrayList<WeakReference<ToolchainListChangeListener>>()
|
||||
class ZigToolchainListService: UUIDMapSerializable.Converting<ZigToolchain, ZigToolchain.Ref, MyState>(MyState()), IZigToolchainListService {
|
||||
override fun serialize(value: ZigToolchain) = value.toRef()
|
||||
override fun deserialize(value: ZigToolchain.Ref) = value.resolve()
|
||||
override fun getStorage(state: MyState) = state.toolchains
|
||||
override fun updateStorage(state: MyState, storage: ToolchainStorage) = state.copy(toolchains = storage)
|
||||
|
||||
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 ref = toolchain.toRef()
|
||||
updateState {
|
||||
val newMap = HashMap<String, ZigToolchain.Ref>()
|
||||
newMap.putAll(it.toolchains)
|
||||
newMap[str] = ref
|
||||
it.copy(toolchains = newMap)
|
||||
}
|
||||
notifyChanged()
|
||||
}
|
||||
|
||||
override fun registerNewToolchain(toolchain: ZigToolchain): UUID {
|
||||
val ref = toolchain.toRef()
|
||||
var uuid = UUID.randomUUID()
|
||||
updateState {
|
||||
val newMap = HashMap<String, ZigToolchain.Ref>()
|
||||
newMap.putAll(it.toolchains)
|
||||
var uuidStr = uuid.toString()
|
||||
while (newMap.containsKey(uuidStr)) {
|
||||
uuid = UUID.randomUUID()
|
||||
uuidStr = uuid.toString()
|
||||
}
|
||||
newMap[uuidStr] = ref
|
||||
it.copy(toolchains = newMap)
|
||||
}
|
||||
notifyChanged()
|
||||
return uuid
|
||||
}
|
||||
|
||||
override fun getToolchain(uuid: UUID): ZigToolchain? {
|
||||
return state.toolchains[uuid.toString()]?.resolve()
|
||||
}
|
||||
|
||||
override fun hasToolchain(uuid: UUID): Boolean {
|
||||
return state.toolchains.containsKey(uuid.toString())
|
||||
}
|
||||
|
||||
override fun removeToolchain(uuid: UUID) {
|
||||
val str = uuid.toString()
|
||||
updateState {
|
||||
it.copy(toolchains = it.toolchains.filter { it.key != str })
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T: ZigToolchain> withUniqueName(toolchain: T): T {
|
||||
val baseName = toolchain.name ?: ""
|
||||
var index = 0
|
||||
var currentName = baseName
|
||||
while (toolchains.any { (_, existing) -> existing.name == currentName }) {
|
||||
index++
|
||||
currentName = "$baseName ($index)"
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return toolchain.withName(currentName) as T
|
||||
}
|
||||
|
||||
private fun notifyChanged() {
|
||||
synchronized(changeListeners) {
|
||||
var i = 0
|
||||
while (i < changeListeners.size) {
|
||||
val v = changeListeners[i].get()
|
||||
if (v == null) {
|
||||
changeListeners.removeAt(i)
|
||||
continue
|
||||
}
|
||||
zigCoroutineScope.launch {
|
||||
v.toolchainListChanged()
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class State(
|
||||
data class MyState(
|
||||
@JvmField
|
||||
val toolchains: Map<String, ZigToolchain.Ref> = emptyMap(),
|
||||
val toolchains: ToolchainStorage = emptyMap(),
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
@ -149,19 +55,8 @@ class ZigToolchainListService: SerializablePersistentStateComponent<ZigToolchain
|
|||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface ToolchainListChangeListener {
|
||||
suspend fun toolchainListChanged()
|
||||
}
|
||||
inline val zigToolchainList: IZigToolchainListService get() = ZigToolchainListService.getInstance()
|
||||
|
||||
sealed interface IZigToolchainListService {
|
||||
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)
|
||||
fun <T: ZigToolchain> withUniqueName(toolchain: T): T
|
||||
}
|
||||
sealed interface IZigToolchainListService: ChangeTrackingStorage, AccessibleStorage<ZigToolchain>, IterableStorage<ZigToolchain>
|
||||
|
||||
private typealias ToolchainStorage = UUIDStorage<ZigToolchain.Ref>
|
||||
|
|
|
@ -45,7 +45,7 @@ import java.util.UUID
|
|||
class ZigToolchainService(val project: Project): SerializablePersistentStateComponent<ZigToolchainService.State>(State()), IZigToolchainService {
|
||||
override var toolchainUUID: UUID?
|
||||
get() = state.toolchain.ifBlank { null }?.let { UUID.fromString(it) }?.takeIf {
|
||||
if (ZigToolchainListService.getInstance().hasToolchain(it)) {
|
||||
if (it in zigToolchainList) {
|
||||
true
|
||||
} else {
|
||||
updateState {
|
||||
|
@ -64,7 +64,7 @@ class ZigToolchainService(val project: Project): SerializablePersistentStateComp
|
|||
}
|
||||
|
||||
override val toolchain: ZigToolchain?
|
||||
get() = toolchainUUID?.let { ZigToolchainListService.getInstance().getToolchain(it) }
|
||||
get() = toolchainUUID?.let { zigToolchainList[it] }
|
||||
|
||||
data class State(
|
||||
@JvmField
|
||||
|
|
|
@ -23,8 +23,10 @@
|
|||
package com.falsepattern.zigbrains.project.toolchain.base
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.tools.ZigCompilerTool
|
||||
import com.falsepattern.zigbrains.shared.NamedObject
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.util.xmlb.annotations.Attribute
|
||||
import com.intellij.util.xmlb.annotations.MapAnnotation
|
||||
import java.nio.file.Path
|
||||
|
@ -32,10 +34,15 @@ import java.nio.file.Path
|
|||
/**
|
||||
* These MUST be stateless and interchangeable! (e.g., immutable data class)
|
||||
*/
|
||||
interface ZigToolchain {
|
||||
interface ZigToolchain: NamedObject<ZigToolchain> {
|
||||
val zig: ZigCompilerTool get() = ZigCompilerTool(this)
|
||||
|
||||
val name: String?
|
||||
fun <T> getUserData(key: Key<T>): T?
|
||||
|
||||
/**
|
||||
* Returned type must be the same class
|
||||
*/
|
||||
fun <T> withUserData(key: Key<T>, value: T?): ZigToolchain
|
||||
|
||||
fun workingDirectory(project: Project? = null): Path?
|
||||
|
||||
|
@ -43,11 +50,6 @@ interface ZigToolchain {
|
|||
|
||||
fun pathToExecutable(toolName: String, project: Project? = null): Path
|
||||
|
||||
/**
|
||||
* Returned object must be the same class.
|
||||
*/
|
||||
fun withName(newName: String?): ZigToolchain
|
||||
|
||||
data class Ref(
|
||||
@JvmField
|
||||
@Attribute
|
||||
|
|
|
@ -23,10 +23,10 @@
|
|||
package com.falsepattern.zigbrains.project.toolchain.base
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
|
||||
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
||||
import com.intellij.openapi.ui.NamedConfigurable
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import com.intellij.ui.util.minimumWidth
|
||||
import java.util.UUID
|
||||
import javax.swing.JComponent
|
||||
|
||||
|
@ -36,7 +36,7 @@ abstract class ZigToolchainConfigurable<T: ZigToolchain>(
|
|||
): NamedConfigurable<UUID>() {
|
||||
var toolchain: T = tc
|
||||
set(value) {
|
||||
ZigToolchainListService.getInstance().setToolchain(uuid, value)
|
||||
zigToolchainList[uuid] = value
|
||||
field = value
|
||||
}
|
||||
private var myView: ZigToolchainPanel<T>? = null
|
||||
|
|
|
@ -22,8 +22,7 @@
|
|||
|
||||
package com.falsepattern.zigbrains.project.toolchain.base
|
||||
|
||||
import com.falsepattern.zigbrains.direnv.DirenvState
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
|
||||
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
|
@ -37,6 +36,7 @@ import kotlinx.coroutines.flow.filter
|
|||
import kotlinx.coroutines.flow.flatMapConcat
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import java.util.UUID
|
||||
import kotlin.collections.none
|
||||
|
||||
private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigToolchainProvider>("com.falsepattern.zigbrains.toolchainProvider")
|
||||
|
||||
|
@ -70,7 +70,7 @@ fun ZigToolchain.createNamedConfigurable(uuid: UUID): ZigToolchainConfigurable<*
|
|||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun suggestZigToolchains(project: Project? = null, data: UserDataHolder = emptyData): Flow<ZigToolchain> {
|
||||
val existing = ZigToolchainListService.getInstance().toolchains.map { (_, tc) -> tc }.toList()
|
||||
val existing = zigToolchainList.map { (_, tc) -> tc }
|
||||
return EXTENSION_POINT_NAME.extensionList.asFlow().flatMapConcat { ext ->
|
||||
val compatibleExisting = existing.filter { ext.isCompatible(it) }
|
||||
val suggestions = ext.suggestToolchains(project, data)
|
||||
|
|
|
@ -27,10 +27,12 @@ import com.falsepattern.zigbrains.ZigBrainsBundle
|
|||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
||||
import com.falsepattern.zigbrains.shared.coroutine.asContextElement
|
||||
import com.falsepattern.zigbrains.shared.coroutine.launchWithEDT
|
||||
import com.falsepattern.zigbrains.shared.coroutine.runInterruptibleEDT
|
||||
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||
import com.falsepattern.zigbrains.shared.withUniqueName
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
|
@ -78,9 +80,7 @@ object LocalSelector {
|
|||
errorMessageBox.text = ZigBrainsBundle.message("settings.toolchain.local-selector.state.invalid")
|
||||
dialog.setOkActionEnabled(false)
|
||||
} else {
|
||||
val existingToolchain = ZigToolchainListService
|
||||
.getInstance()
|
||||
.toolchains
|
||||
val existingToolchain = zigToolchainList
|
||||
.mapNotNull { it.second as? LocalZigToolchain }
|
||||
.firstOrNull { it.location == tc.location }
|
||||
if (existingToolchain != null) {
|
||||
|
@ -95,7 +95,7 @@ object LocalSelector {
|
|||
}
|
||||
}
|
||||
if (tc != null) {
|
||||
tc = ZigToolchainListService.getInstance().withUniqueName(tc)
|
||||
tc = zigToolchainList.withUniqueName(tc)
|
||||
}
|
||||
val prevNameDefault = name.emptyText.text.trim() == name.text.trim() || name.text.isBlank()
|
||||
name.emptyText.text = tc?.name ?: ""
|
||||
|
|
|
@ -28,13 +28,23 @@ import com.intellij.execution.ExecutionException
|
|||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.guessProjectDir
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.openapi.vfs.toNioPathOrNull
|
||||
import com.intellij.util.keyFMap.KeyFMap
|
||||
import java.nio.file.Path
|
||||
|
||||
@JvmRecord
|
||||
data class LocalZigToolchain(val location: Path, val std: Path? = null, override val name: String? = null): ZigToolchain {
|
||||
data class LocalZigToolchain(val location: Path, val std: Path? = null, override val name: String? = null, private val userData: KeyFMap = KeyFMap.EMPTY_MAP): ZigToolchain {
|
||||
override fun <T> getUserData(key: Key<T>): T? {
|
||||
return userData.get(key)
|
||||
}
|
||||
|
||||
override fun <T> withUserData(key: Key<T>, value: T?): LocalZigToolchain {
|
||||
return copy(userData = if (value == null) userData.minus(key) else userData.plus(key, value))
|
||||
}
|
||||
|
||||
override fun workingDirectory(project: Project?): Path? {
|
||||
return project?.guessProjectDir()?.toNioPathOrNull()
|
||||
}
|
||||
|
|
|
@ -25,6 +25,8 @@ package com.falsepattern.zigbrains.project.toolchain.ui
|
|||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
|
||||
import com.falsepattern.zigbrains.project.toolchain.downloader.Downloader
|
||||
import com.falsepattern.zigbrains.project.toolchain.downloader.LocalSelector
|
||||
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
||||
import com.falsepattern.zigbrains.shared.withUniqueName
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import java.awt.Component
|
||||
import java.util.UUID
|
||||
|
@ -32,8 +34,8 @@ import java.util.UUID
|
|||
internal object ZigToolchainComboBoxHandler {
|
||||
@RequiresBackgroundThread
|
||||
suspend fun onItemSelected(context: Component, elem: TCListElem.Pseudo): UUID? = when(elem) {
|
||||
is TCListElem.Toolchain.Suggested -> ZigToolchainListService.getInstance().withUniqueName(elem.toolchain)
|
||||
is TCListElem.Toolchain.Suggested -> zigToolchainList.withUniqueName(elem.toolchain)
|
||||
is TCListElem.Download -> Downloader.downloadToolchain(context)
|
||||
is TCListElem.FromDisk -> LocalSelector.browseFromDisk(context)
|
||||
}?.let { ZigToolchainListService.getInstance().registerNewToolchain(it) }
|
||||
}?.let { zigToolchainList.registerNew(it) }
|
||||
}
|
|
@ -23,14 +23,13 @@
|
|||
package com.falsepattern.zigbrains.project.toolchain.ui
|
||||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.direnv.DirenvService
|
||||
import com.falsepattern.zigbrains.direnv.DirenvState
|
||||
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
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.createNamedConfigurable
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.suggestZigToolchains
|
||||
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
||||
import com.falsepattern.zigbrains.shared.StorageChangeListener
|
||||
import com.falsepattern.zigbrains.shared.SubConfigurable
|
||||
import com.falsepattern.zigbrains.shared.coroutine.asContextElement
|
||||
import com.falsepattern.zigbrains.shared.coroutine.launchWithEDT
|
||||
|
@ -48,6 +47,7 @@ import com.intellij.ui.dsl.builder.AlignX
|
|||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.awt.event.ItemEvent
|
||||
|
@ -55,16 +55,17 @@ import java.util.UUID
|
|||
import javax.swing.JButton
|
||||
import kotlin.collections.addAll
|
||||
|
||||
class ZigToolchainEditor(private var project: Project?, private val sharedState: ZigProjectConfigurationProvider.IUserDataBridge): SubConfigurable<Project>, ToolchainListChangeListener, ZigProjectConfigurationProvider.UserDataListener {
|
||||
class ZigToolchainEditor(private var project: Project?, private val sharedState: ZigProjectConfigurationProvider.IUserDataBridge): SubConfigurable<Project>, ZigProjectConfigurationProvider.UserDataListener {
|
||||
private val toolchainBox: TCComboBox
|
||||
private var selectOnNextReload: UUID? = null
|
||||
private val model: TCModel
|
||||
private var editButton: JButton? = null
|
||||
private val changeListener: StorageChangeListener = { this@ZigToolchainEditor.toolchainListChanged() }
|
||||
init {
|
||||
model = TCModel(getModelList(project, sharedState))
|
||||
toolchainBox = TCComboBox(model)
|
||||
toolchainBox.addItemListener(::itemStateChanged)
|
||||
ZigToolchainListService.getInstance().addChangeListener(this)
|
||||
zigToolchainList.addChangeListener(changeListener)
|
||||
sharedState.addUserDataChangeListener(this)
|
||||
model.whenListChanged {
|
||||
if (toolchainBox.isPopupVisible) {
|
||||
|
@ -89,13 +90,14 @@ class ZigToolchainEditor(private var project: Project?, private val sharedState:
|
|||
return
|
||||
zigCoroutineScope.launch(toolchainBox.asContextElement()) {
|
||||
val uuid = runCatching { ZigToolchainComboBoxHandler.onItemSelected(toolchainBox, item) }.getOrNull()
|
||||
delay(100)
|
||||
withEDTContext(toolchainBox.asContextElement()) {
|
||||
applyUUIDNowOrOnReload(uuid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun toolchainListChanged() {
|
||||
private suspend fun toolchainListChanged() {
|
||||
withContext(Dispatchers.EDT + toolchainBox.asContextElement()) {
|
||||
val list = getModelList(project, sharedState)
|
||||
model.updateContents(list)
|
||||
|
@ -143,7 +145,7 @@ class ZigToolchainEditor(private var project: Project?, private val sharedState:
|
|||
button(ZigBrainsBundle.message("settings.toolchain.editor.toolchain.edit-button.name")) { e ->
|
||||
zigCoroutineScope.launchWithEDT(toolchainBox.asContextElement()) {
|
||||
var selectedUUID = toolchainBox.selectedToolchain ?: return@launchWithEDT
|
||||
val toolchain = ZigToolchainListService.getInstance().getToolchain(selectedUUID) ?: return@launchWithEDT
|
||||
val toolchain = zigToolchainList[selectedUUID] ?: return@launchWithEDT
|
||||
val config = toolchain.createNamedConfigurable(selectedUUID)
|
||||
val apply = ShowSettingsUtil.getInstance().editConfigurable(DialogWrapper.findInstance(toolchainBox)?.contentPane, config)
|
||||
if (apply) {
|
||||
|
@ -182,7 +184,7 @@ class ZigToolchainEditor(private var project: Project?, private val sharedState:
|
|||
}
|
||||
|
||||
override fun dispose() {
|
||||
ZigToolchainListService.getInstance().removeChangeListener(this)
|
||||
zigToolchainList.removeChangeListener(changeListener)
|
||||
}
|
||||
|
||||
override val newProjectBeforeInitSelector get() = true
|
||||
|
@ -199,7 +201,7 @@ class ZigToolchainEditor(private var project: Project?, private val sharedState:
|
|||
private fun getModelList(project: Project?, data: UserDataHolder): List<TCListElemIn> {
|
||||
val modelList = ArrayList<TCListElemIn>()
|
||||
modelList.add(TCListElem.None)
|
||||
modelList.addAll(ZigToolchainListService.getInstance().toolchains.map { it.asActual() }.sortedBy { it.toolchain.name })
|
||||
modelList.addAll(zigToolchainList.map { it.asActual() }.sortedBy { it.toolchain.name })
|
||||
modelList.add(Separator("", true))
|
||||
modelList.addAll(TCListElem.fetchGroup)
|
||||
modelList.add(Separator(ZigBrainsBundle.message("settings.toolchain.model.detected.separator"), true))
|
||||
|
|
|
@ -23,11 +23,12 @@
|
|||
package com.falsepattern.zigbrains.project.toolchain.ui
|
||||
|
||||
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.zigToolchainList
|
||||
import com.falsepattern.zigbrains.shared.StorageChangeListener
|
||||
import com.falsepattern.zigbrains.shared.coroutine.asContextElement
|
||||
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
|
@ -39,14 +40,17 @@ import com.intellij.openapi.project.DumbAwareAction
|
|||
import com.intellij.openapi.ui.MasterDetailsComponent
|
||||
import com.intellij.util.IconUtil
|
||||
import com.intellij.util.asSafely
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.UUID
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.tree.DefaultTreeModel
|
||||
|
||||
class ZigToolchainListEditor : MasterDetailsComponent(), ToolchainListChangeListener {
|
||||
class ZigToolchainListEditor : MasterDetailsComponent() {
|
||||
private var isTreeInitialized = false
|
||||
private var registered: Boolean = false
|
||||
private var selectOnNextReload: UUID? = null
|
||||
private val changeListener: StorageChangeListener = { this@ZigToolchainListEditor.toolchainListChanged() }
|
||||
|
||||
override fun createComponent(): JComponent {
|
||||
if (!isTreeInitialized) {
|
||||
|
@ -54,7 +58,7 @@ class ZigToolchainListEditor : MasterDetailsComponent(), ToolchainListChangeList
|
|||
isTreeInitialized = true
|
||||
}
|
||||
if (!registered) {
|
||||
ZigToolchainListService.getInstance().addChangeListener(this)
|
||||
zigToolchainList.addChangeListener(changeListener)
|
||||
registered = true
|
||||
}
|
||||
return super.createComponent()
|
||||
|
@ -81,7 +85,7 @@ class ZigToolchainListEditor : MasterDetailsComponent(), ToolchainListChangeList
|
|||
|
||||
override fun onItemDeleted(item: Any?) {
|
||||
if (item is UUID) {
|
||||
ZigToolchainListService.getInstance().removeToolchain(item)
|
||||
zigToolchainList.remove(item)
|
||||
}
|
||||
super.onItemDeleted(item)
|
||||
}
|
||||
|
@ -93,7 +97,7 @@ class ZigToolchainListEditor : MasterDetailsComponent(), ToolchainListChangeList
|
|||
val uuid = ZigToolchainComboBoxHandler.onItemSelected(myWholePanel, elem)
|
||||
if (uuid != null) {
|
||||
withEDTContext(myWholePanel.asContextElement()) {
|
||||
selectNodeInTree(uuid)
|
||||
applyUUIDNowOrOnReload(uuid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -108,6 +112,13 @@ class ZigToolchainListEditor : MasterDetailsComponent(), ToolchainListChangeList
|
|||
|
||||
override fun getDisplayName() = ZigBrainsBundle.message("settings.toolchain.list.title")
|
||||
|
||||
override fun disposeUIResources() {
|
||||
super.disposeUIResources()
|
||||
if (registered) {
|
||||
zigToolchainList.removeChangeListener(changeListener)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addToolchain(uuid: UUID, toolchain: ZigToolchain) {
|
||||
val node = MyNode(toolchain.createNamedConfigurable(uuid))
|
||||
addNode(node, myRoot)
|
||||
|
@ -116,23 +127,37 @@ class ZigToolchainListEditor : MasterDetailsComponent(), ToolchainListChangeList
|
|||
private fun reloadTree() {
|
||||
val currentSelection = selectedObject?.asSafely<UUID>()
|
||||
myRoot.removeAllChildren()
|
||||
ZigToolchainListService.getInstance().toolchains.forEach { (uuid, toolchain) ->
|
||||
val onReload = selectOnNextReload
|
||||
selectOnNextReload = null
|
||||
var hasOnReload = false
|
||||
zigToolchainList.forEach { (uuid, toolchain) ->
|
||||
addToolchain(uuid, toolchain)
|
||||
if (uuid == onReload) {
|
||||
hasOnReload = true
|
||||
}
|
||||
}
|
||||
(myTree.model as DefaultTreeModel).reload()
|
||||
if (hasOnReload) {
|
||||
selectNodeInTree(onReload)
|
||||
return
|
||||
}
|
||||
currentSelection?.let {
|
||||
selectNodeInTree(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun disposeUIResources() {
|
||||
super.disposeUIResources()
|
||||
if (registered) {
|
||||
ZigToolchainListService.getInstance().removeChangeListener(this)
|
||||
@RequiresEdt
|
||||
private fun applyUUIDNowOrOnReload(uuid: UUID?) {
|
||||
selectNodeInTree(uuid)
|
||||
val currentSelection = selectedObject?.asSafely<UUID>()
|
||||
if (uuid != null && uuid != currentSelection) {
|
||||
selectOnNextReload = uuid
|
||||
} else {
|
||||
selectOnNextReload = null
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun toolchainListChanged() {
|
||||
private suspend fun toolchainListChanged() {
|
||||
withEDTContext(myWholePanel.asContextElement()) {
|
||||
reloadTree()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
interface NamedObject<T: NamedObject<T>> {
|
||||
val name: String?
|
||||
|
||||
/**
|
||||
* Returned object must be the exact same class as the called one.
|
||||
*/
|
||||
fun withName(newName: String?): T
|
||||
}
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* 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.intellij.openapi.components.SerializablePersistentStateComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.UUID
|
||||
import kotlin.collections.any
|
||||
|
||||
typealias UUIDStorage<T> = Map<String, T>
|
||||
|
||||
abstract class UUIDMapSerializable<T, S: Any>(init: S): SerializablePersistentStateComponent<S>(init), ChangeTrackingStorage {
|
||||
private val changeListeners = ArrayList<WeakReference<StorageChangeListener>>()
|
||||
|
||||
protected abstract fun getStorage(state: S): UUIDStorage<T>
|
||||
|
||||
protected abstract fun updateStorage(state: S, storage: UUIDStorage<T>): S
|
||||
|
||||
override fun addChangeListener(listener: StorageChangeListener) {
|
||||
synchronized(changeListeners) {
|
||||
changeListeners.add(WeakReference(listener))
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeChangeListener(listener: StorageChangeListener) {
|
||||
synchronized(changeListeners) {
|
||||
changeListeners.removeIf {
|
||||
val v = it.get()
|
||||
v == null || v === listener
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun registerNewUUID(value: T): UUID {
|
||||
var uuid = UUID.randomUUID()
|
||||
updateState {
|
||||
val newMap = HashMap<String, T>()
|
||||
newMap.putAll(getStorage(it))
|
||||
var uuidStr = uuid.toString()
|
||||
while (newMap.containsKey(uuidStr)) {
|
||||
uuid = UUID.randomUUID()
|
||||
uuidStr = uuid.toString()
|
||||
}
|
||||
newMap[uuidStr] = value
|
||||
updateStorage(it, newMap)
|
||||
}
|
||||
notifyChanged()
|
||||
return uuid
|
||||
}
|
||||
|
||||
protected fun setStateUUID(uuid: UUID, value: T) {
|
||||
val str = uuid.toString()
|
||||
updateState {
|
||||
val newMap = HashMap<String, T>()
|
||||
newMap.putAll(getStorage(it))
|
||||
newMap[str] = value
|
||||
updateStorage(it, newMap)
|
||||
}
|
||||
notifyChanged()
|
||||
}
|
||||
|
||||
protected fun getStateUUID(uuid: UUID): T? {
|
||||
return getStorage(state)[uuid.toString()]
|
||||
}
|
||||
|
||||
protected fun hasStateUUID(uuid: UUID): Boolean {
|
||||
return getStorage(state).containsKey(uuid.toString())
|
||||
}
|
||||
|
||||
protected fun removeStateUUID(uuid: UUID) {
|
||||
val str = uuid.toString()
|
||||
updateState {
|
||||
updateStorage(state, getStorage(state).filter { it.key != str })
|
||||
}
|
||||
notifyChanged()
|
||||
}
|
||||
|
||||
private fun notifyChanged() {
|
||||
synchronized(changeListeners) {
|
||||
var i = 0
|
||||
while (i < changeListeners.size) {
|
||||
val v = changeListeners[i].get()
|
||||
if (v == null) {
|
||||
changeListeners.removeAt(i)
|
||||
continue
|
||||
}
|
||||
zigCoroutineScope.launch {
|
||||
v()
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Converting<R, T, S: Any>(init: S):
|
||||
UUIDMapSerializable<T, S>(init),
|
||||
AccessibleStorage<R>,
|
||||
IterableStorage<R>
|
||||
{
|
||||
protected abstract fun serialize(value: R): T
|
||||
protected abstract fun deserialize(value: T): R?
|
||||
override fun registerNew(value: R): UUID {
|
||||
val ser = serialize(value)
|
||||
return registerNewUUID(ser)
|
||||
}
|
||||
override operator fun set(uuid: UUID, value: R) {
|
||||
val ser = serialize(value)
|
||||
setStateUUID(uuid, ser)
|
||||
}
|
||||
override operator fun get(uuid: UUID): R? {
|
||||
return getStateUUID(uuid)?.let { deserialize(it) }
|
||||
}
|
||||
override operator fun contains(uuid: UUID): Boolean {
|
||||
return hasStateUUID(uuid)
|
||||
}
|
||||
override fun remove(uuid: UUID) {
|
||||
removeStateUUID(uuid)
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<Pair<UUID, R>> {
|
||||
return getStorage(state)
|
||||
.asSequence()
|
||||
.mapNotNull {
|
||||
val uuid = UUID.fromString(it.key) ?: return@mapNotNull null
|
||||
val tc = deserialize(it.value) ?: return@mapNotNull null
|
||||
uuid to tc
|
||||
}.iterator()
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Direct<T, S: Any>(init: S): Converting<T, T, S>(init) {
|
||||
override fun serialize(value: T): T {
|
||||
return value
|
||||
}
|
||||
|
||||
override fun deserialize(value: T): T? {
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias StorageChangeListener = suspend CoroutineScope.() -> Unit
|
||||
|
||||
interface ChangeTrackingStorage {
|
||||
fun addChangeListener(listener: StorageChangeListener)
|
||||
fun removeChangeListener(listener: StorageChangeListener)
|
||||
}
|
||||
|
||||
interface AccessibleStorage<R> {
|
||||
fun registerNew(value: R): UUID
|
||||
operator fun set(uuid: UUID, value: R)
|
||||
operator fun get(uuid: UUID): R?
|
||||
operator fun contains(uuid: UUID): Boolean
|
||||
fun remove(uuid: UUID)
|
||||
}
|
||||
|
||||
interface IterableStorage<R>: Iterable<Pair<UUID, R>>
|
||||
|
||||
fun <R: NamedObject<R>, T: R> IterableStorage<R>.withUniqueName(value: T): T {
|
||||
val baseName = value.name ?: ""
|
||||
var index = 0
|
||||
var currentName = baseName
|
||||
val names = this.map { (_, existing) -> existing.name }
|
||||
while (names.any { it == currentName }) {
|
||||
index++
|
||||
currentName = "$baseName ($index)"
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return value.withName(currentName) as T
|
||||
}
|
Loading…
Add table
Reference in a new issue