initial LSP work
This commit is contained in:
parent
137977f691
commit
dcede7eb43
49 changed files with 1611 additions and 1342 deletions
|
@ -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 {
|
||||||
|
|
|
@ -92,6 +92,6 @@ abstract class DirenvEditor<T>(private val sharedState: ZigProjectConfigurationP
|
||||||
}
|
}
|
||||||
|
|
||||||
override val index: Int
|
override val index: Int
|
||||||
get() = 1
|
get() = 100
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -35,7 +35,6 @@ import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.project.guessProjectDir
|
import com.intellij.openapi.project.guessProjectDir
|
||||||
import com.intellij.openapi.util.NlsActions.ActionText
|
import com.intellij.openapi.util.NlsActions.ActionText
|
||||||
import com.intellij.openapi.vfs.toNioPathOrNull
|
import com.intellij.openapi.vfs.toNioPathOrNull
|
||||||
import com.intellij.platform.util.progress.reportRawProgress
|
|
||||||
import org.jdom.Element
|
import org.jdom.Element
|
||||||
import org.jetbrains.annotations.Nls
|
import org.jetbrains.annotations.Nls
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,6 @@ package com.falsepattern.zigbrains.project.settings
|
||||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
import com.falsepattern.zigbrains.shared.SubConfigurable
|
import com.falsepattern.zigbrains.shared.SubConfigurable
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.util.NlsContexts
|
|
||||||
import com.intellij.openapi.util.UserDataHolderBase
|
|
||||||
|
|
||||||
class ZigConfigurable(override val context: Project) : SubConfigurable.Adapter<Project>() {
|
class ZigConfigurable(override val context: Project) : SubConfigurable.Adapter<Project>() {
|
||||||
override fun instantiate(): List<SubConfigurable<Project>> {
|
override fun instantiate(): List<SubConfigurable<Project>> {
|
||||||
|
|
|
@ -42,8 +42,8 @@ import java.util.UUID
|
||||||
name = "ZigToolchain",
|
name = "ZigToolchain",
|
||||||
storages = [Storage("zigbrains.xml")]
|
storages = [Storage("zigbrains.xml")]
|
||||||
)
|
)
|
||||||
class ZigToolchainService(val project: Project): SerializablePersistentStateComponent<ZigToolchainService.State>(State()), IZigToolchainService {
|
class ZigToolchainService(private val project: Project): SerializablePersistentStateComponent<ZigToolchainService.State>(State()) {
|
||||||
override var toolchainUUID: UUID?
|
var toolchainUUID: UUID?
|
||||||
get() = state.toolchain.ifBlank { null }?.let { UUID.fromString(it) }?.takeIf {
|
get() = state.toolchain.ifBlank { null }?.let { UUID.fromString(it) }?.takeIf {
|
||||||
if (it in zigToolchainList) {
|
if (it in zigToolchainList) {
|
||||||
true
|
true
|
||||||
|
@ -63,7 +63,7 @@ class ZigToolchainService(val project: Project): SerializablePersistentStateComp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val toolchain: ZigToolchain?
|
val toolchain: ZigToolchain?
|
||||||
get() = toolchainUUID?.let { zigToolchainList[it] }
|
get() = toolchainUUID?.let { zigToolchainList[it] }
|
||||||
|
|
||||||
data class State(
|
data class State(
|
||||||
|
@ -74,11 +74,6 @@ class ZigToolchainService(val project: Project): SerializablePersistentStateComp
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getInstance(project: Project): IZigToolchainService = project.service<ZigToolchainService>()
|
fun getInstance(project: Project): ZigToolchainService = project.service<ZigToolchainService>()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface IZigToolchainService {
|
|
||||||
var toolchainUUID: UUID?
|
|
||||||
val toolchain: ZigToolchain?
|
|
||||||
}
|
}
|
|
@ -22,6 +22,7 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.project.toolchain.base
|
package com.falsepattern.zigbrains.project.toolchain.base
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.project.toolchain.ui.ImmutableElementPanel
|
||||||
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
||||||
import com.intellij.openapi.ui.NamedConfigurable
|
import com.intellij.openapi.ui.NamedConfigurable
|
||||||
import com.intellij.openapi.util.NlsContexts
|
import com.intellij.openapi.util.NlsContexts
|
||||||
|
@ -38,14 +39,14 @@ abstract class ZigToolchainConfigurable<T: ZigToolchain>(
|
||||||
zigToolchainList[uuid] = value
|
zigToolchainList[uuid] = value
|
||||||
field = value
|
field = value
|
||||||
}
|
}
|
||||||
private var myViews: List<ZigToolchainPanel<T>> = emptyList()
|
private var myViews: List<ImmutableElementPanel<T>> = emptyList()
|
||||||
|
|
||||||
abstract fun createPanel(): ZigToolchainPanel<T>
|
abstract fun createPanel(): ImmutableElementPanel<T>
|
||||||
|
|
||||||
override fun createOptionsPanel(): JComponent? {
|
override fun createOptionsPanel(): JComponent? {
|
||||||
var views = myViews
|
var views = myViews
|
||||||
if (views.isEmpty()) {
|
if (views.isEmpty()) {
|
||||||
views = ArrayList<ZigToolchainPanel<T>>()
|
views = ArrayList<ImmutableElementPanel<T>>()
|
||||||
views.add(createPanel())
|
views.add(createPanel())
|
||||||
views.addAll(createZigToolchainExtensionPanels())
|
views.addAll(createZigToolchainExtensionPanels())
|
||||||
views.forEach { it.reset(toolchain) }
|
views.forEach { it.reset(toolchain) }
|
||||||
|
|
|
@ -22,16 +22,17 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.project.toolchain.base
|
package com.falsepattern.zigbrains.project.toolchain.base
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.project.toolchain.ui.ImmutableElementPanel
|
||||||
import com.intellij.openapi.extensions.ExtensionPointName
|
import com.intellij.openapi.extensions.ExtensionPointName
|
||||||
|
|
||||||
private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigToolchainExtensionsProvider>("com.falsepattern.zigbrains.toolchainExtensionsProvider")
|
private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigToolchainExtensionsProvider>("com.falsepattern.zigbrains.toolchainExtensionsProvider")
|
||||||
|
|
||||||
internal interface ZigToolchainExtensionsProvider {
|
internal interface ZigToolchainExtensionsProvider {
|
||||||
fun <T : ZigToolchain> createExtensionPanel(): ZigToolchainPanel<T>?
|
fun <T : ZigToolchain> createExtensionPanel(): ImmutableElementPanel<T>?
|
||||||
val index: Int
|
val index: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T: ZigToolchain> createZigToolchainExtensionPanels(): List<ZigToolchainPanel<T>> {
|
fun <T: ZigToolchain> createZigToolchainExtensionPanels(): List<ImmutableElementPanel<T>> {
|
||||||
return EXTENSION_POINT_NAME.extensionList.sortedBy{ it.index }.mapNotNull {
|
return EXTENSION_POINT_NAME.extensionList.sortedBy{ it.index }.mapNotNull {
|
||||||
it.createExtensionPanel()
|
it.createExtensionPanel()
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,6 @@ import com.intellij.util.asSafely
|
||||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||||
import java.awt.Component
|
import java.awt.Component
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
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
|
||||||
|
|
|
@ -50,10 +50,8 @@ import kotlinx.serialization.json.JsonPrimitive
|
||||||
import kotlinx.serialization.json.decodeFromJsonElement
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
import kotlinx.serialization.json.decodeFromStream
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.lang.IllegalStateException
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.util.*
|
|
||||||
import kotlin.io.path.ExperimentalPathApi
|
import kotlin.io.path.ExperimentalPathApi
|
||||||
import kotlin.io.path.deleteRecursively
|
import kotlin.io.path.deleteRecursively
|
||||||
import kotlin.io.path.isDirectory
|
import kotlin.io.path.isDirectory
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
package com.falsepattern.zigbrains.project.toolchain.local
|
package com.falsepattern.zigbrains.project.toolchain.local
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainPanelBase
|
import com.falsepattern.zigbrains.project.toolchain.ui.ImmutableNamedElementPanelBase
|
||||||
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.application.ModalityState
|
import com.intellij.openapi.application.ModalityState
|
||||||
|
@ -44,7 +44,7 @@ import kotlinx.coroutines.launch
|
||||||
import javax.swing.event.DocumentEvent
|
import javax.swing.event.DocumentEvent
|
||||||
import kotlin.io.path.pathString
|
import kotlin.io.path.pathString
|
||||||
|
|
||||||
class LocalZigToolchainPanel() : ZigToolchainPanelBase<LocalZigToolchain>() {
|
class LocalZigToolchainPanel() : ImmutableNamedElementPanelBase<LocalZigToolchain>() {
|
||||||
private val pathToToolchain = textFieldWithBrowseButton(
|
private val pathToToolchain = textFieldWithBrowseButton(
|
||||||
null,
|
null,
|
||||||
FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.zig-toolchain"))
|
FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.zig-toolchain"))
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
package com.falsepattern.zigbrains.project.toolchain.local
|
package com.falsepattern.zigbrains.project.toolchain.local
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.direnv.DirenvService
|
import com.falsepattern.zigbrains.direnv.DirenvService
|
||||||
import com.falsepattern.zigbrains.direnv.DirenvState
|
|
||||||
import com.falsepattern.zigbrains.direnv.Env
|
import com.falsepattern.zigbrains.direnv.Env
|
||||||
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
|
||||||
|
|
|
@ -20,17 +20,17 @@
|
||||||
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
|
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.project.toolchain.base
|
package com.falsepattern.zigbrains.project.toolchain.ui
|
||||||
|
|
||||||
import com.intellij.openapi.Disposable
|
import com.intellij.openapi.Disposable
|
||||||
import com.intellij.ui.dsl.builder.Panel
|
import com.intellij.ui.dsl.builder.Panel
|
||||||
|
|
||||||
interface ZigToolchainPanel<T: ZigToolchain>: Disposable {
|
interface ImmutableElementPanel<T>: Disposable {
|
||||||
fun attach(p: Panel)
|
fun attach(p: Panel)
|
||||||
fun isModified(toolchain: T): Boolean
|
fun isModified(elem: T): Boolean
|
||||||
/**
|
/**
|
||||||
* Returned object must be the exact same class as the provided one.
|
* Returned object must be the exact same class as the provided one.
|
||||||
*/
|
*/
|
||||||
fun apply(toolchain: T): T?
|
fun apply(elem: T): T?
|
||||||
fun reset(toolchain: T)
|
fun reset(elem: T)
|
||||||
}
|
}
|
|
@ -20,17 +20,15 @@
|
||||||
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
|
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.project.toolchain.base
|
package com.falsepattern.zigbrains.project.toolchain.ui
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
import com.intellij.openapi.Disposable
|
import com.falsepattern.zigbrains.shared.NamedObject
|
||||||
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 ZigToolchainPanelBase<T: ZigToolchain>: ZigToolchainPanel<T> {
|
abstract class ImmutableNamedElementPanelBase<T>: ImmutableElementPanel<T> {
|
||||||
private val nameField = JBTextField(25)
|
private val nameField = JBTextField(25)
|
||||||
|
|
||||||
protected var nameFieldValue: String?
|
protected var nameFieldValue: String?
|
|
@ -22,10 +22,11 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.project.toolchain.ui
|
package com.falsepattern.zigbrains.project.toolchain.ui
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService
|
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||||
import com.falsepattern.zigbrains.project.toolchain.downloader.Downloader
|
import com.falsepattern.zigbrains.project.toolchain.downloader.Downloader
|
||||||
import com.falsepattern.zigbrains.project.toolchain.downloader.LocalSelector
|
import com.falsepattern.zigbrains.project.toolchain.downloader.LocalSelector
|
||||||
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ListElem
|
||||||
import com.falsepattern.zigbrains.shared.withUniqueName
|
import com.falsepattern.zigbrains.shared.withUniqueName
|
||||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||||
import java.awt.Component
|
import java.awt.Component
|
||||||
|
@ -33,9 +34,9 @@ import java.util.UUID
|
||||||
|
|
||||||
internal object ZigToolchainComboBoxHandler {
|
internal object ZigToolchainComboBoxHandler {
|
||||||
@RequiresBackgroundThread
|
@RequiresBackgroundThread
|
||||||
suspend fun onItemSelected(context: Component, elem: TCListElem.Pseudo): UUID? = when(elem) {
|
suspend fun onItemSelected(context: Component, elem: ListElem.Pseudo<ZigToolchain>): UUID? = when(elem) {
|
||||||
is TCListElem.Toolchain.Suggested -> zigToolchainList.withUniqueName(elem.toolchain)
|
is ListElem.One.Suggested -> zigToolchainList.withUniqueName(elem.instance)
|
||||||
is TCListElem.Download -> Downloader.downloadToolchain(context)
|
is ListElem.Download -> Downloader.downloadToolchain(context)
|
||||||
is TCListElem.FromDisk -> LocalSelector.browseFromDisk(context)
|
is ListElem.FromDisk -> LocalSelector.browseFromDisk(context)
|
||||||
}?.let { zigToolchainList.registerNew(it) }
|
}?.let { zigToolchainList.registerNew(it) }
|
||||||
}
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* This file is part of ZigBrains.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2023-2025 FalsePattern
|
||||||
|
* All Rights Reserved
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included
|
||||||
|
* in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* ZigBrains is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, only version 3 of the License.
|
||||||
|
*
|
||||||
|
* ZigBrains is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.falsepattern.zigbrains.project.toolchain.ui
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
|
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.ui.ListElem
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ListElemIn
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.Separator
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.UUIDComboBoxDriver
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ZBComboBox
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ZBContext
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ZBModel
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.asActual
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.asPending
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.ui.NamedConfigurable
|
||||||
|
import com.intellij.openapi.util.UserDataHolder
|
||||||
|
import java.awt.Component
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
sealed interface ZigToolchainDriver: UUIDComboBoxDriver<ZigToolchain> {
|
||||||
|
override val theMap get() = zigToolchainList
|
||||||
|
|
||||||
|
override fun createContext(model: ZBModel<ZigToolchain>): ZBContext<ZigToolchain> {
|
||||||
|
return TCContext(null, model)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createComboBox(model: ZBModel<ZigToolchain>): ZBComboBox<ZigToolchain> {
|
||||||
|
return TCComboBox(model)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun resolvePseudo(
|
||||||
|
context: Component,
|
||||||
|
elem: ListElem.Pseudo<ZigToolchain>
|
||||||
|
): UUID? {
|
||||||
|
return ZigToolchainComboBoxHandler.onItemSelected(context, elem)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createNamedConfigurable(
|
||||||
|
uuid: UUID,
|
||||||
|
elem: ZigToolchain
|
||||||
|
): NamedConfigurable<UUID> {
|
||||||
|
return elem.createNamedConfigurable(uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
object ForList: ZigToolchainDriver {
|
||||||
|
override fun constructModelList(): List<ListElemIn<ZigToolchain>> {
|
||||||
|
val modelList = ArrayList<ListElemIn<ZigToolchain>>()
|
||||||
|
modelList.addAll(ListElem.fetchGroup())
|
||||||
|
modelList.add(Separator(ZigBrainsBundle.message("settings.toolchain.model.detected.separator"), true))
|
||||||
|
modelList.add(suggestZigToolchains().asPending())
|
||||||
|
return modelList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ForSelector(val project: Project?, val data: UserDataHolder): ZigToolchainDriver {
|
||||||
|
override fun constructModelList(): List<ListElemIn<ZigToolchain>> {
|
||||||
|
val modelList = ArrayList<ListElemIn<ZigToolchain>>()
|
||||||
|
modelList.add(ListElem.None())
|
||||||
|
modelList.addAll(zigToolchainList.map { it.asActual() }.sortedBy { it.instance.name })
|
||||||
|
modelList.add(Separator("", true))
|
||||||
|
modelList.addAll(ListElem.fetchGroup())
|
||||||
|
modelList.add(Separator(ZigBrainsBundle.message("settings.toolchain.model.detected.separator"), true))
|
||||||
|
modelList.add(suggestZigToolchains(project, data).asPending())
|
||||||
|
return modelList
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,113 +24,29 @@ 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.settings.ZigProjectConfigurationProvider
|
||||||
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.ZigToolchain
|
||||||
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.SubConfigurable
|
||||||
import com.falsepattern.zigbrains.shared.coroutine.asContextElement
|
import com.falsepattern.zigbrains.shared.ui.UUIDMapSelector
|
||||||
import com.falsepattern.zigbrains.shared.coroutine.launchWithEDT
|
|
||||||
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
|
||||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
import com.intellij.openapi.application.EDT
|
|
||||||
import com.intellij.openapi.observable.util.whenListChanged
|
|
||||||
import com.intellij.openapi.options.ShowSettingsUtil
|
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.project.ProjectManager
|
import com.intellij.openapi.project.ProjectManager
|
||||||
import com.intellij.openapi.ui.DialogWrapper
|
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.openapi.util.UserDataHolder
|
|
||||||
import com.intellij.ui.dsl.builder.AlignX
|
|
||||||
import com.intellij.ui.dsl.builder.Panel
|
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.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import java.awt.event.ItemEvent
|
|
||||||
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>, ZigProjectConfigurationProvider.UserDataListener {
|
class ZigToolchainEditor(private var project: Project?,
|
||||||
private val toolchainBox: TCComboBox
|
private val sharedState: ZigProjectConfigurationProvider.IUserDataBridge):
|
||||||
private var selectOnNextReload: UUID? = null
|
UUIDMapSelector<ZigToolchain>(ZigToolchainDriver.ForSelector(project, sharedState)),
|
||||||
private val model: TCModel
|
SubConfigurable<Project>,
|
||||||
private var editButton: JButton? = null
|
ZigProjectConfigurationProvider.UserDataListener
|
||||||
private val changeListener: StorageChangeListener = { this@ZigToolchainEditor.toolchainListChanged() }
|
{
|
||||||
init {
|
init {
|
||||||
model = TCModel(getModelList(project, sharedState))
|
|
||||||
toolchainBox = TCComboBox(model)
|
|
||||||
toolchainBox.addItemListener(::itemStateChanged)
|
|
||||||
zigToolchainList.addChangeListener(changeListener)
|
|
||||||
sharedState.addUserDataChangeListener(this)
|
sharedState.addUserDataChangeListener(this)
|
||||||
model.whenListChanged {
|
|
||||||
if (toolchainBox.isPopupVisible) {
|
|
||||||
toolchainBox.isPopupVisible = false
|
|
||||||
toolchainBox.isPopupVisible = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun refreshButtonState(item: Any?) {
|
|
||||||
editButton?.isEnabled = item is TCListElem.Toolchain.Actual
|
|
||||||
editButton?.repaint()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun itemStateChanged(event: ItemEvent) {
|
|
||||||
if (event.stateChange != ItemEvent.SELECTED) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val item = event.item
|
|
||||||
refreshButtonState(item)
|
|
||||||
if (item !is TCListElem.Pseudo)
|
|
||||||
return
|
|
||||||
zigCoroutineScope.launch(toolchainBox.asContextElement()) {
|
|
||||||
val uuid = runCatching { ZigToolchainComboBoxHandler.onItemSelected(toolchainBox, item) }.getOrNull()
|
|
||||||
delay(100)
|
|
||||||
withEDTContext(toolchainBox.asContextElement()) {
|
|
||||||
applyUUIDNowOrOnReload(uuid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun toolchainListChanged() {
|
|
||||||
withContext(Dispatchers.EDT + toolchainBox.asContextElement()) {
|
|
||||||
val list = getModelList(project, sharedState)
|
|
||||||
model.updateContents(list)
|
|
||||||
val onReload = selectOnNextReload
|
|
||||||
selectOnNextReload = null
|
|
||||||
if (onReload != null) {
|
|
||||||
val element = list.firstOrNull { when(it) {
|
|
||||||
is TCListElem.Toolchain.Actual -> it.uuid == onReload
|
|
||||||
else -> false
|
|
||||||
} }
|
|
||||||
model.selectedItem = element
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUserDataChanged(key: Key<*>) {
|
override fun onUserDataChanged(key: Key<*>) {
|
||||||
zigCoroutineScope.launch { toolchainListChanged() }
|
zigCoroutineScope.launch { listChanged() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -141,50 +57,26 @@ class ZigToolchainEditor(private var project: Project?, private val sharedState:
|
||||||
else
|
else
|
||||||
"settings.toolchain.editor.toolchain.label")
|
"settings.toolchain.editor.toolchain.label")
|
||||||
) {
|
) {
|
||||||
cell(toolchainBox).resizableColumn().align(AlignX.FILL)
|
attachComboBoxRow(this)
|
||||||
button(ZigBrainsBundle.message("settings.toolchain.editor.toolchain.edit-button.name")) { e ->
|
|
||||||
zigCoroutineScope.launchWithEDT(toolchainBox.asContextElement()) {
|
|
||||||
var selectedUUID = toolchainBox.selectedToolchain ?: 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) {
|
|
||||||
applyUUIDNowOrOnReload(selectedUUID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}.component.let {
|
|
||||||
editButton = it
|
|
||||||
refreshButtonState(toolchainBox.selectedItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresEdt
|
|
||||||
private fun applyUUIDNowOrOnReload(uuid: UUID?) {
|
|
||||||
toolchainBox.selectedToolchain = uuid
|
|
||||||
if (uuid != null && toolchainBox.selectedToolchain == null) {
|
|
||||||
selectOnNextReload = uuid
|
|
||||||
} else {
|
|
||||||
selectOnNextReload = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isModified(context: Project): Boolean {
|
override fun isModified(context: Project): Boolean {
|
||||||
return ZigToolchainService.getInstance(context).toolchainUUID != toolchainBox.selectedToolchain
|
return ZigToolchainService.getInstance(context).toolchainUUID != selectedUUID
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun apply(context: Project) {
|
override fun apply(context: Project) {
|
||||||
ZigToolchainService.getInstance(context).toolchainUUID = toolchainBox.selectedToolchain
|
ZigToolchainService.getInstance(context).toolchainUUID = selectedUUID
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun reset(context: Project?) {
|
override fun reset(context: Project?) {
|
||||||
val project = context ?: ProjectManager.getInstance().defaultProject
|
val project = context ?: ProjectManager.getInstance().defaultProject
|
||||||
toolchainBox.selectedToolchain = ZigToolchainService.getInstance(project).toolchainUUID
|
selectedUUID = ZigToolchainService.getInstance(project).toolchainUUID
|
||||||
refreshButtonState(toolchainBox.selectedItem)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
zigToolchainList.removeChangeListener(changeListener)
|
super.dispose()
|
||||||
|
sharedState.removeUserDataChangeListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val newProjectBeforeInitSelector get() = true
|
override val newProjectBeforeInitSelector get() = true
|
||||||
|
@ -195,16 +87,4 @@ class ZigToolchainEditor(private var project: Project?, private val sharedState:
|
||||||
|
|
||||||
override val index: Int get() = 0
|
override val index: Int get() = 0
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun getModelList(project: Project?, data: UserDataHolder): List<TCListElemIn> {
|
|
||||||
val modelList = ArrayList<TCListElemIn>()
|
|
||||||
modelList.add(TCListElem.None)
|
|
||||||
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))
|
|
||||||
modelList.add(suggestZigToolchains(project, data).asPending())
|
|
||||||
return modelList
|
|
||||||
}
|
}
|
|
@ -23,143 +23,9 @@
|
||||||
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.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.shared.ui.UUIDMapEditor
|
||||||
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
|
|
||||||
import com.intellij.openapi.actionSystem.AnAction
|
|
||||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
|
||||||
import com.intellij.openapi.actionSystem.Presentation
|
|
||||||
import com.intellij.openapi.observable.util.whenListChanged
|
|
||||||
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() {
|
|
||||||
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) {
|
|
||||||
initTree()
|
|
||||||
isTreeInitialized = true
|
|
||||||
}
|
|
||||||
if (!registered) {
|
|
||||||
zigToolchainList.addChangeListener(changeListener)
|
|
||||||
registered = true
|
|
||||||
}
|
|
||||||
return super.createComponent()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createActions(fromPopup: Boolean): List<AnAction> {
|
|
||||||
val add = object : DumbAwareAction({ ZigBrainsBundle.message("settings.toolchain.list.add-action.name") }, Presentation.NULL_STRING, IconUtil.addIcon) {
|
|
||||||
override fun actionPerformed(e: AnActionEvent) {
|
|
||||||
val modelList = ArrayList<TCListElemIn>()
|
|
||||||
modelList.addAll(TCListElem.fetchGroup)
|
|
||||||
modelList.add(Separator(ZigBrainsBundle.message("settings.toolchain.model.detected.separator"), true))
|
|
||||||
modelList.add(suggestZigToolchains().asPending())
|
|
||||||
val model = TCModel(modelList)
|
|
||||||
val context = TCContext(null, model)
|
|
||||||
val popup = TCComboBoxPopup(context, null, ::onItemSelected)
|
|
||||||
model.whenListChanged {
|
|
||||||
popup.syncWithModelChange()
|
|
||||||
}
|
|
||||||
popup.showInBestPositionFor(e.dataContext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return listOf(add, MyDeleteAction())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemDeleted(item: Any?) {
|
|
||||||
if (item is UUID) {
|
|
||||||
zigToolchainList.remove(item)
|
|
||||||
}
|
|
||||||
super.onItemDeleted(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onItemSelected(elem: TCListElem) {
|
|
||||||
if (elem !is TCListElem.Pseudo)
|
|
||||||
return
|
|
||||||
zigCoroutineScope.launch(myWholePanel.asContextElement()) {
|
|
||||||
val uuid = ZigToolchainComboBoxHandler.onItemSelected(myWholePanel, elem)
|
|
||||||
if (uuid != null) {
|
|
||||||
withEDTContext(myWholePanel.asContextElement()) {
|
|
||||||
applyUUIDNowOrOnReload(uuid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun reset() {
|
|
||||||
reloadTree()
|
|
||||||
super.reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getEmptySelectionString() = ZigBrainsBundle.message("settings.toolchain.list.empty")
|
|
||||||
|
|
||||||
|
class ZigToolchainListEditor : UUIDMapEditor<ZigToolchain>(ZigToolchainDriver.ForList) {
|
||||||
override fun getDisplayName() = ZigBrainsBundle.message("settings.toolchain.list.title")
|
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun reloadTree() {
|
|
||||||
val currentSelection = selectedObject?.asSafely<UUID>()
|
|
||||||
myRoot.removeAllChildren()
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresEdt
|
|
||||||
private fun applyUUIDNowOrOnReload(uuid: UUID?) {
|
|
||||||
selectNodeInTree(uuid)
|
|
||||||
val currentSelection = selectedObject?.asSafely<UUID>()
|
|
||||||
if (uuid != null && uuid != currentSelection) {
|
|
||||||
selectOnNextReload = uuid
|
|
||||||
} else {
|
|
||||||
selectOnNextReload = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun toolchainListChanged() {
|
|
||||||
withEDTContext(myWholePanel.asContextElement()) {
|
|
||||||
reloadTree()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,62 +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.toolchain.ui
|
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
|
||||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
|
|
||||||
internal sealed interface TCListElemIn
|
|
||||||
internal sealed interface TCListElem : TCListElemIn {
|
|
||||||
sealed interface Pseudo: TCListElem
|
|
||||||
sealed interface Toolchain : TCListElem {
|
|
||||||
val toolchain: ZigToolchain
|
|
||||||
|
|
||||||
@JvmRecord
|
|
||||||
data class Suggested(override val toolchain: ZigToolchain): Toolchain, Pseudo
|
|
||||||
|
|
||||||
@JvmRecord
|
|
||||||
data class Actual(val uuid: UUID, override val toolchain: ZigToolchain): Toolchain
|
|
||||||
}
|
|
||||||
object None: TCListElem
|
|
||||||
object Download : TCListElem, Pseudo
|
|
||||||
object FromDisk : TCListElem, Pseudo
|
|
||||||
data class Pending(val elems: Flow<TCListElem>): TCListElem
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
val fetchGroup get() = listOf(Download, FromDisk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmRecord
|
|
||||||
internal data class Separator(val text: String, val line: Boolean) : TCListElemIn
|
|
||||||
|
|
||||||
internal fun Pair<UUID, ZigToolchain>.asActual() = TCListElem.Toolchain.Actual(first, second)
|
|
||||||
|
|
||||||
internal fun ZigToolchain.asSuggested() = TCListElem.Toolchain.Suggested(this)
|
|
||||||
|
|
||||||
internal fun Flow<ZigToolchain>.asPending() = TCListElem.Pending(map { it.asSuggested() })
|
|
|
@ -24,269 +24,61 @@ package com.falsepattern.zigbrains.project.toolchain.ui
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.Icons
|
import com.falsepattern.zigbrains.Icons
|
||||||
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.render
|
import com.falsepattern.zigbrains.project.toolchain.base.render
|
||||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
import com.falsepattern.zigbrains.shared.ui.ListElem
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ZBCellRenderer
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ZBComboBox
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ZBContext
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ZBModel
|
||||||
import com.intellij.icons.AllIcons
|
import com.intellij.icons.AllIcons
|
||||||
import com.intellij.openapi.application.EDT
|
|
||||||
import com.intellij.openapi.application.ModalityState
|
|
||||||
import com.intellij.openapi.application.asContextElement
|
|
||||||
import com.intellij.openapi.application.runInEdt
|
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.ui.ComboBox
|
|
||||||
import com.intellij.ui.CellRendererPanel
|
|
||||||
import com.intellij.ui.CollectionComboBoxModel
|
|
||||||
import com.intellij.ui.ColoredListCellRenderer
|
|
||||||
import com.intellij.ui.GroupHeaderSeparator
|
|
||||||
import com.intellij.ui.SimpleColoredComponent
|
|
||||||
import com.intellij.ui.SimpleTextAttributes
|
import com.intellij.ui.SimpleTextAttributes
|
||||||
import com.intellij.ui.components.panels.OpaquePanel
|
import com.intellij.ui.icons.EMPTY_ICON
|
||||||
import com.intellij.ui.popup.list.ComboBoxPopup
|
|
||||||
import com.intellij.util.Consumer
|
|
||||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
|
||||||
import com.intellij.util.ui.EmptyIcon
|
|
||||||
import com.intellij.util.ui.JBUI
|
|
||||||
import com.intellij.util.ui.UIUtil
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.awt.BorderLayout
|
|
||||||
import java.awt.Component
|
|
||||||
import java.util.IdentityHashMap
|
|
||||||
import java.util.UUID
|
|
||||||
import javax.accessibility.AccessibleContext
|
|
||||||
import javax.swing.JList
|
import javax.swing.JList
|
||||||
import javax.swing.border.Border
|
|
||||||
|
|
||||||
internal class TCComboBoxPopup(
|
class TCComboBox(model: ZBModel<ZigToolchain>): ZBComboBox<ZigToolchain>(model, ::TCCellRenderer)
|
||||||
context: TCContext,
|
|
||||||
selected: TCListElem?,
|
|
||||||
onItemSelected: Consumer<TCListElem>,
|
|
||||||
) : ComboBoxPopup<TCListElem>(context, selected, onItemSelected)
|
|
||||||
|
|
||||||
internal class TCComboBox(model: TCModel): ComboBox<TCListElem>(model) {
|
class TCContext(project: Project?, model: ZBModel<ZigToolchain>): ZBContext<ZigToolchain>(project, model, ::TCCellRenderer)
|
||||||
init {
|
|
||||||
setRenderer(TCCellRenderer { model })
|
|
||||||
}
|
|
||||||
|
|
||||||
var selectedToolchain: UUID?
|
|
||||||
set(value) {
|
|
||||||
if (value == null) {
|
|
||||||
selectedItem = TCListElem.None
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for (i in 0..<model.size) {
|
|
||||||
val element = model.getElementAt(i)
|
|
||||||
if (element is TCListElem.Toolchain.Actual) {
|
|
||||||
if (element.uuid == value) {
|
|
||||||
selectedIndex = i
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectedItem = TCListElem.None
|
|
||||||
}
|
|
||||||
get() {
|
|
||||||
val item = selectedItem
|
|
||||||
return when(item) {
|
|
||||||
is TCListElem.Toolchain.Actual -> item.uuid
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class TCModel private constructor(elements: List<TCListElem>, private var separators: MutableMap<TCListElem, Separator>) : CollectionComboBoxModel<TCListElem>(elements) {
|
|
||||||
private var counter: Int = 0
|
|
||||||
companion object {
|
|
||||||
operator fun invoke(input: List<TCListElemIn>): TCModel {
|
|
||||||
val (elements, separators) = convert(input)
|
|
||||||
val model = TCModel(elements, separators)
|
|
||||||
model.launchPendingResolve()
|
|
||||||
return model
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun convert(input: List<TCListElemIn>): Pair<List<TCListElem>, MutableMap<TCListElem, Separator>> {
|
|
||||||
val separators = IdentityHashMap<TCListElem, Separator>()
|
|
||||||
var lastSeparator: Separator? = null
|
|
||||||
val elements = ArrayList<TCListElem>()
|
|
||||||
input.forEach {
|
|
||||||
when (it) {
|
|
||||||
is TCListElem -> {
|
|
||||||
if (lastSeparator != null) {
|
|
||||||
separators[it] = lastSeparator
|
|
||||||
lastSeparator = null
|
|
||||||
}
|
|
||||||
elements.add(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
is Separator -> lastSeparator = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return elements to separators
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun separatorAbove(elem: TCListElem) = separators[elem]
|
|
||||||
|
|
||||||
private fun launchPendingResolve() {
|
|
||||||
runInEdt(ModalityState.any()) {
|
|
||||||
val counter = this.counter
|
|
||||||
val size = this.size
|
|
||||||
for (i in 0..<size) {
|
|
||||||
val elem = getElementAt(i)
|
|
||||||
?: continue
|
|
||||||
if (elem !is TCListElem.Pending)
|
|
||||||
continue
|
|
||||||
zigCoroutineScope.launch(Dispatchers.EDT + ModalityState.any().asContextElement()) {
|
|
||||||
elem.elems.collect { newElem ->
|
|
||||||
insertBefore(elem, newElem, counter)
|
|
||||||
}
|
|
||||||
remove(elem, counter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresEdt
|
|
||||||
private fun remove(old: TCListElem, oldCounter: Int) {
|
|
||||||
val newCounter = this@TCModel.counter
|
|
||||||
if (oldCounter != newCounter) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val index = this@TCModel.getElementIndex(old)
|
|
||||||
this@TCModel.remove(index)
|
|
||||||
val sep = separators.remove(old)
|
|
||||||
if (sep != null && this@TCModel.size > index) {
|
|
||||||
this@TCModel.getElementAt(index)?.let { separators[it] = sep }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresEdt
|
|
||||||
private fun insertBefore(old: TCListElem, new: TCListElem?, oldCounter: Int) {
|
|
||||||
val newCounter = this@TCModel.counter
|
|
||||||
if (oldCounter != newCounter) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (new == null) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val currentIndex = this@TCModel.getElementIndex(old)
|
|
||||||
separators.remove(old)?.let {
|
|
||||||
separators.put(new, it)
|
|
||||||
}
|
|
||||||
this@TCModel.add(currentIndex, new)
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresEdt
|
|
||||||
fun updateContents(input: List<TCListElemIn>) {
|
|
||||||
counter++
|
|
||||||
val (elements, separators) = convert(input)
|
|
||||||
this.separators = separators
|
|
||||||
replaceAll(elements)
|
|
||||||
launchPendingResolve()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class TCContext(private val project: Project?, private val model: TCModel) : ComboBoxPopup.Context<TCListElem> {
|
|
||||||
override fun getProject(): Project? {
|
|
||||||
return project
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getModel(): TCModel {
|
|
||||||
return model
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getRenderer(): TCCellRenderer {
|
|
||||||
return TCCellRenderer(::getModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class TCCellRenderer(val getModel: () -> TCModel) : ColoredListCellRenderer<TCListElem>() {
|
|
||||||
|
|
||||||
override fun getListCellRendererComponent(
|
|
||||||
list: JList<out TCListElem?>?,
|
|
||||||
value: TCListElem?,
|
|
||||||
index: Int,
|
|
||||||
selected: Boolean,
|
|
||||||
hasFocus: Boolean
|
|
||||||
): Component? {
|
|
||||||
val component = super.getListCellRendererComponent(list, value, index, selected, hasFocus) as SimpleColoredComponent
|
|
||||||
val panel = object : CellRendererPanel(BorderLayout()) {
|
|
||||||
val myContext = component.accessibleContext
|
|
||||||
|
|
||||||
override fun getAccessibleContext(): AccessibleContext? {
|
|
||||||
return myContext
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setBorder(border: Border?) {
|
|
||||||
component.border = border
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panel.add(component, BorderLayout.CENTER)
|
|
||||||
|
|
||||||
component.isOpaque = true
|
|
||||||
list?.let { background = if (selected) it.selectionBackground else it.background }
|
|
||||||
|
|
||||||
val model = getModel()
|
|
||||||
|
|
||||||
if (index == -1) {
|
|
||||||
component.isOpaque = false
|
|
||||||
panel.isOpaque = false
|
|
||||||
return panel
|
|
||||||
}
|
|
||||||
|
|
||||||
val separator = value?.let { model.separatorAbove(it) }
|
|
||||||
|
|
||||||
if (separator != null) {
|
|
||||||
val vGap = if (UIUtil.isUnderNativeMacLookAndFeel()) 1 else 3
|
|
||||||
val separatorComponent = GroupHeaderSeparator(JBUI.insets(vGap, 10, vGap, 0))
|
|
||||||
separatorComponent.isHideLine = !separator.line
|
|
||||||
separatorComponent.caption = separator.text.ifBlank { null }
|
|
||||||
val wrapper = OpaquePanel(BorderLayout())
|
|
||||||
wrapper.add(separatorComponent, BorderLayout.CENTER)
|
|
||||||
list?.let { wrapper.background = it.background }
|
|
||||||
panel.add(wrapper, BorderLayout.NORTH)
|
|
||||||
}
|
|
||||||
|
|
||||||
return panel
|
|
||||||
}
|
|
||||||
|
|
||||||
|
class TCCellRenderer(getModel: () -> ZBModel<ZigToolchain>): ZBCellRenderer<ZigToolchain>(getModel) {
|
||||||
override fun customizeCellRenderer(
|
override fun customizeCellRenderer(
|
||||||
list: JList<out TCListElem?>,
|
list: JList<out ListElem<ZigToolchain>?>,
|
||||||
value: TCListElem?,
|
value: ListElem<ZigToolchain>?,
|
||||||
index: Int,
|
index: Int,
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
hasFocus: Boolean
|
hasFocus: Boolean
|
||||||
) {
|
) {
|
||||||
icon = EMPTY_ICON
|
icon = EMPTY_ICON
|
||||||
when (value) {
|
when (value) {
|
||||||
is TCListElem.Toolchain -> {
|
is ListElem.One -> {
|
||||||
val (icon, isSuggestion) = when(value) {
|
val (icon, isSuggestion) = when(value) {
|
||||||
is TCListElem.Toolchain.Suggested -> AllIcons.General.Information to true
|
is ListElem.One.Suggested -> AllIcons.General.Information to true
|
||||||
is TCListElem.Toolchain.Actual -> Icons.Zig to false
|
is ListElem.One.Actual -> Icons.Zig to false
|
||||||
}
|
}
|
||||||
this.icon = icon
|
this.icon = icon
|
||||||
val toolchain = value.toolchain
|
val item = value.instance
|
||||||
toolchain.render(this, isSuggestion, index == -1)
|
item.render(this, isSuggestion, index == -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
is TCListElem.Download -> {
|
is ListElem.Download -> {
|
||||||
icon = AllIcons.Actions.Download
|
icon = AllIcons.Actions.Download
|
||||||
append(ZigBrainsBundle.message("settings.toolchain.model.download.text"))
|
append(ZigBrainsBundle.message("settings.toolchain.model.download.text"))
|
||||||
}
|
}
|
||||||
|
|
||||||
is TCListElem.FromDisk -> {
|
is ListElem.FromDisk -> {
|
||||||
icon = AllIcons.General.OpenDisk
|
icon = AllIcons.General.OpenDisk
|
||||||
append(ZigBrainsBundle.message("settings.toolchain.model.from-disk.text"))
|
append(ZigBrainsBundle.message("settings.toolchain.model.from-disk.text"))
|
||||||
}
|
}
|
||||||
is TCListElem.Pending -> {
|
is ListElem.Pending -> {
|
||||||
icon = AllIcons.Empty
|
icon = AllIcons.Empty
|
||||||
append("Loading\u2026", SimpleTextAttributes.GRAYED_ATTRIBUTES)
|
append(ZigBrainsBundle.message("settings.toolchain.model.loading.text"), SimpleTextAttributes.GRAYED_ATTRIBUTES)
|
||||||
}
|
}
|
||||||
is TCListElem.None, null -> {
|
is ListElem.None, null -> {
|
||||||
icon = AllIcons.General.BalloonError
|
icon = AllIcons.General.BalloonError
|
||||||
append(ZigBrainsBundle.message("settings.toolchain.model.none.text"), SimpleTextAttributes.ERROR_ATTRIBUTES)
|
append(ZigBrainsBundle.message("settings.toolchain.model.none.text"), SimpleTextAttributes.ERROR_ATTRIBUTES)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private val EMPTY_ICON = EmptyIcon.create(1, 16)
|
}
|
|
@ -28,7 +28,6 @@ import com.falsepattern.zigbrains.shared.ipc.IPCUtil
|
||||||
import com.falsepattern.zigbrains.shared.ipc.ipc
|
import com.falsepattern.zigbrains.shared.ipc.ipc
|
||||||
import com.intellij.execution.ExecutionException
|
import com.intellij.execution.ExecutionException
|
||||||
import com.intellij.execution.configurations.GeneralCommandLine
|
import com.intellij.execution.configurations.GeneralCommandLine
|
||||||
import com.intellij.execution.process.ProcessHandler
|
|
||||||
import com.intellij.execution.process.ProcessOutput
|
import com.intellij.execution.process.ProcessOutput
|
||||||
import com.intellij.execution.process.ProcessTerminatedListener
|
import com.intellij.execution.process.ProcessTerminatedListener
|
||||||
import com.intellij.openapi.options.ConfigurationException
|
import com.intellij.openapi.options.ConfigurationException
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
/*
|
||||||
|
* 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.ui
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.shared.UUIDMapSerializable
|
||||||
|
import com.intellij.openapi.ui.NamedConfigurable
|
||||||
|
import java.awt.Component
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
interface UUIDComboBoxDriver<T> {
|
||||||
|
val theMap: UUIDMapSerializable.Converting<T, *, *>
|
||||||
|
fun constructModelList(): List<ListElemIn<T>>
|
||||||
|
fun createContext(model: ZBModel<T>): ZBContext<T>
|
||||||
|
fun createComboBox(model: ZBModel<T>): ZBComboBox<T>
|
||||||
|
suspend fun resolvePseudo(context: Component, elem: ListElem.Pseudo<T>): UUID?
|
||||||
|
fun createNamedConfigurable(uuid: UUID, elem: T): NamedConfigurable<UUID>
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
* 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.ui
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
|
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
|
||||||
|
import com.intellij.openapi.actionSystem.AnAction
|
||||||
|
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||||
|
import com.intellij.openapi.actionSystem.Presentation
|
||||||
|
import com.intellij.openapi.observable.util.whenListChanged
|
||||||
|
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
|
||||||
|
|
||||||
|
abstract class UUIDMapEditor<T>(val driver: UUIDComboBoxDriver<T>): MasterDetailsComponent() {
|
||||||
|
private var isTreeInitialized = false
|
||||||
|
private var registered: Boolean = false
|
||||||
|
private var selectOnNextReload: UUID? = null
|
||||||
|
private val changeListener: StorageChangeListener = { this@UUIDMapEditor.listChanged() }
|
||||||
|
|
||||||
|
override fun createComponent(): JComponent {
|
||||||
|
if (!isTreeInitialized) {
|
||||||
|
initTree()
|
||||||
|
isTreeInitialized = true
|
||||||
|
}
|
||||||
|
if (!registered) {
|
||||||
|
driver.theMap.addChangeListener(changeListener)
|
||||||
|
registered = true
|
||||||
|
}
|
||||||
|
return super.createComponent()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createActions(fromPopup: Boolean): List<AnAction> {
|
||||||
|
val add = object : DumbAwareAction({ ZigBrainsBundle.message("settings.shared.list.add-action.name") }, Presentation.NULL_STRING, IconUtil.addIcon) {
|
||||||
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
|
val modelList = driver.constructModelList()
|
||||||
|
val model = ZBModel(modelList)
|
||||||
|
val context = driver.createContext(model)
|
||||||
|
val popup = ZBComboBoxPopup(context, null, ::onItemSelected)
|
||||||
|
model.whenListChanged {
|
||||||
|
popup.syncWithModelChange()
|
||||||
|
}
|
||||||
|
popup.showInBestPositionFor(e.dataContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return listOf(add, MyDeleteAction())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onItemDeleted(item: Any?) {
|
||||||
|
if (item is UUID) {
|
||||||
|
driver.theMap.remove(item)
|
||||||
|
}
|
||||||
|
super.onItemDeleted(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onItemSelected(elem: ListElem<T>) {
|
||||||
|
if (elem !is ListElem.Pseudo)
|
||||||
|
return
|
||||||
|
zigCoroutineScope.launch(myWholePanel.asContextElement()) {
|
||||||
|
val uuid = driver.resolvePseudo(myWholePanel, elem)
|
||||||
|
if (uuid != null) {
|
||||||
|
withEDTContext(myWholePanel.asContextElement()) {
|
||||||
|
applyUUIDNowOrOnReload(uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reset() {
|
||||||
|
reloadTree()
|
||||||
|
super.reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getEmptySelectionString() = ZigBrainsBundle.message("settings.shared.list.empty")
|
||||||
|
|
||||||
|
override fun disposeUIResources() {
|
||||||
|
super.disposeUIResources()
|
||||||
|
if (registered) {
|
||||||
|
driver.theMap.removeChangeListener(changeListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addElem(uuid: UUID, elem: T) {
|
||||||
|
val node = MyNode(driver.createNamedConfigurable(uuid, elem))
|
||||||
|
addNode(node, myRoot)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun reloadTree() {
|
||||||
|
val currentSelection = selectedObject?.asSafely<UUID>()
|
||||||
|
myRoot.removeAllChildren()
|
||||||
|
val onReload = selectOnNextReload
|
||||||
|
selectOnNextReload = null
|
||||||
|
var hasOnReload = false
|
||||||
|
driver.theMap.forEach { (uuid, elem) ->
|
||||||
|
addElem(uuid, elem)
|
||||||
|
if (uuid == onReload) {
|
||||||
|
hasOnReload = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(myTree.model as DefaultTreeModel).reload()
|
||||||
|
if (hasOnReload) {
|
||||||
|
selectNodeInTree(onReload)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
currentSelection?.let {
|
||||||
|
selectNodeInTree(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresEdt
|
||||||
|
private fun applyUUIDNowOrOnReload(uuid: UUID?) {
|
||||||
|
selectNodeInTree(uuid)
|
||||||
|
val currentSelection = selectedObject?.asSafely<UUID>()
|
||||||
|
if (uuid != null && uuid != currentSelection) {
|
||||||
|
selectOnNextReload = uuid
|
||||||
|
} else {
|
||||||
|
selectOnNextReload = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun listChanged() {
|
||||||
|
withEDTContext(myWholePanel.asContextElement()) {
|
||||||
|
reloadTree()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,161 @@
|
||||||
|
/*
|
||||||
|
* 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.ui
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
|
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.launchWithEDT
|
||||||
|
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||||
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
|
import com.intellij.openapi.Disposable
|
||||||
|
import com.intellij.openapi.application.EDT
|
||||||
|
import com.intellij.openapi.observable.util.whenListChanged
|
||||||
|
import com.intellij.openapi.options.ShowSettingsUtil
|
||||||
|
import com.intellij.openapi.ui.DialogWrapper
|
||||||
|
import com.intellij.ui.dsl.builder.AlignX
|
||||||
|
import com.intellij.ui.dsl.builder.Row
|
||||||
|
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
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.swing.JButton
|
||||||
|
|
||||||
|
abstract class UUIDMapSelector<T>(val driver: UUIDComboBoxDriver<T>): Disposable {
|
||||||
|
private val comboBox: ZBComboBox<T>
|
||||||
|
private var selectOnNextReload: UUID? = null
|
||||||
|
private val model: ZBModel<T>
|
||||||
|
private var editButton: JButton? = null
|
||||||
|
private val changeListener: StorageChangeListener = { this@UUIDMapSelector.listChanged() }
|
||||||
|
init {
|
||||||
|
model = ZBModel(driver.constructModelList())
|
||||||
|
comboBox = driver.createComboBox(model)
|
||||||
|
comboBox.addItemListener(::itemStateChanged)
|
||||||
|
driver.theMap.addChangeListener(changeListener)
|
||||||
|
model.whenListChanged {
|
||||||
|
if (comboBox.isPopupVisible) {
|
||||||
|
comboBox.isPopupVisible = false
|
||||||
|
comboBox.isPopupVisible = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected var selectedUUID: UUID?
|
||||||
|
get() = comboBox.selectedUUID
|
||||||
|
set(value) {
|
||||||
|
comboBox.selectedUUID = value
|
||||||
|
refreshButtonState(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun refreshButtonState(item: Any?) {
|
||||||
|
editButton?.isEnabled = item is ListElem.One.Actual<*>
|
||||||
|
editButton?.repaint()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun itemStateChanged(event: ItemEvent) {
|
||||||
|
if (event.stateChange != ItemEvent.SELECTED) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val item = event.item
|
||||||
|
refreshButtonState(item)
|
||||||
|
if (item !is ListElem.Pseudo<*>)
|
||||||
|
return
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
item as ListElem.Pseudo<T>
|
||||||
|
zigCoroutineScope.launch(comboBox.asContextElement()) {
|
||||||
|
val uuid = runCatching { driver.resolvePseudo(comboBox, item) }.getOrNull()
|
||||||
|
delay(100)
|
||||||
|
withEDTContext(comboBox.asContextElement()) {
|
||||||
|
applyUUIDNowOrOnReload(uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected suspend fun listChanged() {
|
||||||
|
withContext(Dispatchers.EDT + comboBox.asContextElement()) {
|
||||||
|
val list = driver.constructModelList()
|
||||||
|
model.updateContents(list)
|
||||||
|
val onReload = selectOnNextReload
|
||||||
|
selectOnNextReload = null
|
||||||
|
if (onReload != null) {
|
||||||
|
val element = list.firstOrNull { when(it) {
|
||||||
|
is ListElem.One.Actual<*> -> it.uuid == onReload
|
||||||
|
else -> false
|
||||||
|
} }
|
||||||
|
model.selectedItem = element
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
val selected = model.selected
|
||||||
|
if (selected != null && list.contains(selected)) {
|
||||||
|
model.selectedItem = selected
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
if (selected is ListElem.One.Actual<*>) {
|
||||||
|
val uuid = selected.uuid
|
||||||
|
val element = list.firstOrNull { when(it) {
|
||||||
|
is ListElem.One.Actual -> it.uuid == uuid
|
||||||
|
else -> false
|
||||||
|
} }
|
||||||
|
model.selectedItem = element
|
||||||
|
return@withContext
|
||||||
|
}
|
||||||
|
model.selectedItem = ListElem.None<Any>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun attachComboBoxRow(row: Row): Unit = with(row) {
|
||||||
|
cell(comboBox).resizableColumn().align(AlignX.FILL)
|
||||||
|
button(ZigBrainsBundle.message("settings.toolchain.editor.toolchain.edit-button.name")) { e ->
|
||||||
|
zigCoroutineScope.launchWithEDT(comboBox.asContextElement()) {
|
||||||
|
var selectedUUID = comboBox.selectedUUID ?: return@launchWithEDT
|
||||||
|
val elem = driver.theMap[selectedUUID] ?: return@launchWithEDT
|
||||||
|
val config = driver.createNamedConfigurable(selectedUUID, elem)
|
||||||
|
val apply = ShowSettingsUtil.getInstance().editConfigurable(DialogWrapper.findInstance(comboBox)?.contentPane, config)
|
||||||
|
if (apply) {
|
||||||
|
applyUUIDNowOrOnReload(selectedUUID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.component.let {
|
||||||
|
editButton = it
|
||||||
|
refreshButtonState(comboBox.selectedItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresEdt
|
||||||
|
private fun applyUUIDNowOrOnReload(uuid: UUID?) {
|
||||||
|
comboBox.selectedUUID = uuid
|
||||||
|
if (uuid != null && comboBox.selectedUUID == null) {
|
||||||
|
selectOnNextReload = uuid
|
||||||
|
} else {
|
||||||
|
selectOnNextReload = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
zigToolchainList.removeChangeListener(changeListener)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* 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.ui
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
|
||||||
|
sealed interface ListElemIn<T>
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
sealed interface ListElem<T> : ListElemIn<T> {
|
||||||
|
sealed interface Pseudo<T>: ListElem<T>
|
||||||
|
sealed interface One<T> : ListElem<T> {
|
||||||
|
val instance: T
|
||||||
|
|
||||||
|
@JvmRecord
|
||||||
|
data class Suggested<T>(override val instance: T): One<T>, Pseudo<T>
|
||||||
|
|
||||||
|
@JvmRecord
|
||||||
|
data class Actual<T>(val uuid: UUID, override val instance: T): One<T>
|
||||||
|
}
|
||||||
|
class None<T> private constructor(): ListElem<T> {
|
||||||
|
companion object {
|
||||||
|
private val INSTANCE = None<Any>()
|
||||||
|
operator fun <T> invoke(): None<T> {
|
||||||
|
return INSTANCE as None<T>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class Download<T> private constructor(): ListElem<T>, Pseudo<T> {
|
||||||
|
companion object {
|
||||||
|
private val INSTANCE = Download<Any>()
|
||||||
|
operator fun <T> invoke(): Download<T> {
|
||||||
|
return INSTANCE as Download<T>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class FromDisk<T> private constructor(): ListElem<T>, Pseudo<T> {
|
||||||
|
companion object {
|
||||||
|
private val INSTANCE = FromDisk<Any>()
|
||||||
|
operator fun <T> invoke(): FromDisk<T> {
|
||||||
|
return INSTANCE as FromDisk<T>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data class Pending<T>(val elems: Flow<ListElem<T>>): ListElem<T>
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val fetchGroup = listOf<ListElem<Any>>(Download(), FromDisk())
|
||||||
|
fun <T> fetchGroup() = fetchGroup as List<ListElem<T>>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmRecord
|
||||||
|
data class Separator<T>(val text: String, val line: Boolean) : ListElemIn<T>
|
||||||
|
|
||||||
|
fun <T> Pair<UUID, T>.asActual() = ListElem.One.Actual(first, second)
|
||||||
|
|
||||||
|
fun <T> T.asSuggested() = ListElem.One.Suggested(this)
|
||||||
|
|
||||||
|
fun <T> Flow<T>.asPending() = ListElem.Pending(map { it.asSuggested() })
|
|
@ -0,0 +1,255 @@
|
||||||
|
/*
|
||||||
|
* 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.ui
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
|
import com.intellij.openapi.application.EDT
|
||||||
|
import com.intellij.openapi.application.ModalityState
|
||||||
|
import com.intellij.openapi.application.asContextElement
|
||||||
|
import com.intellij.openapi.application.runInEdt
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.ui.ComboBox
|
||||||
|
import com.intellij.ui.CellRendererPanel
|
||||||
|
import com.intellij.ui.CollectionComboBoxModel
|
||||||
|
import com.intellij.ui.ColoredListCellRenderer
|
||||||
|
import com.intellij.ui.GroupHeaderSeparator
|
||||||
|
import com.intellij.ui.SimpleColoredComponent
|
||||||
|
import com.intellij.ui.components.panels.OpaquePanel
|
||||||
|
import com.intellij.ui.popup.list.ComboBoxPopup
|
||||||
|
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||||
|
import com.intellij.util.ui.EmptyIcon
|
||||||
|
import com.intellij.util.ui.JBUI
|
||||||
|
import com.intellij.util.ui.UIUtil
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.Component
|
||||||
|
import java.util.IdentityHashMap
|
||||||
|
import java.util.UUID
|
||||||
|
import java.util.function.Consumer
|
||||||
|
import javax.accessibility.AccessibleContext
|
||||||
|
import javax.swing.JList
|
||||||
|
import javax.swing.border.Border
|
||||||
|
|
||||||
|
class ZBComboBoxPopup<T>(
|
||||||
|
context: ZBContext<T>,
|
||||||
|
selected: ListElem<T>?,
|
||||||
|
onItemSelected: Consumer<ListElem<T>>,
|
||||||
|
) : ComboBoxPopup<ListElem<T>>(context, selected, onItemSelected)
|
||||||
|
|
||||||
|
open class ZBComboBox<T>(model: ZBModel<T>, renderer: (() -> ZBModel<T>)-> ZBCellRenderer<T>): ComboBox<ListElem<T>>(model) {
|
||||||
|
init {
|
||||||
|
setRenderer(renderer { model })
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedUUID: UUID?
|
||||||
|
set(value) {
|
||||||
|
if (value == null) {
|
||||||
|
selectedItem = ListElem.None
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (i in 0..<model.size) {
|
||||||
|
val element = model.getElementAt(i)
|
||||||
|
if (element is ListElem.One.Actual) {
|
||||||
|
if (element.uuid == value) {
|
||||||
|
selectedIndex = i
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectedItem = ListElem.None
|
||||||
|
}
|
||||||
|
get() {
|
||||||
|
val item = selectedItem
|
||||||
|
return when(item) {
|
||||||
|
is ListElem.One.Actual<*> -> item.uuid
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ZBModel<T> private constructor(elements: List<ListElem<T>>, private var separators: MutableMap<ListElem<T>, Separator<T>>) : CollectionComboBoxModel<ListElem<T>>(elements) {
|
||||||
|
private var counter: Int = 0
|
||||||
|
companion object {
|
||||||
|
operator fun <T> invoke(input: List<ListElemIn<T>>): ZBModel<T> {
|
||||||
|
val (elements, separators) = convert(input)
|
||||||
|
val model = ZBModel<T>(elements, separators)
|
||||||
|
model.launchPendingResolve()
|
||||||
|
return model
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> convert(input: List<ListElemIn<T>>): Pair<List<ListElem<T>>, MutableMap<ListElem<T>, Separator<T>>> {
|
||||||
|
val separators = IdentityHashMap<ListElem<T>, Separator<T>>()
|
||||||
|
var lastSeparator: Separator<T>? = null
|
||||||
|
val elements = ArrayList<ListElem<T>>()
|
||||||
|
input.forEach {
|
||||||
|
when (it) {
|
||||||
|
is ListElem -> {
|
||||||
|
if (lastSeparator != null) {
|
||||||
|
separators[it] = lastSeparator
|
||||||
|
lastSeparator = null
|
||||||
|
}
|
||||||
|
elements.add(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Separator -> lastSeparator = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return elements to separators
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun separatorAbove(elem: ListElem<T>) = separators[elem]
|
||||||
|
|
||||||
|
private fun launchPendingResolve() {
|
||||||
|
runInEdt(ModalityState.any()) {
|
||||||
|
val counter = this.counter
|
||||||
|
val size = this.size
|
||||||
|
for (i in 0..<size) {
|
||||||
|
val elem = getElementAt(i)
|
||||||
|
?: continue
|
||||||
|
if (elem !is ListElem.Pending)
|
||||||
|
continue
|
||||||
|
zigCoroutineScope.launch(Dispatchers.EDT + ModalityState.any().asContextElement()) {
|
||||||
|
elem.elems.collect { newElem ->
|
||||||
|
insertBefore(elem, newElem, counter)
|
||||||
|
}
|
||||||
|
remove(elem, counter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresEdt
|
||||||
|
private fun remove(old: ListElem<T>, oldCounter: Int) {
|
||||||
|
val newCounter = this@ZBModel.counter
|
||||||
|
if (oldCounter != newCounter) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val index = this@ZBModel.getElementIndex(old)
|
||||||
|
this@ZBModel.remove(index)
|
||||||
|
val sep = separators.remove(old)
|
||||||
|
if (sep != null && this@ZBModel.size > index) {
|
||||||
|
this@ZBModel.getElementAt(index)?.let { separators[it] = sep }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresEdt
|
||||||
|
private fun insertBefore(old: ListElem<T>, new: ListElem<T>?, oldCounter: Int) {
|
||||||
|
val newCounter = this@ZBModel.counter
|
||||||
|
if (oldCounter != newCounter) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (new == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val currentIndex = this@ZBModel.getElementIndex(old)
|
||||||
|
separators.remove(old)?.let {
|
||||||
|
separators.put(new, it)
|
||||||
|
}
|
||||||
|
this@ZBModel.add(currentIndex, new)
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresEdt
|
||||||
|
fun updateContents(input: List<ListElemIn<T>>) {
|
||||||
|
counter++
|
||||||
|
val (elements, separators) = convert(input)
|
||||||
|
this.separators = separators
|
||||||
|
replaceAll(elements)
|
||||||
|
launchPendingResolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open class ZBContext<T>(private val project: Project?, private val model: ZBModel<T>, private val getRenderer: (() -> ZBModel<T>) -> ZBCellRenderer<T>) : ComboBoxPopup.Context<ListElem<T>> {
|
||||||
|
override fun getProject(): Project? {
|
||||||
|
return project
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getModel(): ZBModel<T> {
|
||||||
|
return model
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getRenderer(): ZBCellRenderer<T> {
|
||||||
|
return getRenderer(::getModel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class ZBCellRenderer<T>(val getModel: () -> ZBModel<T>) : ColoredListCellRenderer<ListElem<T>>() {
|
||||||
|
final override fun getListCellRendererComponent(
|
||||||
|
list: JList<out ListElem<T>?>?,
|
||||||
|
value: ListElem<T>?,
|
||||||
|
index: Int,
|
||||||
|
selected: Boolean,
|
||||||
|
hasFocus: Boolean
|
||||||
|
): Component? {
|
||||||
|
val component = super.getListCellRendererComponent(list, value, index, selected, hasFocus) as SimpleColoredComponent
|
||||||
|
val panel = object : CellRendererPanel(BorderLayout()) {
|
||||||
|
val myContext = component.accessibleContext
|
||||||
|
|
||||||
|
override fun getAccessibleContext(): AccessibleContext? {
|
||||||
|
return myContext
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setBorder(border: Border?) {
|
||||||
|
component.border = border
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panel.add(component, BorderLayout.CENTER)
|
||||||
|
|
||||||
|
component.isOpaque = true
|
||||||
|
list?.let { background = if (selected) it.selectionBackground else it.background }
|
||||||
|
|
||||||
|
val model = getModel()
|
||||||
|
|
||||||
|
if (index == -1) {
|
||||||
|
component.isOpaque = false
|
||||||
|
panel.isOpaque = false
|
||||||
|
return panel
|
||||||
|
}
|
||||||
|
|
||||||
|
val separator = value?.let { model.separatorAbove(it) }
|
||||||
|
|
||||||
|
if (separator != null) {
|
||||||
|
val vGap = if (UIUtil.isUnderNativeMacLookAndFeel()) 1 else 3
|
||||||
|
val separatorComponent = GroupHeaderSeparator(JBUI.insets(vGap, 10, vGap, 0))
|
||||||
|
separatorComponent.isHideLine = !separator.line
|
||||||
|
separatorComponent.caption = separator.text.ifBlank { null }
|
||||||
|
val wrapper = OpaquePanel(BorderLayout())
|
||||||
|
wrapper.add(separatorComponent, BorderLayout.CENTER)
|
||||||
|
list?.let { wrapper.background = it.background }
|
||||||
|
panel.add(wrapper, BorderLayout.NORTH)
|
||||||
|
}
|
||||||
|
|
||||||
|
return panel
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract override fun customizeCellRenderer(
|
||||||
|
list: JList<out ListElem<T>?>,
|
||||||
|
value: ListElem<T>?,
|
||||||
|
index: Int,
|
||||||
|
selected: Boolean,
|
||||||
|
hasFocus: Boolean
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val EMPTY_ICON = EmptyIcon.create(1, 16)
|
|
@ -110,22 +110,22 @@ 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.shared.list.add-action.name=Add New
|
||||||
|
settings.shared.list.empty=Select an entry to view or edit its details here
|
||||||
settings.project.display-name=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
|
||||||
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.toolchain.label=Toolchain
|
settings.toolchain.editor.toolchain.label=Toolchain
|
||||||
settings.toolchain.editor.toolchain-default.label=Default 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>
|
||||||
|
settings.toolchain.model.loading.text=Loading\u2026
|
||||||
settings.toolchain.model.from-disk.text=Add Zig from disk\u2026
|
settings.toolchain.model.from-disk.text=Add Zig from disk\u2026
|
||||||
settings.toolchain.model.download.text=Download Zig\u2026
|
settings.toolchain.model.download.text=Download Zig\u2026
|
||||||
settings.toolchain.list.title=Toolchains
|
settings.toolchain.list.title=Toolchains
|
||||||
settings.toolchain.list.add-action.name=Add New
|
|
||||||
settings.toolchain.list.empty=Select a toolchain to view or edit its details here
|
|
||||||
settings.toolchain.downloader.title=Install Zig
|
settings.toolchain.downloader.title=Install Zig
|
||||||
settings.toolchain.downloader.version.label=Version:
|
settings.toolchain.downloader.version.label=Version:
|
||||||
settings.toolchain.downloader.location.label=Location:
|
settings.toolchain.downloader.location.label=Location:
|
||||||
|
|
|
@ -24,20 +24,18 @@ package com.falsepattern.zigbrains.lsp
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.lsp.config.SuspendingZLSConfigProvider
|
import com.falsepattern.zigbrains.lsp.config.SuspendingZLSConfigProvider
|
||||||
import com.falsepattern.zigbrains.lsp.config.ZLSConfig
|
import com.falsepattern.zigbrains.lsp.config.ZLSConfig
|
||||||
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
|
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
|
||||||
import com.falsepattern.zigbrains.project.toolchain.base.suggestZigToolchain
|
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
|
||||||
import com.intellij.notification.Notification
|
import com.intellij.notification.Notification
|
||||||
import com.intellij.notification.NotificationType
|
import com.intellij.notification.NotificationType
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.util.UserDataHolderBase
|
|
||||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||||
import kotlin.io.path.pathString
|
import kotlin.io.path.pathString
|
||||||
|
|
||||||
class ToolchainZLSConfigProvider: SuspendingZLSConfigProvider {
|
class ToolchainZLSConfigProvider: SuspendingZLSConfigProvider {
|
||||||
override suspend fun getEnvironment(project: Project, previous: ZLSConfig): ZLSConfig {
|
override suspend fun getEnvironment(project: Project, previous: ZLSConfig): ZLSConfig {
|
||||||
val svc = project.zigProjectSettings
|
val svc = ZigToolchainService.getInstance(project)
|
||||||
var state = svc.state
|
val toolchain = svc.toolchain ?: return previous
|
||||||
val toolchain = state.toolchain ?: project.suggestZigToolchain(UserDataHolderBase()) ?: return previous
|
|
||||||
|
|
||||||
val env = toolchain.zig.getEnv(project).getOrElse { throwable ->
|
val env = toolchain.zig.getEnv(project).getOrElse { throwable ->
|
||||||
throwable.printStackTrace()
|
throwable.printStackTrace()
|
||||||
|
@ -65,16 +63,10 @@ class ToolchainZLSConfigProvider: SuspendingZLSConfigProvider {
|
||||||
).notify(project)
|
).notify(project)
|
||||||
return previous
|
return previous
|
||||||
}
|
}
|
||||||
var lib = if (state.overrideStdPath && state.explicitPathToStd != null) {
|
var lib = if (toolchain is LocalZigToolchain)
|
||||||
state.explicitPathToStd?.toNioPathOrNull() ?: run {
|
toolchain.std
|
||||||
Notification(
|
else
|
||||||
"zigbrains-lsp",
|
null
|
||||||
"Invalid zig standard library path override: ${state.explicitPathToStd}",
|
|
||||||
NotificationType.ERROR
|
|
||||||
).notify(project)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
} else null
|
|
||||||
|
|
||||||
if (lib == null) {
|
if (lib == null) {
|
||||||
lib = env.libDirectory.toNioPathOrNull() ?: run {
|
lib = env.libDirectory.toNioPathOrNull() ?: run {
|
||||||
|
|
|
@ -1,47 +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.lsp
|
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.lsp.settings.ZLSSettingsConfigurable
|
|
||||||
import com.falsepattern.zigbrains.lsp.settings.ZLSSettingsPanel
|
|
||||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
|
||||||
import com.falsepattern.zigbrains.shared.SubConfigurable
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.openapi.project.ProjectManager
|
|
||||||
|
|
||||||
class ZLSProjectConfigurationProvider: ZigProjectConfigurationProvider {
|
|
||||||
override fun handleMainConfigChanged(project: Project) {
|
|
||||||
startLSP(project, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createConfigurable(project: Project): SubConfigurable<Project> {
|
|
||||||
return ZLSSettingsConfigurable(project)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun createNewProjectSettingsPanel(holder: ZigProjectConfigurationProvider.SettingsPanelHolder): ZigProjectConfigurationProvider.SettingsPanel {
|
|
||||||
return ZLSSettingsPanel(ProjectManager.getInstance().defaultProject)
|
|
||||||
}
|
|
||||||
|
|
||||||
override val priority: Int
|
|
||||||
get() = 1000
|
|
||||||
}
|
|
|
@ -22,36 +22,19 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.lsp
|
package com.falsepattern.zigbrains.lsp
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.direnv.DirenvCmd
|
|
||||||
import com.falsepattern.zigbrains.direnv.emptyEnv
|
|
||||||
import com.falsepattern.zigbrains.direnv.getDirenv
|
|
||||||
import com.falsepattern.zigbrains.lsp.settings.zlsSettings
|
|
||||||
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
|
|
||||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.startup.ProjectActivity
|
import com.intellij.openapi.startup.ProjectActivity
|
||||||
import com.intellij.ui.EditorNotifications
|
import com.intellij.ui.EditorNotifications
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.io.path.pathString
|
|
||||||
|
|
||||||
class ZLSStartup: ProjectActivity {
|
class ZLSStartup: ProjectActivity {
|
||||||
override suspend fun execute(project: Project) {
|
override suspend fun execute(project: Project) {
|
||||||
val zlsState = project.zlsSettings.state
|
|
||||||
if (zlsState.zlsPath.isBlank()) {
|
|
||||||
val env = if (DirenvCmd.direnvInstalled() && !project.isDefault && project.zigProjectSettings.state.direnv)
|
|
||||||
project.getDirenv()
|
|
||||||
else
|
|
||||||
emptyEnv
|
|
||||||
env.findExecutableOnPATH("zls")?.let {
|
|
||||||
zlsState.zlsPath = it.pathString
|
|
||||||
project.zlsSettings.state = zlsState
|
|
||||||
}
|
|
||||||
}
|
|
||||||
project.zigCoroutineScope.launch {
|
project.zigCoroutineScope.launch {
|
||||||
var currentState = project.zlsRunningAsync()
|
var currentState = project.zlsRunning()
|
||||||
while (!project.isDisposed) {
|
while (!project.isDisposed) {
|
||||||
val running = project.zlsRunningAsync()
|
val running = project.zlsRunning()
|
||||||
if (currentState != running) {
|
if (currentState != running) {
|
||||||
EditorNotifications.getInstance(project).updateAllNotifications()
|
EditorNotifications.getInstance(project).updateAllNotifications()
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,8 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.lsp
|
package com.falsepattern.zigbrains.lsp
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.direnv.emptyEnv
|
|
||||||
import com.falsepattern.zigbrains.direnv.getDirenv
|
|
||||||
import com.falsepattern.zigbrains.lsp.config.ZLSConfigProviderBase
|
import com.falsepattern.zigbrains.lsp.config.ZLSConfigProviderBase
|
||||||
import com.falsepattern.zigbrains.lsp.settings.zlsSettings
|
import com.falsepattern.zigbrains.lsp.zls.ZLSService
|
||||||
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
|
|
||||||
import com.intellij.execution.configurations.GeneralCommandLine
|
import com.intellij.execution.configurations.GeneralCommandLine
|
||||||
import com.intellij.notification.Notification
|
import com.intellij.notification.Notification
|
||||||
import com.intellij.notification.NotificationType
|
import com.intellij.notification.NotificationType
|
||||||
|
@ -55,30 +52,9 @@ class ZLSStreamConnectionProvider private constructor(private val project: Proje
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
suspend fun getCommand(project: Project): List<String>? {
|
suspend fun getCommand(project: Project): List<String>? {
|
||||||
val svc = project.zlsSettings
|
val svc = ZLSService.getInstance(project)
|
||||||
val state = svc.state
|
val zls = svc.zls ?: return null
|
||||||
val zlsPath: Path = state.zlsPath.let { zlsPath ->
|
val zlsPath: Path = zls.path
|
||||||
if (zlsPath.isEmpty()) {
|
|
||||||
val env = if (project.zigProjectSettings.state.direnv) project.getDirenv() else emptyEnv
|
|
||||||
env.findExecutableOnPATH("zls") ?: run {
|
|
||||||
Notification(
|
|
||||||
"zigbrains-lsp",
|
|
||||||
ZLSBundle.message("notification.message.could-not-detect.content"),
|
|
||||||
NotificationType.ERROR
|
|
||||||
).notify(project)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
zlsPath.toNioPathOrNull() ?: run {
|
|
||||||
Notification(
|
|
||||||
"zigbrains-lsp",
|
|
||||||
ZLSBundle.message("notification.message.zls-exe-path-invalid.content", zlsPath),
|
|
||||||
NotificationType.ERROR
|
|
||||||
).notify(project)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!zlsPath.toFile().exists()) {
|
if (!zlsPath.toFile().exists()) {
|
||||||
Notification(
|
Notification(
|
||||||
"zigbrains-lsp",
|
"zigbrains-lsp",
|
||||||
|
@ -95,7 +71,7 @@ class ZLSStreamConnectionProvider private constructor(private val project: Proje
|
||||||
).notify(project)
|
).notify(project)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val configPath: Path? = state.zlsConfigPath.let { configPath ->
|
val configPath: Path? = "".let { configPath ->
|
||||||
if (configPath.isNotBlank()) {
|
if (configPath.isNotBlank()) {
|
||||||
configPath.toNioPathOrNull()?.let { nioPath ->
|
configPath.toNioPathOrNull()?.let { nioPath ->
|
||||||
if (!nioPath.toFile().exists()) {
|
if (!nioPath.toFile().exists()) {
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.lsp
|
package com.falsepattern.zigbrains.lsp
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.lsp.settings.zlsSettings
|
import com.falsepattern.zigbrains.lsp.zls.ZLSService
|
||||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
import com.intellij.openapi.components.service
|
import com.intellij.openapi.components.service
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
@ -68,39 +68,29 @@ class ZigLanguageServerFactory: LanguageServerFactory, LanguageServerEnablementS
|
||||||
}
|
}
|
||||||
features.inlayHintFeature = object: LSPInlayHintFeature() {
|
features.inlayHintFeature = object: LSPInlayHintFeature() {
|
||||||
override fun isEnabled(file: PsiFile): Boolean {
|
override fun isEnabled(file: PsiFile): Boolean {
|
||||||
return features.project.zlsSettings.state.inlayHints
|
return ZLSService.getInstance(project).zls?.settings?.inlayHints == true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return features
|
return features
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun isEnabled(project: Project) = project.zlsEnabledSync()
|
override fun isEnabled(project: Project) = project.zlsEnabled()
|
||||||
|
|
||||||
override fun setEnabled(enabled: Boolean, project: Project) {
|
override fun setEnabled(enabled: Boolean, project: Project) {
|
||||||
project.zlsEnabled(enabled)
|
project.zlsEnabled(enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun Project.zlsEnabledAsync(): Boolean {
|
fun Project.zlsEnabled(): Boolean {
|
||||||
return (getUserData(ENABLED_KEY) != false) && zlsSettings.validateAsync()
|
return (getUserData(ENABLED_KEY) != false) && ZLSService.getInstance(this).zls?.isValid() == true
|
||||||
}
|
|
||||||
|
|
||||||
fun Project.zlsEnabledSync(): Boolean {
|
|
||||||
return (getUserData(ENABLED_KEY) != false) && zlsSettings.validateSync()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Project.zlsEnabled(value: Boolean) {
|
fun Project.zlsEnabled(value: Boolean) {
|
||||||
putUserData(ENABLED_KEY, value)
|
putUserData(ENABLED_KEY, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun Project.zlsRunningAsync(): Boolean {
|
fun Project.zlsRunning(): Boolean {
|
||||||
if (!zlsEnabledAsync())
|
if (!zlsEnabled())
|
||||||
return false
|
|
||||||
return lsm.isRunning
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Project.zlsRunningSync(): Boolean {
|
|
||||||
if (!zlsEnabledSync())
|
|
||||||
return false
|
return false
|
||||||
return lsm.isRunning
|
return lsm.isRunning
|
||||||
}
|
}
|
||||||
|
@ -135,7 +125,7 @@ private suspend fun doStart(project: Project, restart: Boolean) {
|
||||||
project.lsm.stop("ZigBrains")
|
project.lsm.stop("ZigBrains")
|
||||||
delay(250)
|
delay(250)
|
||||||
}
|
}
|
||||||
if (project.zlsSettings.validateAsync()) {
|
if (ZLSService.getInstance(project).zls?.isValid() == true) {
|
||||||
delay(250)
|
delay(250)
|
||||||
project.lsm.start("ZigBrains")
|
project.lsm.start("ZigBrains")
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,8 @@
|
||||||
package com.falsepattern.zigbrains.lsp.notification
|
package com.falsepattern.zigbrains.lsp.notification
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.lsp.ZLSBundle
|
import com.falsepattern.zigbrains.lsp.ZLSBundle
|
||||||
import com.falsepattern.zigbrains.lsp.settings.zlsSettings
|
import com.falsepattern.zigbrains.lsp.zls.ZLSService
|
||||||
import com.falsepattern.zigbrains.lsp.zlsRunningAsync
|
import com.falsepattern.zigbrains.lsp.zlsRunning
|
||||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
import com.falsepattern.zigbrains.zig.ZigFileType
|
import com.falsepattern.zigbrains.zig.ZigFileType
|
||||||
import com.falsepattern.zigbrains.zon.ZonFileType
|
import com.falsepattern.zigbrains.zon.ZonFileType
|
||||||
|
@ -49,10 +49,10 @@ class ZigEditorNotificationProvider: EditorNotificationProvider, DumbAware {
|
||||||
else -> return null
|
else -> return null
|
||||||
}
|
}
|
||||||
val task = project.zigCoroutineScope.async {
|
val task = project.zigCoroutineScope.async {
|
||||||
if (project.zlsRunningAsync()) {
|
if (project.zlsRunning()) {
|
||||||
return@async null
|
return@async null
|
||||||
} else {
|
} else {
|
||||||
return@async project.zlsSettings.validateAsync()
|
return@async ZLSService.getInstance(project).zls?.isValid() == true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Function { editor ->
|
return Function { editor ->
|
||||||
|
|
|
@ -1,158 +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.lsp.settings
|
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.direnv.emptyEnv
|
|
||||||
import com.falsepattern.zigbrains.direnv.getDirenv
|
|
||||||
import com.falsepattern.zigbrains.lsp.ZLSBundle
|
|
||||||
import com.falsepattern.zigbrains.lsp.startLSP
|
|
||||||
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
|
|
||||||
import com.intellij.ide.IdeEventQueue
|
|
||||||
import com.intellij.openapi.components.*
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
|
||||||
import com.intellij.platform.ide.progress.ModalTaskOwner
|
|
||||||
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
|
|
||||||
import com.intellij.util.application
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import java.nio.file.Path
|
|
||||||
import kotlin.io.path.isExecutable
|
|
||||||
import kotlin.io.path.isRegularFile
|
|
||||||
|
|
||||||
@Service(Service.Level.PROJECT)
|
|
||||||
@State(
|
|
||||||
name = "ZLSSettings",
|
|
||||||
storages = [Storage(value = "zigbrains.xml")]
|
|
||||||
)
|
|
||||||
class ZLSProjectSettingsService(val project: Project): PersistentStateComponent<ZLSSettings> {
|
|
||||||
@Volatile
|
|
||||||
private var state = ZLSSettings()
|
|
||||||
@Volatile
|
|
||||||
private var dirty = true
|
|
||||||
@Volatile
|
|
||||||
private var valid = false
|
|
||||||
|
|
||||||
private val mutex = Mutex()
|
|
||||||
override fun getState(): ZLSSettings {
|
|
||||||
return state.copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setState(value: ZLSSettings) {
|
|
||||||
runBlocking {
|
|
||||||
mutex.withLock {
|
|
||||||
this@ZLSProjectSettingsService.state = value
|
|
||||||
dirty = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
startLSP(project, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun loadState(state: ZLSSettings) {
|
|
||||||
setState(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun validateAsync(): Boolean {
|
|
||||||
mutex.withLock {
|
|
||||||
if (dirty) {
|
|
||||||
val state = this.state
|
|
||||||
valid = doValidate(project, state)
|
|
||||||
dirty = false
|
|
||||||
}
|
|
||||||
return valid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun validateSync(): Boolean {
|
|
||||||
val isValid: Boolean? = runBlocking {
|
|
||||||
mutex.withLock {
|
|
||||||
if (dirty)
|
|
||||||
null
|
|
||||||
else
|
|
||||||
valid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isValid != null) {
|
|
||||||
return isValid
|
|
||||||
}
|
|
||||||
return if (useModalProgress()) {
|
|
||||||
runWithModalProgressBlocking(ModalTaskOwner.project(project), ZLSBundle.message("progress.title.validate")) {
|
|
||||||
validateAsync()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
runBlocking {
|
|
||||||
validateAsync()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val prohibitClass: Class<*>? = runCatching {
|
|
||||||
Class.forName("com_intellij_ide_ProhibitAWTEvents".replace('_', '.'))
|
|
||||||
}.getOrNull()
|
|
||||||
|
|
||||||
private val postProcessors: List<*>? = runCatching {
|
|
||||||
if (prohibitClass == null)
|
|
||||||
return@runCatching null
|
|
||||||
val postProcessorsField = IdeEventQueue::class.java.getDeclaredField("postProcessors")
|
|
||||||
postProcessorsField.isAccessible = true
|
|
||||||
postProcessorsField.get(IdeEventQueue.getInstance()) as? List<*>
|
|
||||||
}.getOrNull()
|
|
||||||
|
|
||||||
private fun useModalProgress(): Boolean {
|
|
||||||
if (!application.isDispatchThread)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (application.isWriteAccessAllowed)
|
|
||||||
return false
|
|
||||||
|
|
||||||
if (postProcessors == null)
|
|
||||||
return true
|
|
||||||
|
|
||||||
return postProcessors.none { prohibitClass!!.isInstance(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun doValidate(project: Project, state: ZLSSettings): Boolean {
|
|
||||||
val zlsPath: Path = state.zlsPath.let { zlsPath ->
|
|
||||||
if (zlsPath.isEmpty()) {
|
|
||||||
val env = if (project.zigProjectSettings.state.direnv) project.getDirenv() else emptyEnv
|
|
||||||
env.findExecutableOnPATH("zls") ?: run {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
zlsPath.toNioPathOrNull() ?: run {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!zlsPath.toFile().exists()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!zlsPath.isRegularFile() || !zlsPath.isExecutable()) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
val Project.zlsSettings get() = service<ZLSProjectSettingsService>()
|
|
|
@ -25,35 +25,31 @@ package com.falsepattern.zigbrains.lsp.settings
|
||||||
import com.falsepattern.zigbrains.lsp.config.SemanticTokens
|
import com.falsepattern.zigbrains.lsp.config.SemanticTokens
|
||||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.util.xmlb.annotations.Attribute
|
||||||
import org.jetbrains.annotations.NonNls
|
import org.jetbrains.annotations.NonNls
|
||||||
|
|
||||||
@Suppress("PropertyName")
|
@Suppress("PropertyName")
|
||||||
data class ZLSSettings(
|
data class ZLSSettings(
|
||||||
var zlsPath: @NonNls String = "",
|
@JvmField @Attribute val zlsConfigPath: @NonNls String = "",
|
||||||
var zlsConfigPath: @NonNls String = "",
|
@JvmField @Attribute val inlayHints: Boolean = true,
|
||||||
val inlayHints: Boolean = true,
|
@JvmField @Attribute val enable_snippets: Boolean = true,
|
||||||
val enable_snippets: Boolean = true,
|
@JvmField @Attribute val enable_argument_placeholders: Boolean = true,
|
||||||
val enable_argument_placeholders: Boolean = true,
|
@JvmField @Attribute val completion_label_details: Boolean = true,
|
||||||
val completion_label_details: Boolean = true,
|
@JvmField @Attribute val enable_build_on_save: Boolean = false,
|
||||||
val enable_build_on_save: Boolean = false,
|
@JvmField @Attribute val build_on_save_args: String = "",
|
||||||
val build_on_save_args: String = "",
|
@JvmField @Attribute val semantic_tokens: SemanticTokens = SemanticTokens.full,
|
||||||
val semantic_tokens: SemanticTokens = SemanticTokens.full,
|
@JvmField @Attribute val inlay_hints_show_variable_type_hints: Boolean = true,
|
||||||
val inlay_hints_show_variable_type_hints: Boolean = true,
|
@JvmField @Attribute val inlay_hints_show_struct_literal_field_type: Boolean = true,
|
||||||
val inlay_hints_show_struct_literal_field_type: Boolean = true,
|
@JvmField @Attribute val inlay_hints_show_parameter_name: Boolean = true,
|
||||||
val inlay_hints_show_parameter_name: Boolean = true,
|
@JvmField @Attribute val inlay_hints_show_builtin: Boolean = true,
|
||||||
val inlay_hints_show_builtin: Boolean = true,
|
@JvmField @Attribute val inlay_hints_exclude_single_argument: Boolean = true,
|
||||||
val inlay_hints_exclude_single_argument: Boolean = true,
|
@JvmField @Attribute val inlay_hints_hide_redundant_param_names: Boolean = false,
|
||||||
val inlay_hints_hide_redundant_param_names: Boolean = false,
|
@JvmField @Attribute val inlay_hints_hide_redundant_param_names_last_token: Boolean = false,
|
||||||
val inlay_hints_hide_redundant_param_names_last_token: Boolean = false,
|
@JvmField @Attribute val warn_style: Boolean = false,
|
||||||
val warn_style: Boolean = false,
|
@JvmField @Attribute val highlight_global_var_declarations: Boolean = false,
|
||||||
val highlight_global_var_declarations: Boolean = false,
|
@JvmField @Attribute val skip_std_references: Boolean = false,
|
||||||
val skip_std_references: Boolean = false,
|
@JvmField @Attribute val prefer_ast_check_as_child_process: Boolean = true,
|
||||||
val prefer_ast_check_as_child_process: Boolean = true,
|
@JvmField @Attribute val builtin_path: String? = null,
|
||||||
val builtin_path: String? = null,
|
@JvmField @Attribute val build_runner_path: @NonNls String? = null,
|
||||||
val build_runner_path: @NonNls String? = null,
|
@JvmField @Attribute val global_cache_path: @NonNls String? = null,
|
||||||
val global_cache_path: @NonNls String? = null,
|
)
|
||||||
): ZigProjectConfigurationProvider.Settings {
|
|
||||||
override fun apply(project: Project) {
|
|
||||||
project.zlsSettings.loadState(this)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -24,12 +24,13 @@ package com.falsepattern.zigbrains.lsp.settings
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.lsp.config.ZLSConfig
|
import com.falsepattern.zigbrains.lsp.config.ZLSConfig
|
||||||
import com.falsepattern.zigbrains.lsp.config.ZLSConfigProvider
|
import com.falsepattern.zigbrains.lsp.config.ZLSConfigProvider
|
||||||
|
import com.falsepattern.zigbrains.lsp.zls.ZLSService
|
||||||
import com.falsepattern.zigbrains.shared.cli.translateCommandline
|
import com.falsepattern.zigbrains.shared.cli.translateCommandline
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
|
|
||||||
class ZLSSettingsConfigProvider: ZLSConfigProvider {
|
class ZLSSettingsConfigProvider: ZLSConfigProvider {
|
||||||
override fun getEnvironment(project: Project, previous: ZLSConfig): ZLSConfig {
|
override fun getEnvironment(project: Project, previous: ZLSConfig): ZLSConfig {
|
||||||
val state = project.zlsSettings.state
|
val state = ZLSService.getInstance(project).zls?.settings ?: return previous
|
||||||
return previous.copy(
|
return previous.copy(
|
||||||
enable_snippets = state.enable_snippets,
|
enable_snippets = state.enable_snippets,
|
||||||
enable_argument_placeholders = state.enable_argument_placeholders,
|
enable_argument_placeholders = state.enable_argument_placeholders,
|
||||||
|
|
|
@ -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.lsp.settings
|
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
|
||||||
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 ZLSSettingsConfigurable(private val project: Project): SubConfigurable {
|
|
||||||
private var appSettingsComponent: ZLSSettingsPanel? = null
|
|
||||||
override fun createComponent(holder: ZigProjectConfigurationProvider.SettingsPanelHolder, panel: Panel): ZigProjectConfigurationProvider.SettingsPanel {
|
|
||||||
val settingsPanel = ZLSSettingsPanel(project).apply { attach(panel) }.also { Disposer.register(this, it) }
|
|
||||||
appSettingsComponent = settingsPanel
|
|
||||||
return settingsPanel
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun isModified(): Boolean {
|
|
||||||
val data = appSettingsComponent?.data ?: return false
|
|
||||||
return project.zlsSettings.state != data
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun apply() {
|
|
||||||
val data = appSettingsComponent?.data ?: return
|
|
||||||
val settings = project.zlsSettings
|
|
||||||
settings.state = data
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun reset() {
|
|
||||||
appSettingsComponent?.data = project.zlsSettings.state
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dispose() {
|
|
||||||
appSettingsComponent = null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,354 +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.lsp.settings
|
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.direnv.DirenvCmd
|
|
||||||
import com.falsepattern.zigbrains.direnv.Env
|
|
||||||
import com.falsepattern.zigbrains.direnv.emptyEnv
|
|
||||||
import com.falsepattern.zigbrains.direnv.getDirenv
|
|
||||||
import com.falsepattern.zigbrains.lsp.ZLSBundle
|
|
||||||
import com.falsepattern.zigbrains.lsp.config.SemanticTokens
|
|
||||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
|
||||||
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
|
|
||||||
import com.falsepattern.zigbrains.shared.cli.call
|
|
||||||
import com.falsepattern.zigbrains.shared.cli.createCommandLineSafe
|
|
||||||
import com.falsepattern.zigbrains.shared.coroutine.launchWithEDT
|
|
||||||
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
|
||||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
|
||||||
import com.intellij.execution.processTools.mapFlat
|
|
||||||
import com.intellij.openapi.application.ModalityState
|
|
||||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
|
||||||
import com.intellij.openapi.project.Project
|
|
||||||
import com.intellij.openapi.project.guessProjectDir
|
|
||||||
import com.intellij.openapi.ui.ComboBox
|
|
||||||
import com.intellij.openapi.util.Disposer
|
|
||||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
|
||||||
import com.intellij.openapi.vfs.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.fields.ExtendableTextField
|
|
||||||
import com.intellij.ui.components.textFieldWithBrowseButton
|
|
||||||
import com.intellij.ui.dsl.builder.AlignX
|
|
||||||
import com.intellij.ui.dsl.builder.Panel
|
|
||||||
import com.intellij.ui.dsl.builder.Row
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.jetbrains.annotations.PropertyKey
|
|
||||||
import javax.swing.event.DocumentEvent
|
|
||||||
import kotlin.io.path.pathString
|
|
||||||
|
|
||||||
@Suppress("PrivatePropertyName")
|
|
||||||
class ZLSSettingsPanel(private val project: Project) : ZigProjectConfigurationProvider.SettingsPanel {
|
|
||||||
private val zlsPath = textFieldWithBrowseButton(
|
|
||||||
project,
|
|
||||||
FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()
|
|
||||||
.withTitle(ZLSBundle.message("settings.zls-path.browse.title")),
|
|
||||||
).also {
|
|
||||||
it.textField.document.addDocumentListener(object: DocumentAdapter() {
|
|
||||||
override fun textChanged(p0: DocumentEvent) {
|
|
||||||
dispatchUpdateUI()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
Disposer.register(this, it)
|
|
||||||
}
|
|
||||||
private val zlsConfigPath = textFieldWithBrowseButton(
|
|
||||||
project,
|
|
||||||
FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()
|
|
||||||
.withTitle(ZLSBundle.message("settings.zls-config-path.browse.title"))
|
|
||||||
).also { Disposer.register(this, it) }
|
|
||||||
|
|
||||||
private val zlsVersion = JBTextArea().also { it.isEditable = false }
|
|
||||||
|
|
||||||
private var debounce: Job? = null
|
|
||||||
|
|
||||||
private var direnv: Boolean = project.zigProjectSettings.state.direnv
|
|
||||||
|
|
||||||
private val inlayHints = JBCheckBox()
|
|
||||||
private val enable_snippets = JBCheckBox()
|
|
||||||
private val enable_argument_placeholders = JBCheckBox()
|
|
||||||
private val completion_label_details = JBCheckBox()
|
|
||||||
private val enable_build_on_save = JBCheckBox()
|
|
||||||
private val build_on_save_args = ExtendableTextField()
|
|
||||||
private val semantic_tokens = ComboBox(SemanticTokens.entries.toTypedArray())
|
|
||||||
private val inlay_hints_show_variable_type_hints = JBCheckBox()
|
|
||||||
private val inlay_hints_show_struct_literal_field_type = JBCheckBox()
|
|
||||||
private val inlay_hints_show_parameter_name = JBCheckBox()
|
|
||||||
private val inlay_hints_show_builtin = JBCheckBox()
|
|
||||||
private val inlay_hints_exclude_single_argument = JBCheckBox()
|
|
||||||
private val inlay_hints_hide_redundant_param_names = JBCheckBox()
|
|
||||||
private val inlay_hints_hide_redundant_param_names_last_token = JBCheckBox()
|
|
||||||
private val warn_style = JBCheckBox()
|
|
||||||
private val highlight_global_var_declarations = JBCheckBox()
|
|
||||||
private val skip_std_references = JBCheckBox()
|
|
||||||
private val prefer_ast_check_as_child_process = JBCheckBox()
|
|
||||||
private val builtin_path = ExtendableTextField()
|
|
||||||
private val build_runner_path = ExtendableTextField()
|
|
||||||
private val global_cache_path = ExtendableTextField()
|
|
||||||
|
|
||||||
override fun attach(p: Panel) = with(p) {
|
|
||||||
if (!project.isDefault) {
|
|
||||||
group(ZLSBundle.message("settings.group.title")) {
|
|
||||||
fancyRow(
|
|
||||||
"settings.zls-path.label",
|
|
||||||
"settings.zls-path.tooltip"
|
|
||||||
) {
|
|
||||||
cell(zlsPath).resizableColumn().align(AlignX.FILL)
|
|
||||||
}
|
|
||||||
row(ZLSBundle.message("settings.zls-version.label")) {
|
|
||||||
cell(zlsVersion)
|
|
||||||
}
|
|
||||||
fancyRow(
|
|
||||||
"settings.zls-config-path.label",
|
|
||||||
"settings.zls-config-path.tooltip"
|
|
||||||
) { cell(zlsConfigPath).align(AlignX.FILL) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.enable_snippets.label",
|
|
||||||
"settings.enable_snippets.tooltip"
|
|
||||||
) { cell(enable_snippets) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.enable_argument_placeholders.label",
|
|
||||||
"settings.enable_argument_placeholders.tooltip"
|
|
||||||
) { cell(enable_argument_placeholders) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.completion_label_details.label",
|
|
||||||
"settings.completion_label_details.tooltip"
|
|
||||||
) { cell(completion_label_details) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.enable_build_on_save.label",
|
|
||||||
"settings.enable_build_on_save.tooltip"
|
|
||||||
) { cell(enable_build_on_save) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.build_on_save_args.label",
|
|
||||||
"settings.build_on_save_args.tooltip"
|
|
||||||
) { cell(build_on_save_args).resizableColumn().align(AlignX.FILL) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.semantic_tokens.label",
|
|
||||||
"settings.semantic_tokens.tooltip"
|
|
||||||
) { cell(semantic_tokens) }
|
|
||||||
group(ZLSBundle.message("settings.inlay-hints-group.label")) {
|
|
||||||
fancyRow(
|
|
||||||
"settings.inlay-hints-enable.label",
|
|
||||||
"settings.inlay-hints-enable.tooltip"
|
|
||||||
) { cell(inlayHints) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.inlay_hints_show_variable_type_hints.label",
|
|
||||||
"settings.inlay_hints_show_variable_type_hints.tooltip"
|
|
||||||
) { cell(inlay_hints_show_variable_type_hints) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.inlay_hints_show_struct_literal_field_type.label",
|
|
||||||
"settings.inlay_hints_show_struct_literal_field_type.tooltip"
|
|
||||||
) { cell(inlay_hints_show_struct_literal_field_type) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.inlay_hints_show_parameter_name.label",
|
|
||||||
"settings.inlay_hints_show_parameter_name.tooltip"
|
|
||||||
) { cell(inlay_hints_show_parameter_name) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.inlay_hints_show_builtin.label",
|
|
||||||
"settings.inlay_hints_show_builtin.tooltip"
|
|
||||||
) { cell(inlay_hints_show_builtin) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.inlay_hints_exclude_single_argument.label",
|
|
||||||
"settings.inlay_hints_exclude_single_argument.tooltip"
|
|
||||||
) { cell(inlay_hints_exclude_single_argument) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.inlay_hints_hide_redundant_param_names.label",
|
|
||||||
"settings.inlay_hints_hide_redundant_param_names.tooltip"
|
|
||||||
) { cell(inlay_hints_hide_redundant_param_names) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.inlay_hints_hide_redundant_param_names_last_token.label",
|
|
||||||
"settings.inlay_hints_hide_redundant_param_names_last_token.tooltip"
|
|
||||||
) { cell(inlay_hints_hide_redundant_param_names_last_token) }
|
|
||||||
}
|
|
||||||
fancyRow(
|
|
||||||
"settings.warn_style.label",
|
|
||||||
"settings.warn_style.tooltip"
|
|
||||||
) { cell(warn_style) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.highlight_global_var_declarations.label",
|
|
||||||
"settings.highlight_global_var_declarations.tooltip"
|
|
||||||
) { cell(highlight_global_var_declarations) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.skip_std_references.label",
|
|
||||||
"settings.skip_std_references.tooltip"
|
|
||||||
) { cell(skip_std_references) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.prefer_ast_check_as_child_process.label",
|
|
||||||
"settings.prefer_ast_check_as_child_process.tooltip"
|
|
||||||
) { cell(prefer_ast_check_as_child_process) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.builtin_path.label",
|
|
||||||
"settings.builtin_path.tooltip"
|
|
||||||
) { cell(builtin_path).resizableColumn().align(AlignX.FILL) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.build_runner_path.label",
|
|
||||||
"settings.build_runner_path.tooltip"
|
|
||||||
) { cell(build_runner_path).resizableColumn().align(AlignX.FILL) }
|
|
||||||
fancyRow(
|
|
||||||
"settings.global_cache_path.label",
|
|
||||||
"settings.global_cache_path.tooltip"
|
|
||||||
) { cell(global_cache_path).resizableColumn().align(AlignX.FILL) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dispatchAutodetect(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun direnvChanged(state: Boolean) {
|
|
||||||
direnv = state
|
|
||||||
dispatchAutodetect(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override var data
|
|
||||||
get() = if (project.isDefault) ZLSSettings() else ZLSSettings(
|
|
||||||
zlsPath.text,
|
|
||||||
zlsConfigPath.text,
|
|
||||||
inlayHints.isSelected,
|
|
||||||
enable_snippets.isSelected,
|
|
||||||
enable_argument_placeholders.isSelected,
|
|
||||||
completion_label_details.isSelected,
|
|
||||||
enable_build_on_save.isSelected,
|
|
||||||
build_on_save_args.text,
|
|
||||||
semantic_tokens.item ?: SemanticTokens.full,
|
|
||||||
inlay_hints_show_variable_type_hints.isSelected,
|
|
||||||
inlay_hints_show_struct_literal_field_type.isSelected,
|
|
||||||
inlay_hints_show_parameter_name.isSelected,
|
|
||||||
inlay_hints_show_builtin.isSelected,
|
|
||||||
inlay_hints_exclude_single_argument.isSelected,
|
|
||||||
inlay_hints_hide_redundant_param_names.isSelected,
|
|
||||||
inlay_hints_hide_redundant_param_names_last_token.isSelected,
|
|
||||||
warn_style.isSelected,
|
|
||||||
highlight_global_var_declarations.isSelected,
|
|
||||||
skip_std_references.isSelected,
|
|
||||||
prefer_ast_check_as_child_process.isSelected,
|
|
||||||
builtin_path.text?.ifBlank { null },
|
|
||||||
build_runner_path.text?.ifBlank { null },
|
|
||||||
global_cache_path.text?.ifBlank { null },
|
|
||||||
)
|
|
||||||
set(value) {
|
|
||||||
zlsPath.text = value.zlsPath
|
|
||||||
zlsConfigPath.text = value.zlsConfigPath
|
|
||||||
inlayHints.isSelected = value.inlayHints
|
|
||||||
enable_snippets.isSelected = value.enable_snippets
|
|
||||||
enable_argument_placeholders.isSelected = value.enable_argument_placeholders
|
|
||||||
completion_label_details.isSelected = value.completion_label_details
|
|
||||||
enable_build_on_save.isSelected = value.enable_build_on_save
|
|
||||||
build_on_save_args.text = value.build_on_save_args
|
|
||||||
semantic_tokens.item = value.semantic_tokens
|
|
||||||
inlay_hints_show_variable_type_hints.isSelected = value.inlay_hints_show_variable_type_hints
|
|
||||||
inlay_hints_show_struct_literal_field_type.isSelected = value.inlay_hints_show_struct_literal_field_type
|
|
||||||
inlay_hints_show_parameter_name.isSelected = value.inlay_hints_show_parameter_name
|
|
||||||
inlay_hints_show_builtin.isSelected = value.inlay_hints_show_builtin
|
|
||||||
inlay_hints_exclude_single_argument.isSelected = value.inlay_hints_exclude_single_argument
|
|
||||||
inlay_hints_hide_redundant_param_names.isSelected = value.inlay_hints_hide_redundant_param_names
|
|
||||||
inlay_hints_hide_redundant_param_names_last_token.isSelected =
|
|
||||||
value.inlay_hints_hide_redundant_param_names_last_token
|
|
||||||
warn_style.isSelected = value.warn_style
|
|
||||||
highlight_global_var_declarations.isSelected = value.highlight_global_var_declarations
|
|
||||||
skip_std_references.isSelected = value.skip_std_references
|
|
||||||
prefer_ast_check_as_child_process.isSelected = value.prefer_ast_check_as_child_process
|
|
||||||
builtin_path.text = value.builtin_path ?: ""
|
|
||||||
build_runner_path.text = value.build_runner_path ?: ""
|
|
||||||
global_cache_path.text = value.global_cache_path ?: ""
|
|
||||||
dispatchUpdateUI()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun dispatchAutodetect(force: Boolean) {
|
|
||||||
project.zigCoroutineScope.launchWithEDT(ModalityState.defaultModalityState()) {
|
|
||||||
withModalProgress(ModalTaskOwner.component(zlsPath), "Detecting ZLS...", TaskCancellation.cancellable()) {
|
|
||||||
autodetect(force)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun autodetect(force: Boolean) {
|
|
||||||
if (force || zlsPath.text.isBlank()) {
|
|
||||||
getDirenv().findExecutableOnPATH("zls")?.let {
|
|
||||||
if (force || zlsPath.text.isBlank()) {
|
|
||||||
zlsPath.text = it.pathString
|
|
||||||
dispatchUpdateUI()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dispose() {
|
|
||||||
debounce?.cancel("Disposed")
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getDirenv(): Env {
|
|
||||||
if (!project.isDefault && DirenvCmd.direnvInstalled() && direnv)
|
|
||||||
return project.getDirenv()
|
|
||||||
return emptyEnv
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun dispatchUpdateUI() {
|
|
||||||
debounce?.cancel("New debounce")
|
|
||||||
debounce = project.zigCoroutineScope.launch {
|
|
||||||
updateUI()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun updateUI() {
|
|
||||||
if (project.isDefault)
|
|
||||||
return
|
|
||||||
delay(200)
|
|
||||||
val zlsPath = this.zlsPath.text.ifBlank { null }?.toNioPathOrNull()
|
|
||||||
if (zlsPath == null) {
|
|
||||||
withEDTContext(ModalityState.any()) {
|
|
||||||
zlsVersion.text = "[zls path empty or invalid]"
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val workingDir = project.guessProjectDir()?.toNioPathOrNull()
|
|
||||||
val result = createCommandLineSafe(workingDir, zlsPath, "version")
|
|
||||||
.map { it.withEnvironment(getDirenv().env) }
|
|
||||||
.mapFlat { it.call() }
|
|
||||||
.getOrElse { throwable ->
|
|
||||||
throwable.printStackTrace()
|
|
||||||
withEDTContext(ModalityState.any()) {
|
|
||||||
zlsVersion.text = "[failed to run \"zls version\"]\n${throwable.message}"
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val version = result.stdout.trim()
|
|
||||||
withEDTContext(ModalityState.any()) {
|
|
||||||
zlsVersion.text = version
|
|
||||||
zlsVersion.foreground = JBColor.foreground()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Panel.fancyRow(
|
|
||||||
label: @PropertyKey(resourceBundle = "zigbrains.lsp.Bundle") String,
|
|
||||||
tooltip: @PropertyKey(resourceBundle = "zigbrains.lsp.Bundle") String,
|
|
||||||
cb: Row.() -> Unit
|
|
||||||
) = row(ZLSBundle.message(label)) {
|
|
||||||
contextHelp(ZLSBundle.message(tooltip))
|
|
||||||
cb()
|
|
||||||
}
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
* 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.lsp.zls
|
||||||
|
|
||||||
|
import com.intellij.openapi.ui.NamedConfigurable
|
||||||
|
import com.intellij.openapi.util.NlsContexts
|
||||||
|
import com.intellij.openapi.util.NlsSafe
|
||||||
|
import com.intellij.ui.dsl.builder.panel
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.swing.JComponent
|
||||||
|
|
||||||
|
class ZLSConfigurable(val uuid: UUID, zls: ZLSVersion): NamedConfigurable<UUID>() {
|
||||||
|
var zls: ZLSVersion = zls
|
||||||
|
set(value) {
|
||||||
|
zlsInstallations[uuid] = value
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
private var myView: ZLSPanel? = null
|
||||||
|
|
||||||
|
override fun setDisplayName(name: String?) {
|
||||||
|
zls = zls.copy(name = name)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getEditableObject(): UUID? {
|
||||||
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getBannerSlogan(): @NlsContexts.DetailedDescription String? {
|
||||||
|
return displayName
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createOptionsPanel(): JComponent? {
|
||||||
|
var view = myView
|
||||||
|
if (view == null) {
|
||||||
|
view = ZLSPanel()
|
||||||
|
view.reset(zls)
|
||||||
|
myView = view
|
||||||
|
}
|
||||||
|
return panel {
|
||||||
|
view.attach(this@panel)
|
||||||
|
}.withMaximumWidth(20)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDisplayName(): @NlsContexts.ConfigurableName String? {
|
||||||
|
return zls.name
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isModified(): Boolean {
|
||||||
|
return myView?.isModified(zls) == true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apply() {
|
||||||
|
myView?.apply(zls)?.let { zls = it }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reset() {
|
||||||
|
myView?.reset(zls)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun disposeUIResources() {
|
||||||
|
myView?.dispose()
|
||||||
|
myView = null
|
||||||
|
super.disposeUIResources()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
* 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.lsp.zls
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.lsp.zls.ZLSInstallationsService.MyState
|
||||||
|
import com.falsepattern.zigbrains.shared.UUIDMapSerializable
|
||||||
|
import com.falsepattern.zigbrains.shared.UUIDStorage
|
||||||
|
import com.intellij.openapi.components.*
|
||||||
|
|
||||||
|
@Service(Service.Level.APP)
|
||||||
|
@State(
|
||||||
|
name = "ZLSInstallations",
|
||||||
|
storages = [Storage("zigbrains.xml")]
|
||||||
|
)
|
||||||
|
class ZLSInstallationsService: UUIDMapSerializable.Converting<ZLSVersion, ZLSVersion.Ref, MyState>(MyState()) {
|
||||||
|
override fun serialize(value: ZLSVersion) = value.toRef()
|
||||||
|
override fun deserialize(value: ZLSVersion.Ref) = value.resolve()
|
||||||
|
override fun getStorage(state: MyState) = state.zlsInstallations
|
||||||
|
override fun updateStorage(state: MyState, storage: ZLSStorage) = state.copy(zlsInstallations = storage)
|
||||||
|
|
||||||
|
data class MyState(@JvmField val zlsInstallations: ZLSStorage = emptyMap())
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun getInstance(): ZLSInstallationsService = service<ZLSInstallationsService>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline val zlsInstallations: ZLSInstallationsService get() = ZLSInstallationsService.getInstance()
|
||||||
|
|
||||||
|
private typealias ZLSStorage = UUIDStorage<ZLSVersion.Ref>
|
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
* 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.lsp.zls
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
|
||||||
|
import com.falsepattern.zigbrains.project.toolchain.ui.ImmutableNamedElementPanelBase
|
||||||
|
import com.falsepattern.zigbrains.shared.cli.call
|
||||||
|
import com.falsepattern.zigbrains.shared.cli.createCommandLineSafe
|
||||||
|
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.util.Disposer
|
||||||
|
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||||
|
import com.intellij.ui.DocumentAdapter
|
||||||
|
import com.intellij.ui.JBColor
|
||||||
|
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 ZLSPanel() : ImmutableNamedElementPanelBase<ZLSVersion>() {
|
||||||
|
private val pathToZLS = textFieldWithBrowseButton(
|
||||||
|
null,
|
||||||
|
FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor().withTitle("Path to the zls executable")
|
||||||
|
).also {
|
||||||
|
it.textField.document.addDocumentListener(object : DocumentAdapter() {
|
||||||
|
override fun textChanged(e: DocumentEvent) {
|
||||||
|
dispatchUpdateUI()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Disposer.register(this, it)
|
||||||
|
}
|
||||||
|
private val zlsVersion = JBTextArea().also { it.isEditable = false }
|
||||||
|
private var debounce: Job? = null
|
||||||
|
|
||||||
|
override fun attach(p: Panel): Unit = with(p) {
|
||||||
|
super.attach(p)
|
||||||
|
row("Path:") {
|
||||||
|
cell(pathToZLS).resizableColumn().align(AlignX.FILL)
|
||||||
|
}
|
||||||
|
row("Version:") {
|
||||||
|
cell(zlsVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isModified(version: ZLSVersion): Boolean {
|
||||||
|
val name = nameFieldValue ?: return false
|
||||||
|
val path = this.pathToZLS.text.ifBlank { null }?.toNioPathOrNull() ?: return false
|
||||||
|
return name != version.name || version.path != path
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apply(version: ZLSVersion): ZLSVersion? {
|
||||||
|
val path = this.pathToZLS.text.ifBlank { null }?.toNioPathOrNull() ?: return null
|
||||||
|
return version.copy(path = path, name = nameFieldValue ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reset(version: ZLSVersion) {
|
||||||
|
nameFieldValue = version.name
|
||||||
|
this.pathToZLS.text = version.path.pathString
|
||||||
|
dispatchUpdateUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dispatchUpdateUI() {
|
||||||
|
debounce?.cancel("New debounce")
|
||||||
|
debounce = zigCoroutineScope.launch {
|
||||||
|
updateUI()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun updateUI() {
|
||||||
|
delay(200)
|
||||||
|
val pathToZLS = this.pathToZLS.text.ifBlank { null }?.toNioPathOrNull()
|
||||||
|
if (pathToZLS == null) {
|
||||||
|
withEDTContext(ModalityState.any()) {
|
||||||
|
zlsVersion.text = "[zls path empty or invalid]"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val versionCommand = createCommandLineSafe(null, pathToZLS, "--version").getOrElse {
|
||||||
|
it.printStackTrace()
|
||||||
|
withEDTContext(ModalityState.any()) {
|
||||||
|
zlsVersion.text = "[could not create \"zls --version\" command]\n${it.message}"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val result = versionCommand.call().getOrElse {
|
||||||
|
it.printStackTrace()
|
||||||
|
withEDTContext(ModalityState.any()) {
|
||||||
|
zlsVersion.text = "[failed to run \"zls --version\"]\n${it.message}"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val version = result.stdout.trim()
|
||||||
|
|
||||||
|
withEDTContext(ModalityState.any()) {
|
||||||
|
zlsVersion.text = version
|
||||||
|
zlsVersion.foreground = JBColor.foreground()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
debounce?.cancel("Disposed")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* 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.lsp.zls
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
||||||
|
import com.intellij.openapi.components.*
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.util.xmlb.annotations.Attribute
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Service(Service.Level.PROJECT)
|
||||||
|
@State(
|
||||||
|
name = "ZLS",
|
||||||
|
storages = [Storage("zigbrains.xml")]
|
||||||
|
)
|
||||||
|
class ZLSService: SerializablePersistentStateComponent<ZLSService.MyState>(MyState()) {
|
||||||
|
var zlsUUID: UUID?
|
||||||
|
get() = state.zls.ifBlank { null }?.let { UUID.fromString(it) }?.takeIf {
|
||||||
|
if (it in zigToolchainList) {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
updateState {
|
||||||
|
it.copy(zls = "")
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
updateState {
|
||||||
|
it.copy(zls = value?.toString() ?: "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val zls: ZLSVersion?
|
||||||
|
get() = zlsUUID?.let { zlsInstallations[it] }
|
||||||
|
|
||||||
|
data class MyState(@JvmField @Attribute var zls: String = "")
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun getInstance(project: Project): ZLSService = project.service<ZLSService>()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* 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.lsp.zls
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.lsp.settings.ZLSSettings
|
||||||
|
import com.falsepattern.zigbrains.shared.NamedObject
|
||||||
|
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||||
|
import java.nio.file.Path
|
||||||
|
import com.intellij.util.xmlb.annotations.Attribute
|
||||||
|
import kotlin.io.path.isExecutable
|
||||||
|
import kotlin.io.path.isRegularFile
|
||||||
|
import kotlin.io.path.pathString
|
||||||
|
|
||||||
|
data class ZLSVersion(val path: Path, override val name: String?, val settings: ZLSSettings): NamedObject<ZLSVersion> {
|
||||||
|
override fun withName(newName: String?): ZLSVersion {
|
||||||
|
return copy(name = newName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toRef(): Ref {
|
||||||
|
return Ref(path.pathString, name, settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isValid(): Boolean {
|
||||||
|
if (!path.toFile().exists())
|
||||||
|
return false
|
||||||
|
if (!path.isRegularFile() || !path.isExecutable())
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Ref(
|
||||||
|
@JvmField
|
||||||
|
@Attribute
|
||||||
|
val path: String? = "",
|
||||||
|
@JvmField
|
||||||
|
@Attribute
|
||||||
|
val name: String? = "",
|
||||||
|
@JvmField
|
||||||
|
val settings: ZLSSettings = ZLSSettings()
|
||||||
|
) {
|
||||||
|
fun resolve(): ZLSVersion? {
|
||||||
|
return path?.ifBlank { null }?.toNioPathOrNull()?.let { ZLSVersion(it, name, settings) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
/*
|
||||||
|
* 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.lsp.zls.ui
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.lsp.zls.ZLSConfigurable
|
||||||
|
import com.falsepattern.zigbrains.lsp.zls.ZLSVersion
|
||||||
|
import com.falsepattern.zigbrains.lsp.zls.zlsInstallations
|
||||||
|
import com.falsepattern.zigbrains.shared.UUIDMapSerializable
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ListElem
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ListElemIn
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.UUIDComboBoxDriver
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ZBComboBox
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ZBContext
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ZBModel
|
||||||
|
import com.intellij.openapi.ui.NamedConfigurable
|
||||||
|
import java.awt.Component
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
object ZLSDriver: UUIDComboBoxDriver<ZLSVersion> {
|
||||||
|
override val theMap: UUIDMapSerializable.Converting<ZLSVersion, *, *>
|
||||||
|
get() = zlsInstallations
|
||||||
|
|
||||||
|
override fun constructModelList(): List<ListElemIn<ZLSVersion>> {
|
||||||
|
return ListElem.fetchGroup()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createContext(model: ZBModel<ZLSVersion>): ZBContext<ZLSVersion> {
|
||||||
|
return ZLSContext(null, model)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createComboBox(model: ZBModel<ZLSVersion>): ZBComboBox<ZLSVersion> {
|
||||||
|
return ZLSComboBox(model)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun resolvePseudo(
|
||||||
|
context: Component,
|
||||||
|
elem: ListElem.Pseudo<ZLSVersion>
|
||||||
|
): UUID? {
|
||||||
|
//TODO
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createNamedConfigurable(uuid: UUID, elem: ZLSVersion): NamedConfigurable<UUID> {
|
||||||
|
//TODO
|
||||||
|
return ZLSConfigurable(uuid, elem)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
* This file is part of ZigBrains.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2023-2025 FalsePattern
|
||||||
|
* All Rights Reserved
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included
|
||||||
|
* in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* ZigBrains is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, only version 3 of the License.
|
||||||
|
*
|
||||||
|
* ZigBrains is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.falsepattern.zigbrains.lsp.zls.ui
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.lsp.zls.ZLSService
|
||||||
|
import com.falsepattern.zigbrains.lsp.zls.ZLSVersion
|
||||||
|
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
||||||
|
import com.falsepattern.zigbrains.shared.SubConfigurable
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.UUIDMapEditor
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.UUIDMapSelector
|
||||||
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.project.ProjectManager
|
||||||
|
import com.intellij.openapi.util.Key
|
||||||
|
import com.intellij.ui.dsl.builder.Panel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class ZLSEditor(private var project: Project?,
|
||||||
|
private val sharedState: ZigProjectConfigurationProvider.IUserDataBridge):
|
||||||
|
UUIDMapSelector<ZLSVersion>(ZLSDriver),
|
||||||
|
SubConfigurable<Project>,
|
||||||
|
ZigProjectConfigurationProvider.UserDataListener
|
||||||
|
{
|
||||||
|
init {
|
||||||
|
sharedState.addUserDataChangeListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onUserDataChanged(key: Key<*>) {
|
||||||
|
zigCoroutineScope.launch { listChanged() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun attach(panel: Panel): Unit = with(panel) {
|
||||||
|
row("ZLS") {
|
||||||
|
attachComboBoxRow(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isModified(context: Project): Boolean {
|
||||||
|
return ZLSService.getInstance(context).zlsUUID != selectedUUID
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun apply(context: Project) {
|
||||||
|
ZLSService.getInstance(context).zlsUUID = selectedUUID
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun reset(context: Project?) {
|
||||||
|
val project = context ?: ProjectManager.getInstance().defaultProject
|
||||||
|
selectedUUID = ZLSService.getInstance(project).zlsUUID
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispose() {
|
||||||
|
super.dispose()
|
||||||
|
sharedState.removeUserDataChangeListener(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val newProjectBeforeInitSelector: Boolean get() = true
|
||||||
|
class Provider: ZigProjectConfigurationProvider {
|
||||||
|
override fun create(
|
||||||
|
project: Project?,
|
||||||
|
sharedState: ZigProjectConfigurationProvider.IUserDataBridge
|
||||||
|
): SubConfigurable<Project>? {
|
||||||
|
return ZLSEditor(project, sharedState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val index: Int
|
||||||
|
get() = 50
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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.lsp.zls.ui
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.lsp.ZLSBundle
|
||||||
|
import com.falsepattern.zigbrains.lsp.zls.ZLSVersion
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.UUIDMapEditor
|
||||||
|
import com.intellij.openapi.ui.MasterDetailsComponent
|
||||||
|
import com.intellij.openapi.util.NlsContexts
|
||||||
|
|
||||||
|
class ZLSListEditor : UUIDMapEditor<ZLSVersion>(ZLSDriver) {
|
||||||
|
override fun getEmptySelectionString(): String {
|
||||||
|
return ZLSBundle.message("settings.list.empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDisplayName(): @NlsContexts.ConfigurableName String? {
|
||||||
|
return ZLSBundle.message("settings.list.title")
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
* This file is part of ZigBrains.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2023-2025 FalsePattern
|
||||||
|
* All Rights Reserved
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included
|
||||||
|
* in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* ZigBrains is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
* the Free Software Foundation, only version 3 of the License.
|
||||||
|
*
|
||||||
|
* ZigBrains is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.falsepattern.zigbrains.lsp.zls.ui
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.Icons
|
||||||
|
import com.falsepattern.zigbrains.lsp.ZLSBundle
|
||||||
|
import com.falsepattern.zigbrains.lsp.zls.ZLSVersion
|
||||||
|
import com.falsepattern.zigbrains.project.toolchain.base.render
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ListElem
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ZBCellRenderer
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ZBComboBox
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ZBContext
|
||||||
|
import com.falsepattern.zigbrains.shared.ui.ZBModel
|
||||||
|
import com.intellij.icons.AllIcons
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.ui.SimpleTextAttributes
|
||||||
|
import com.intellij.ui.icons.EMPTY_ICON
|
||||||
|
import javax.swing.JList
|
||||||
|
import kotlin.io.path.pathString
|
||||||
|
|
||||||
|
|
||||||
|
class ZLSComboBox(model: ZBModel<ZLSVersion>): ZBComboBox<ZLSVersion>(model, ::ZLSCellRenderer)
|
||||||
|
|
||||||
|
class ZLSContext(project: Project?, model: ZBModel<ZLSVersion>): ZBContext<ZLSVersion>(project, model, ::ZLSCellRenderer)
|
||||||
|
|
||||||
|
class ZLSCellRenderer(getModel: () -> ZBModel<ZLSVersion>): ZBCellRenderer<ZLSVersion>(getModel) {
|
||||||
|
override fun customizeCellRenderer(
|
||||||
|
list: JList<out ListElem<ZLSVersion>?>,
|
||||||
|
value: ListElem<ZLSVersion>?,
|
||||||
|
index: Int,
|
||||||
|
selected: Boolean,
|
||||||
|
hasFocus: Boolean
|
||||||
|
) {
|
||||||
|
icon = EMPTY_ICON
|
||||||
|
when (value) {
|
||||||
|
is ListElem.One -> {
|
||||||
|
val (icon, isSuggestion) = when(value) {
|
||||||
|
is ListElem.One.Suggested -> AllIcons.General.Information to true
|
||||||
|
is ListElem.One.Actual -> Icons.Zig to false
|
||||||
|
}
|
||||||
|
this.icon = icon
|
||||||
|
val item = value.instance
|
||||||
|
//TODO proper renderer
|
||||||
|
if (item.name != null) {
|
||||||
|
append(item.name)
|
||||||
|
append(item.path.pathString, SimpleTextAttributes.GRAYED_ATTRIBUTES)
|
||||||
|
} else {
|
||||||
|
append(item.path.pathString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is ListElem.Download -> {
|
||||||
|
icon = AllIcons.Actions.Download
|
||||||
|
append(ZLSBundle.message("settings.model.download.text"))
|
||||||
|
}
|
||||||
|
|
||||||
|
is ListElem.FromDisk -> {
|
||||||
|
icon = AllIcons.General.OpenDisk
|
||||||
|
append(ZLSBundle.message("settings.model.from-disk.text"))
|
||||||
|
}
|
||||||
|
is ListElem.Pending -> {
|
||||||
|
icon = AllIcons.Empty
|
||||||
|
append(ZLSBundle.message("settings.model.loading.text"), SimpleTextAttributes.GRAYED_ATTRIBUTES)
|
||||||
|
}
|
||||||
|
is ListElem.None, null -> {
|
||||||
|
icon = AllIcons.General.BalloonError
|
||||||
|
append(ZLSBundle.message("settings.model.none.text"), SimpleTextAttributes.ERROR_ATTRIBUTES)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -49,6 +49,13 @@
|
||||||
<editorNotificationProvider
|
<editorNotificationProvider
|
||||||
implementation="com.falsepattern.zigbrains.lsp.notification.ZigEditorNotificationProvider"
|
implementation="com.falsepattern.zigbrains.lsp.notification.ZigEditorNotificationProvider"
|
||||||
/>
|
/>
|
||||||
|
<applicationConfigurable
|
||||||
|
parentId="ZigConfigurable"
|
||||||
|
instance="com.falsepattern.zigbrains.lsp.zls.ui.ZLSListEditor"
|
||||||
|
id="ZLSListEditor"
|
||||||
|
bundle="zigbrains.lsp.Bundle"
|
||||||
|
key="settings.list.title"
|
||||||
|
/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="com.falsepattern.zigbrains">
|
<extensions defaultExtensionNs="com.falsepattern.zigbrains">
|
||||||
|
@ -59,7 +66,7 @@
|
||||||
implementation="com.falsepattern.zigbrains.lsp.ToolchainZLSConfigProvider"
|
implementation="com.falsepattern.zigbrains.lsp.ToolchainZLSConfigProvider"
|
||||||
/>
|
/>
|
||||||
<projectConfigProvider
|
<projectConfigProvider
|
||||||
implementation="com.falsepattern.zigbrains.lsp.ZLSProjectConfigurationProvider"
|
implementation="com.falsepattern.zigbrains.lsp.zls.ui.ZLSEditor$Provider"
|
||||||
/>
|
/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
|
|
|
@ -66,3 +66,10 @@ progress.title.validate=Validating ZLS
|
||||||
lsp.zls.name=Zig Language Server
|
lsp.zls.name=Zig Language Server
|
||||||
# suppress inspection "UnusedProperty"
|
# suppress inspection "UnusedProperty"
|
||||||
lsp.zls.description=The <a href="https://github.com/Zigtools/ZLS">Zig Language Server</a>, via ZigBrains
|
lsp.zls.description=The <a href="https://github.com/Zigtools/ZLS">Zig Language Server</a>, via ZigBrains
|
||||||
|
settings.list.title=ZLS Instances
|
||||||
|
settings.list.empty=Select a ZLS version to view or edit its details here
|
||||||
|
settings.model.detected.separator=Detected ZLS version
|
||||||
|
settings.model.none.text=<No ZLS>
|
||||||
|
settings.model.loading.text=Loading\u2026
|
||||||
|
settings.model.from-disk.text=Add ZLS from disk\u2026
|
||||||
|
settings.model.download.text=Download ZLS\u2026
|
|
@ -17,6 +17,11 @@
|
||||||
dynamic="true"
|
dynamic="true"
|
||||||
name="zlsConfigProvider"
|
name="zlsConfigProvider"
|
||||||
/>
|
/>
|
||||||
|
<extensionPoint
|
||||||
|
interface="com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainExtensionsProvider"
|
||||||
|
dynamic="true"
|
||||||
|
name="toolchainExtensionsProvider"
|
||||||
|
/>
|
||||||
<extensionPoint
|
<extensionPoint
|
||||||
interface="com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider"
|
interface="com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider"
|
||||||
dynamic="true"
|
dynamic="true"
|
||||||
|
|
Loading…
Add table
Reference in a new issue