From 0162e53b01dbb52b468e7614225a1ee69760fded Mon Sep 17 00:00:00 2001 From: FalsePattern Date: Tue, 11 Mar 2025 14:29:48 +0100 Subject: [PATCH] backport: 22.0.0 --- CHANGELOG.md | 25 +++ CODE_OF_CONDUCT.md | 53 ------ .../execution/binary/ZigExecConfigBinary.kt | 2 +- .../runner/binary/ZigDebugParametersBinary.kt | 2 +- .../runner/build/ZigDebugParametersBuild.kt | 2 +- .../runner/run/ZigDebugParametersRun.kt | 2 +- .../resources/META-INF/zigbrains-debugger.xml | 2 +- .../project/execution/base/Configuration.kt | 14 +- .../execution/build/ZigExecConfigBuild.kt | 7 +- .../project/execution/run/ZigExecConfigRun.kt | 4 +- .../execution/test/ZigExecConfigTest.kt | 2 +- .../newproject/ZigProjectConfigurationData.kt | 5 +- .../settings/ZigProjectSettingsPanel.kt | 48 +++--- .../discovery/ZigStepDiscoveryService.kt | 7 +- .../steps/ui/BuildToolWindowContext.kt | 4 +- .../toolchain/stdlib/ZigSyntheticLibrary.kt | 4 +- .../toolchain/tools/ZigCompilerTool.kt | 9 +- .../project/toolchain/tools/ZigTool.kt | 42 +---- .../zigbrains/shared/cli/CLIUtil.kt | 47 +++++ .../resources/zigbrains/Bundle.properties | 2 +- gradle.properties | 2 +- .../falsepattern/zigbrains/lsp/ZLSStartup.kt | 15 ++ .../zigbrains/lsp/ZigLanguageServerFactory.kt | 41 ++++- .../ZigEditorNotificationProvider.kt | 64 +++++++ .../lsp/settings/ZLSProjectSettingsService.kt | 47 +++-- .../zigbrains/lsp/settings/ZLSSettings.kt | 2 +- .../lsp/settings/ZLSSettingsConfigProvider.kt | 10 +- .../lsp/settings/ZLSSettingsConfigurable.kt | 4 - .../lsp/settings/ZLSSettingsPanel.kt | 162 ++++++++---------- .../toolchain/ToolchainZLSConfigProvider.kt | 7 +- .../main/resources/META-INF/zigbrains-lsp.xml | 4 + .../resources/zigbrains/lsp/Bundle.properties | 5 +- src/main/resources/META-INF/plugin.xml | 2 +- 33 files changed, 378 insertions(+), 270 deletions(-) delete mode 100644 CODE_OF_CONDUCT.md create mode 100644 lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/notification/ZigEditorNotificationProvider.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 76b0f6f7..4106120d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,31 @@ Changelog structure reference: ## [Unreleased] +## [22.0.0] + +### Added + +- LSP + - Error/Warning banner at the top of the editor when ZLS is misconfigured/not running + - ZLS version indicator in the zig settings + +- Toolchain + - More descriptive error messages when toolchain detection fails + +### Changed + +- Project + - !!BREAKING CHANGE!! Changed file format of zig tasks to store command line arguments as strings instead of string lists. +This (and newer) versions of the plugin will automatically upgrade tasks from 21.1.0 and before. + +### Fixed + +- Debugging + - Breakpoints could not be placed inside zig code in Android Studio + +- Project + - Zig run/debug configuration command line arguments would lose quotes around arguments + ## [21.1.0] ### Added diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 89090b12..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,53 +0,0 @@ -# Code of Merit - -1. The project creators, lead developers, core team, constitute - the managing members of the project and have final say in every decision - of the project, technical or otherwise, including overruling previous decisions. - There are no limitations to this decisional power. - -2. Contributions are an expected result of your membership on the project. - Don't expect others to do your work or help you with your work forever. - -3. All members have the same opportunities to seek any challenge they want - within the project. - -4. Authority or position in the project will be proportional - to the accrued contribution. Seniority must be earned. - -5. Software is evolutive: the better implementations must supersede lesser - implementations. Technical advantage is the primary evaluation metric. - -6. This is a space for technical prowess; topics outside of the project - will not be tolerated. - -7. Non technical conflicts will be discussed in a separate space. Disruption - of the project will not be allowed. - -8. Individual characteristics, including but not limited to, - body, sex, sexual preference, race, language, religion, nationality, - or political preferences are irrelevant in the scope of the project and - will not be taken into account concerning your value or that of your contribution - to the project. - -9. Discuss or debate the idea, not the person. - -10. There is no room for ambiguity: Ambiguity will be met with questioning; - further ambiguity will be met with silence. It is the responsibility - of the originator to provide requested context. - -11. If something is illegal outside the scope of the project, it is illegal - in the scope of the project. This Code of Merit does not take precedence over - governing law. - -12. This Code of Merit governs the technical procedures of the project not the - activities outside of it. - -13. Participation on the project equates to agreement of this Code of Merit. - -14. No objectives beyond the stated objectives of this project are relevant - to the project. Any intent to deviate the project from its original purpose - of existence will constitute grounds for remedial action which may include - expulsion from the project. - -This document is adapted from the Code of Merit, version 1.0. -See: https://codeofmerit.org/. diff --git a/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/execution/binary/ZigExecConfigBinary.kt b/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/execution/binary/ZigExecConfigBinary.kt index b7beed30..f6da872e 100644 --- a/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/execution/binary/ZigExecConfigBinary.kt +++ b/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/execution/binary/ZigExecConfigBinary.kt @@ -39,7 +39,7 @@ class ZigExecConfigBinary(project: Project, factory: ConfigurationFactory) : Zig get() = ZigDebugBundle.message("configuration.binary.suggested-name") override suspend fun buildCommandLineArgs(debug: Boolean): List { - return args.args + return args.argsSplit() } override fun getConfigurables(): List> { diff --git a/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/binary/ZigDebugParametersBinary.kt b/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/binary/ZigDebugParametersBinary.kt index f253d691..95620485 100644 --- a/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/binary/ZigDebugParametersBinary.kt +++ b/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/binary/ZigDebugParametersBinary.kt @@ -36,6 +36,6 @@ class ZigDebugParametersBinary @Throws(ExecutionException::class) constructor(dr ZigDebugParametersBase(driverConfiguration, toolchain, profileState) { private val executableFile = profileState.configuration.exePath.path?.toFile() ?: throw ExecutionException(ZigDebugBundle.message("exception.missing-exe-path")) override fun getInstaller(): Installer { - return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, profileState.configuration.args.args) + return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, profileState.configuration.args.argsSplit()) } } \ No newline at end of file diff --git a/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/build/ZigDebugParametersBuild.kt b/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/build/ZigDebugParametersBuild.kt index a0a3ff76..b8b31068 100644 --- a/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/build/ZigDebugParametersBuild.kt +++ b/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/build/ZigDebugParametersBuild.kt @@ -53,7 +53,7 @@ class ZigDebugParametersBuild( private lateinit var executableFile: File override fun getInstaller(): Installer { - return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, profileState.configuration.exeArgs.args) + return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, profileState.configuration.exeArgs.argsSplit()) } @Throws(ExecutionException::class) diff --git a/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/run/ZigDebugParametersRun.kt b/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/run/ZigDebugParametersRun.kt index 1b09d4ec..32558668 100644 --- a/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/run/ZigDebugParametersRun.kt +++ b/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/run/ZigDebugParametersRun.kt @@ -32,6 +32,6 @@ import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration class ZigDebugParametersRun(driverConfiguration: DebuggerDriverConfiguration, toolchain: AbstractZigToolchain, profileState: ZigProfileStateRun) : ZigDebugParametersEmitBinaryBase(driverConfiguration, toolchain, profileState) { override fun getInstaller(): Installer { - return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, profileState.configuration.exeArgs.args) + return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, profileState.configuration.exeArgs.argsSplit()) } } \ No newline at end of file diff --git a/cidr/src/main/resources/META-INF/zigbrains-debugger.xml b/cidr/src/main/resources/META-INF/zigbrains-debugger.xml index 19b99ee5..86bfdd93 100644 --- a/cidr/src/main/resources/META-INF/zigbrains-debugger.xml +++ b/cidr/src/main/resources/META-INF/zigbrains-debugger.xml @@ -1,5 +1,5 @@ - com.intellij.nativeDebug + com.intellij.modules.cidr.debugger , Cloneable { - var args: List = emptyList() + var args: String = "" override fun readExternal(element: Element) { - args = element.readStrings(serializedName) ?: return + args = element.readString(serializedName) ?: element.readStrings(serializedName)?.joinToString(separator = " ") { if (it.contains(' ')) "\"$it\"" else it } ?: "" + } + + fun argsSplit(): List { + return translateCommandline(args) } override fun writeExternal(element: Element) { - element.writeStrings(serializedName, args) + element.writeString(serializedName, args) } override fun createEditor(): ZigConfigModule { @@ -376,12 +380,12 @@ class ArgsConfigurable( } override fun apply(configurable: ArgsConfigurable): Boolean { - configurable.args = translateCommandline(argsField.text) + configurable.args = argsField.text ?: "" return true } override fun reset(configurable: ArgsConfigurable) { - argsField.text = configurable.args.joinToString(separator = " ") + argsField.text = configurable.args } override fun construct(p: Panel): Unit = with(p) { diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigExecConfigBuild.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigExecConfigBuild.kt index 5a0bcb4a..c77c408b 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigExecConfigBuild.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigExecConfigBuild.kt @@ -48,9 +48,10 @@ class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigEx override suspend fun buildCommandLineArgs(debug: Boolean): List { val result = ArrayList() result.add("build") + val argsSplit = buildSteps.argsSplit() val steps = if (debug) { val truncatedSteps = ArrayList() - for (step in buildSteps.args) { + for (step in argsSplit) { if (step == "run") continue @@ -60,10 +61,10 @@ class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigEx truncatedSteps.add(step) } truncatedSteps - } else buildSteps.args + } else argsSplit result.addAll(steps) result.addAll(coloredCliFlags(colored.value, debug)) - result.addAll(extraArgs.args) + result.addAll(extraArgs.argsSplit()) return result } diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigExecConfigRun.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigExecConfigRun.kt index d68a6dce..b399105d 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigExecConfigRun.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigExecConfigRun.kt @@ -52,10 +52,10 @@ class ZigExecConfigRun(project: Project, factory: ConfigurationFactory): ZigExec if (!debug || optimization.forced) { result.addAll(listOf("-O", optimization.level.name)) } - result.addAll(compilerArgs.args) + result.addAll(compilerArgs.argsSplit()) if (!debug) { result.add("--") - result.addAll(exeArgs.args) + result.addAll(exeArgs.argsSplit()) } return result } diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigExecConfigTest.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigExecConfigTest.kt index a5894756..4f37fee3 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigExecConfigTest.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigExecConfigTest.kt @@ -51,7 +51,7 @@ class ZigExecConfigTest(project: Project, factory: ConfigurationFactory): ZigExe if (!debug || optimization.forced) { result.addAll(listOf("-O", optimization.level.name)) } - result.addAll(compilerArgs.args) + result.addAll(compilerArgs.argsSplit()) if (debug) { result.add("--test-no-exec") } diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectConfigurationData.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectConfigurationData.kt index e15d2636..5fa4133a 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectConfigurationData.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectConfigurationData.kt @@ -73,11 +73,10 @@ data class ZigProjectConfigurationData( ).notify(project) return@indeterminateStep false } - val result = zig.callWithArgs(workDir, "init") - if (result == null) { + val result = zig.callWithArgs(workDir, "init").getOrElse { throwable -> Notification( "zigbrains", - "\"zig init\" could not run because the zig executable was missing!", + "Failed to run \"zig init\": ${throwable.message}", NotificationType.ERROR ).notify(project) return@indeterminateStep false diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettingsPanel.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettingsPanel.kt index 601ee937..b248e9ae 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettingsPanel.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettingsPanel.kt @@ -27,11 +27,11 @@ import com.falsepattern.zigbrains.direnv.DirenvCmd import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider 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.ModalityState import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory import com.intellij.openapi.project.Project -import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.UserDataHolderBase import com.intellij.openapi.util.io.toNioPathOrNull @@ -41,7 +41,7 @@ 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.JBLabel +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 @@ -68,7 +68,7 @@ class ZigProjectSettingsPanel(private val project: Project) : ZigProjectConfigur }) Disposer.register(this, it) } - private val toolchainVersion = JBLabel() + private val toolchainVersion = JBTextArea().also { it.isEditable = false } private val stdFieldOverride = JBCheckBox(ZigBrainsBundle.message("settings.project.label.override-std")).apply { addChangeListener { if (isSelected) { @@ -161,35 +161,39 @@ class ZigProjectSettingsPanel(private val project: Project) : ZigProjectConfigur } } - private suspend fun updateUI() { - val pathToToolchain = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull() delay(200) - val toolchain = pathToToolchain?.let { LocalZigToolchain(it) } - val zig = toolchain?.zig - if (zig?.path()?.toFile()?.exists() != true) { - toolchainVersion.text = "[zig binary not found]" - - if (!stdFieldOverride.isSelected) { - pathToStd.text = "" + val pathToToolchain = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull() + if (pathToToolchain == null) { + withEDTContext(ModalityState.any()) { + toolchainVersion.text = "[toolchain path empty or invalid]" + if (!stdFieldOverride.isSelected) { + pathToStd.text = "" + } } return } - val env = zig.getEnv(project) - if (env == null) { - toolchainVersion.text = "[failed to run zig env]" - if (!stdFieldOverride.isSelected) { - pathToStd.text = "" + val toolchain = LocalZigToolchain(pathToToolchain) + val zig = toolchain.zig + val env = zig.getEnv(project).getOrElse { throwable -> + throwable.printStackTrace() + withEDTContext(ModalityState.any()) { + toolchainVersion.text = "[failed to run \"zig env\"]\n${throwable.message}" + if (!stdFieldOverride.isSelected) { + pathToStd.text = "" + } } return } val version = env.version val stdPath = env.stdPath(toolchain, project) - toolchainVersion.text = version - toolchainVersion.foreground = JBColor.foreground() - if (!stdFieldOverride.isSelected) { - pathToStd.text = stdPath?.pathString ?: "" + withEDTContext(ModalityState.any()) { + toolchainVersion.text = version + toolchainVersion.foreground = JBColor.foreground() + if (!stdFieldOverride.isSelected) { + pathToStd.text = stdPath?.pathString ?: "" + } } } diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigStepDiscoveryService.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigStepDiscoveryService.kt index 0b704283..3cf6fc92 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigStepDiscoveryService.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigStepDiscoveryService.kt @@ -84,9 +84,12 @@ class ZigStepDiscoveryService(private val project: Project) { project.guessProjectDir()?.toNioPathOrNull(), "build", "-l", timeoutMillis = currentTimeoutSec * 1000L - ) + ).getOrElse { throwable -> + errorReload(ErrorType.MissingZigExe, throwable.message) + null + } if (result == null) { - errorReload(ErrorType.MissingZigExe) + {} } else if (result.checkSuccess(LOG)) { currentTimeoutSec = DEFAULT_TIMEOUT_SEC val lines = result.stdoutLines diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BuildToolWindowContext.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BuildToolWindowContext.kt index 3112ec4e..d2d61dc6 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BuildToolWindowContext.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BuildToolWindowContext.kt @@ -87,7 +87,7 @@ class BuildToolWindowContext(private val project: Project): Disposable { val factory = firstConfigFactory() val newConfig = manager.createConfiguration("zig build $stepName", factory) val config = newConfig.configuration as ZigExecConfigBuild - config.buildSteps.args = listOf(stepName) + config.buildSteps.args = stepName manager.addConfiguration(newConfig) return@run newConfig } @@ -213,7 +213,7 @@ private fun getViewport(project: Project): JBScrollPane? { private fun getExistingRunConfig(manager: RunManager, stepName: String): RunnerAndConfigurationSettings? { for (config in manager.getConfigurationSettingsList(ZigConfigTypeBuild::class.java)) { val build = config.configuration as? ZigExecConfigBuild ?: continue - val steps = build.buildSteps.args + val steps = build.buildSteps.argsSplit() if (steps.size != 1) continue if (steps[0] != stepName) diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/stdlib/ZigSyntheticLibrary.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/stdlib/ZigSyntheticLibrary.kt index 5fb11a8c..f81dab44 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/stdlib/ZigSyntheticLibrary.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/stdlib/ZigSyntheticLibrary.kt @@ -131,7 +131,7 @@ private fun getName( project: Project ): String { val tc = state.toolchain ?: return "Zig" - val version = runBlocking { tc.zig.getEnv(project)?.version } ?: return "Zig" + val version = runBlocking { tc.zig.getEnv(project) }.mapCatching { it.version }.getOrElse { return "Zig" } return "Zig $version" } @@ -155,7 +155,7 @@ suspend fun getRoot( } } if (toolchain != null) { - val stdPath = toolchain.zig.getEnv(project)?.stdPath(toolchain, project) ?: return null + val stdPath = toolchain.zig.getEnv(project).mapCatching { it.stdPath(toolchain, project) }.getOrNull() ?: return null val roots = stdPath.refreshAndFindVirtualDirectory() ?: return null return roots } diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigCompilerTool.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigCompilerTool.kt index baa0c441..394cda12 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigCompilerTool.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigCompilerTool.kt @@ -27,6 +27,7 @@ import com.falsepattern.zigbrains.project.toolchain.ZigToolchainEnvironmentSeria import com.intellij.openapi.project.Project import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json +import java.lang.IllegalStateException import java.nio.file.Path class ZigCompilerTool(toolchain: AbstractZigToolchain) : ZigTool(toolchain) { @@ -37,12 +38,12 @@ class ZigCompilerTool(toolchain: AbstractZigToolchain) : ZigTool(toolchain) { return toolchain.pathToExecutable(toolName) } - suspend fun getEnv(project: Project?): ZigToolchainEnvironmentSerializable? { - val stdout = callWithArgs(toolchain.workingDirectory(project), "env")?.stdout ?: return null + suspend fun getEnv(project: Project?): Result { + val stdout = callWithArgs(toolchain.workingDirectory(project), "env").getOrElse { throwable -> return Result.failure(throwable) }.stdout return try { - envJson.decodeFromString(stdout) + Result.success(envJson.decodeFromString(stdout)) } catch (e: SerializationException) { - null + Result.failure(IllegalStateException("could not deserialize zig env", e)) } } } diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigTool.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigTool.kt index de5e0e7a..a4dc5170 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigTool.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigTool.kt @@ -23,52 +23,26 @@ package com.falsepattern.zigbrains.project.toolchain.tools import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.falsepattern.zigbrains.shared.cli.call +import com.falsepattern.zigbrains.shared.cli.createCommandLineSafe import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.execution.process.ProcessOutput -import com.intellij.util.io.awaitExit -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runInterruptible -import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeoutOrNull import java.nio.file.Path -import kotlin.io.path.isRegularFile abstract class ZigTool(val toolchain: AbstractZigToolchain) { abstract val toolName: String - suspend fun callWithArgs(workingDirectory: Path?, vararg parameters: String, timeoutMillis: Long = Long.MAX_VALUE): ProcessOutput? { - val cli = createBaseCommandLine(workingDirectory, *parameters) ?: return null - - val (process, exitCode) = withContext(Dispatchers.IO) { - val process = cli.createProcess() - val exit = withTimeoutOrNull(timeoutMillis) { - process.awaitExit() - } - process to exit - } - return runInterruptible { - ProcessOutput( - process.inputStream.bufferedReader().use { it.readText() }, - process.errorStream.bufferedReader().use { it.readText() }, - exitCode ?: -1, - exitCode == null, - false - ) - } + suspend fun callWithArgs(workingDirectory: Path?, vararg parameters: String, timeoutMillis: Long = Long.MAX_VALUE): Result { + val cli = createBaseCommandLine(workingDirectory, *parameters).let { it.getOrElse { return Result.failure(it) } } + return cli.call(timeoutMillis) } private suspend fun createBaseCommandLine( workingDirectory: Path?, vararg parameters: String - ): GeneralCommandLine? { + ): Result { val exe = toolchain.pathToExecutable(toolName) - if (!exe.isRegularFile()) - return null - val cli = GeneralCommandLine() - .withExePath(exe.toString()) - .withWorkingDirectory(workingDirectory) - .withParameters(*parameters) - .withCharset(Charsets.UTF_8) - return toolchain.patchCommandLine(cli) + return createCommandLineSafe(workingDirectory, exe, *parameters) + .mapCatching { toolchain.patchCommandLine(it) } } } \ No newline at end of file diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/shared/cli/CLIUtil.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/shared/cli/CLIUtil.kt index 3836fd34..032c7b98 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/shared/cli/CLIUtil.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/shared/cli/CLIUtil.kt @@ -23,8 +23,19 @@ package com.falsepattern.zigbrains.shared.cli import com.falsepattern.zigbrains.ZigBrainsBundle +import com.intellij.execution.configurations.GeneralCommandLine +import com.intellij.execution.process.ProcessOutput import com.intellij.openapi.options.ConfigurationException +import com.intellij.util.io.awaitExit +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runInterruptible +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeoutOrNull +import java.nio.file.Path import java.util.* +import kotlin.io.path.exists +import kotlin.io.path.isDirectory +import kotlin.io.path.pathString //From Apache Ant @@ -100,4 +111,40 @@ fun coloredCliFlags(colored: Boolean, debug: Boolean): List { } else { listOf("--color", if (colored) "on" else "off") } +} + +fun createCommandLineSafe( + workingDirectory: Path?, + exe: Path, + vararg parameters: String, +): Result { + if (!exe.exists()) + return Result.failure(IllegalArgumentException("file does not exist: ${exe.pathString}")) + if (exe.isDirectory()) + return Result.failure(IllegalArgumentException("file is a directory: ${exe.pathString}")) + val cli = GeneralCommandLine() + .withExePath(exe.toString()) + .withWorkingDirectory(workingDirectory) + .withParameters(*parameters) + .withCharset(Charsets.UTF_8) + return Result.success(cli) +} + +suspend fun GeneralCommandLine.call(timeoutMillis: Long = Long.MAX_VALUE): Result { + val (process, exitCode) = withContext(Dispatchers.IO) { + val process = createProcess() + val exit = withTimeoutOrNull(timeoutMillis) { + process.awaitExit() + } + process to exit + } + return runInterruptible { + Result.success(ProcessOutput( + process.inputStream.bufferedReader().use { it.readText() }, + process.errorStream.bufferedReader().use { it.readText() }, + exitCode ?: -1, + exitCode == null, + false + )) + } } \ No newline at end of file diff --git a/core/src/main/resources/zigbrains/Bundle.properties b/core/src/main/resources/zigbrains/Bundle.properties index 53db873f..49718cae 100644 --- a/core/src/main/resources/zigbrains/Bundle.properties +++ b/core/src/main/resources/zigbrains/Bundle.properties @@ -105,7 +105,7 @@ configuration.build.marker-name=Build and Run settings.project.group.title=Zig Settings settings.project.label.direnv=Use direnv settings.project.label.toolchain=Toolchain location -settings.project.label.toolchain-version=Toolchain version +settings.project.label.toolchain-version=Detected zig version settings.project.label.override-std=Override standard library settings.project.label.std-location=Standard library location build.tool.window.tree.steps.label=Steps diff --git a/gradle.properties b/gradle.properties index dbb4cf44..e9c3c5e8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ pluginName=ZigBrains pluginRepositoryUrl=https://github.com/FalsePattern/ZigBrains -pluginVersion=21.1.0 +pluginVersion=22.0.0 pluginSinceBuild=242 pluginUntilBuild=242.* diff --git a/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZLSStartup.kt b/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZLSStartup.kt index 6c79451d..f126be57 100644 --- a/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZLSStartup.kt +++ b/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZLSStartup.kt @@ -26,8 +26,12 @@ 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.shared.zigCoroutineScope import com.intellij.openapi.project.Project import com.intellij.openapi.startup.ProjectActivity +import com.intellij.ui.EditorNotifications +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import kotlin.io.path.pathString class ZLSStartup: ProjectActivity { @@ -43,5 +47,16 @@ class ZLSStartup: ProjectActivity { project.zlsSettings.state = zlsState } } + project.zigCoroutineScope.launch { + var currentState = project.zlsRunningAsync() + while (!project.isDisposed) { + val running = project.zlsRunningAsync() + if (currentState != running) { + EditorNotifications.getInstance(project).updateAllNotifications() + } + currentState = running + delay(1000) + } + } } } \ No newline at end of file diff --git a/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLanguageServerFactory.kt b/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLanguageServerFactory.kt index 23a78eac..d1c61d49 100644 --- a/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLanguageServerFactory.kt +++ b/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLanguageServerFactory.kt @@ -71,15 +71,43 @@ class ZigLanguageServerFactory: LanguageServerFactory, LanguageServerEnablementS return features } - override fun isEnabled(project: Project): Boolean { - return (project.getUserData(ENABLED_KEY) != false) && project.zlsSettings.validate() - } + override fun isEnabled(project: Project) = project.zlsEnabledSync() override fun setEnabled(enabled: Boolean, project: Project) { - project.putUserData(ENABLED_KEY, enabled) + project.zlsEnabled(enabled) } } +suspend fun Project.zlsEnabledAsync(): Boolean { + return (getUserData(ENABLED_KEY) != false) && zlsSettings.validateAsync() +} + +fun Project.zlsEnabledSync(): Boolean { + return (getUserData(ENABLED_KEY) != false) && zlsSettings.validateSync() +} + +fun Project.zlsEnabled(value: Boolean) { + putUserData(ENABLED_KEY, value) +} + +suspend fun Project.zlsRunningAsync(): Boolean { + if (!zlsEnabledAsync()) + return false + return zlsRunningLsp4ij() +} + +fun Project.zlsRunningSync(): Boolean { + if (!zlsEnabledSync()) + return false + return zlsRunningLsp4ij() +} + +private fun Project.zlsRunningLsp4ij(): Boolean { + val manager = service() + val status = manager.getServerStatus("ZigBrains") + return status == ServerStatus.started || status == ServerStatus.starting +} + class ZLSStarter: LanguageServerStarter { override fun startLSP(project: Project, restart: Boolean) { project.zigCoroutineScope.launch { @@ -87,7 +115,10 @@ class ZLSStarter: LanguageServerStarter { val status = manager.getServerStatus("ZigBrains") if ((status == ServerStatus.started || status == ServerStatus.starting) && !restart) return@launch - manager.start("ZigBrains") + manager.stop("ZigBrains") + if (project.zlsSettings.validateAsync()) { + manager.start("ZigBrains") + } } } diff --git a/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/notification/ZigEditorNotificationProvider.kt b/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/notification/ZigEditorNotificationProvider.kt new file mode 100644 index 00000000..b72dde64 --- /dev/null +++ b/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/notification/ZigEditorNotificationProvider.kt @@ -0,0 +1,64 @@ +/* + * 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 . + */ + +package com.falsepattern.zigbrains.lsp.notification + +import com.falsepattern.zigbrains.lsp.ZLSBundle +import com.falsepattern.zigbrains.lsp.settings.zlsSettings +import com.falsepattern.zigbrains.lsp.zlsRunningSync +import com.falsepattern.zigbrains.zig.ZigFileType +import com.falsepattern.zigbrains.zon.ZonFileType +import com.intellij.openapi.fileEditor.FileEditor +import com.intellij.openapi.project.DumbAware +import com.intellij.openapi.project.Project +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.ui.EditorNotificationPanel +import com.intellij.ui.EditorNotificationProvider +import java.util.function.Function +import javax.swing.JComponent + +class ZigEditorNotificationProvider: EditorNotificationProvider, DumbAware { + override fun collectNotificationData( + project: Project, + file: VirtualFile + ): Function? { + when (file.fileType) { + ZigFileType, ZonFileType -> {} + else -> return null + } + if (project.zlsRunningSync()) { + return null + } + return Function { editor -> + val status: EditorNotificationPanel.Status + val message: String + if (!project.zlsSettings.validateSync()) { + status = EditorNotificationPanel.Status.Error + message = ZLSBundle.message("notification.banner.zls-bad-config") + } else { + status = EditorNotificationPanel.Status.Warning + message = ZLSBundle.message("notification.banner.zls-not-running") + } + EditorNotificationPanel(editor, status).also { it.text = message } + } + } +} \ No newline at end of file diff --git a/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSProjectSettingsService.kt b/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSProjectSettingsService.kt index 618385da..cccda858 100644 --- a/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSProjectSettingsService.kt +++ b/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSProjectSettingsService.kt @@ -25,6 +25,7 @@ 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.intellij.openapi.components.* import com.intellij.openapi.project.Project import com.intellij.openapi.util.io.toNioPathOrNull @@ -32,9 +33,9 @@ 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 java.util.concurrent.locks.ReentrantLock -import kotlin.concurrent.withLock import kotlin.io.path.isExecutable import kotlin.io.path.isRegularFile @@ -51,47 +52,45 @@ class ZLSProjectSettingsService(val project: Project): PersistentStateComponent< @Volatile private var valid = false - private val mutex = ReentrantLock() + private val mutex = Mutex() override fun getState(): ZLSSettings { return state.copy() } fun setState(value: ZLSSettings) { - mutex.withLock { - this.state = value - dirty = true + runBlocking { + mutex.withLock { + this@ZLSProjectSettingsService.state = value + dirty = true + } } + startLSP(project, true) } override fun loadState(state: ZLSSettings) { - mutex.withLock { - this.state = state - dirty = true - } + setState(state) } - fun isModified(otherData: ZLSSettings): Boolean { - return state != otherData - } - - fun validate(): Boolean { + suspend fun validateAsync(): Boolean { mutex.withLock { if (dirty) { val state = this.state - valid = if (application.isDispatchThread) { - runWithModalProgressBlocking(ModalTaskOwner.project(project), ZLSBundle.message("progress.title.validate")) { - doValidate(project, state) - } - } else { - runBlocking { - doValidate(project, state) - } - } + valid = doValidate(project, state) dirty = false } return valid } } + + fun validateSync() = if (application.isDispatchThread) { + runWithModalProgressBlocking(ModalTaskOwner.project(project), ZLSBundle.message("progress.title.validate")) { + validateAsync() + } + } else { + runBlocking { + validateAsync() + } + } } private suspend fun doValidate(project: Project, state: ZLSSettings): Boolean { diff --git a/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettings.kt b/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettings.kt index 51c29771..48e8bf13 100644 --- a/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettings.kt +++ b/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettings.kt @@ -38,7 +38,7 @@ data class ZLSSettings( val enable_argument_placeholders: Boolean = true, val completion_label_details: Boolean = true, val enable_build_on_save: Boolean = false, - val build_on_save_args: List = emptyList(), + val build_on_save_args: String = "", val semantic_tokens: SemanticTokens = SemanticTokens.full, val inlay_hints_show_variable_type_hints: Boolean = true, val inlay_hints_show_struct_literal_field_type: Boolean = true, diff --git a/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigProvider.kt b/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigProvider.kt index 5d42ab13..b11e3ab4 100644 --- a/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigProvider.kt +++ b/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigProvider.kt @@ -24,6 +24,7 @@ package com.falsepattern.zigbrains.lsp.settings import com.falsepattern.zigbrains.lsp.config.ZLSConfig import com.falsepattern.zigbrains.lsp.config.ZLSConfigProvider +import com.falsepattern.zigbrains.shared.cli.translateCommandline import com.intellij.openapi.project.Project class ZLSSettingsConfigProvider: ZLSConfigProvider { @@ -34,7 +35,14 @@ class ZLSSettingsConfigProvider: ZLSConfigProvider { enable_argument_placeholders = state.enable_argument_placeholders, completion_label_details = state.completion_label_details, enable_build_on_save = state.enable_build_on_save, - build_on_save_args = state.build_on_save_args, + build_on_save_args = run { + val args = state.build_on_save_args + return@run if (args.isEmpty()) { + emptyList() + } else { + translateCommandline(args).toList() + } + }, semantic_tokens = state.semantic_tokens, inlay_hints_show_variable_type_hints = state.inlay_hints_show_variable_type_hints, inlay_hints_show_struct_literal_field_type = state.inlay_hints_show_struct_literal_field_type, diff --git a/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigurable.kt b/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigurable.kt index d5ebe2c5..edb49349 100644 --- a/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigurable.kt +++ b/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsConfigurable.kt @@ -42,11 +42,7 @@ class ZLSSettingsConfigurable(private val project: Project): SubConfigurable { override fun apply() { val data = appSettingsComponent?.data ?: return val settings = project.zlsSettings - val reloadZLS = settings.isModified(data) settings.state = data - if (reloadZLS) { - startLSP(project, true) - } } override fun reset() { diff --git a/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsPanel.kt b/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsPanel.kt index 7d9902be..a352f288 100644 --- a/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsPanel.kt +++ b/lsp/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsPanel.kt @@ -29,24 +29,38 @@ 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.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 java.lang.IllegalArgumentException -import java.util.* +import javax.swing.event.DocumentEvent import kotlin.io.path.pathString @Suppress("PrivatePropertyName") @@ -55,13 +69,24 @@ class ZLSSettingsPanel(private val project: Project) : ZigProjectConfigurationPr project, ZLSBundle.message("settings.zls-path.browse.title"), FileChooserDescriptorFactory.createSingleFileDescriptor(), - ).also { Disposer.register(this, it) } + ).also { + it.textField.document.addDocumentListener(object: DocumentAdapter() { + override fun textChanged(p0: DocumentEvent) { + dispatchUpdateUI() + } + }) + Disposer.register(this, it) + } private val zlsConfigPath = textFieldWithBrowseButton( project, ZLSBundle.message("settings.zls-config-path.browse.title"), FileChooserDescriptorFactory.createSingleFileDescriptor() ).also { Disposer.register(this, it) } + private val zlsVersion = JBTextArea().also { it.isEditable = false } + + private var debounce: Job? = null + private val inlayHints = JBCheckBox() private val enable_snippets = JBCheckBox() private val enable_argument_placeholders = JBCheckBox() @@ -102,6 +127,9 @@ class ZLSSettingsPanel(private val project: Project) : ZigProjectConfigurationPr cell(direnv) } } + row(ZLSBundle.message("settings.zls-version.label")) { + cell(zlsVersion) + } fancyRow( "settings.zls-config-path.label", "settings.zls-config-path.tooltip" @@ -207,14 +235,7 @@ class ZLSSettingsPanel(private val project: Project) : ZigProjectConfigurationPr enable_argument_placeholders.isSelected, completion_label_details.isSelected, enable_build_on_save.isSelected, - run { - val args = build_on_save_args.text ?: "" - return@run if (args.isEmpty()) { - emptyList() - } else { - translateCommandline(args).toList() - } - }, + 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, @@ -240,7 +261,7 @@ class ZLSSettingsPanel(private val project: Project) : ZigProjectConfigurationPr 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.joinToString(separator = " ") { it } + 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 @@ -257,6 +278,7 @@ class ZLSSettingsPanel(private val project: Project) : ZigProjectConfigurationPr 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) { @@ -272,12 +294,14 @@ class ZLSSettingsPanel(private val project: Project) : ZigProjectConfigurationPr 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 { @@ -285,6 +309,42 @@ class ZLSSettingsPanel(private val project: Project) : ZigProjectConfigurationPr 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 + withEDTContext(ModalityState.any()) { + zlsVersion.text = version + zlsVersion.foreground = JBColor.foreground() + } + } } private fun Panel.fancyRow( @@ -294,80 +354,4 @@ private fun Panel.fancyRow( ) = row(ZLSBundle.message(label)) { contextHelp(ZLSBundle.message(tooltip)) cb() -} - - -@Throws(Exception::class) -private fun translateCommandline(toProcess: String): List { - if (toProcess.isEmpty()) { - return emptyList() - } - val normal = 0 - val inQuote = 1 - val inDoubleQuote = 2 - val inEscape = 3 - var state = normal - var escapeState = normal - val tok = StringTokenizer(toProcess, "\\\"' ", true) - val v = ArrayList() - val current = StringBuilder() - - while (tok.hasMoreTokens()) { - val nextTok = tok.nextToken() - when (state) { - inQuote -> if ("'" == nextTok) { - state = normal - } else if ("\\" == nextTok) { - escapeState = inQuote - state = inEscape - } else { - current.append(nextTok) - } - - inDoubleQuote -> if ("\"" == nextTok) { - state = normal - } else if ("\\" == nextTok) { - escapeState = inDoubleQuote - state = inEscape - } else { - current.append(nextTok) - } - - inEscape -> { - current.append(when(nextTok) { - "n" -> "\n" - "r" -> "\r" - "t" -> "\t" - else -> nextTok - }) - state = escapeState - } - - else -> if ("'" == nextTok) { - state = inQuote - } else if ("\"" == nextTok) { - state = inDoubleQuote - } else if (" " == nextTok) { - if (current.isNotEmpty()) { - v.add(current.toString()) - current.setLength(0) - } - } else if ("\\" == nextTok) { - escapeState = normal - state = inEscape - } else { - current.append(nextTok) - } - } - } - - if (current.isNotEmpty()) { - v.add(current.toString()) - } - - if (state != inQuote && state != inDoubleQuote) { - return v - } else { - throw IllegalArgumentException("unbalanced quotes in $toProcess") - } -} +} \ No newline at end of file diff --git a/lsp/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ToolchainZLSConfigProvider.kt b/lsp/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ToolchainZLSConfigProvider.kt index 410a5bb8..0b24a3d0 100644 --- a/lsp/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ToolchainZLSConfigProvider.kt +++ b/lsp/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/ToolchainZLSConfigProvider.kt @@ -39,12 +39,11 @@ class ToolchainZLSConfigProvider: SuspendingZLSConfigProvider { var state = svc.state val toolchain = state.toolchain ?: ZigToolchainProvider.suggestToolchain(project, UserDataHolderBase()) ?: return previous - val env = toolchain.zig.getEnv(project) - - if (env == null) { + val env = toolchain.zig.getEnv(project).getOrElse { throwable -> + throwable.printStackTrace() Notification( "zigbrains-lsp", - "Failed to evaluate zig env", + "Failed to evaluate \"zig env\": ${throwable.message}", NotificationType.ERROR ).notify(project) return previous diff --git a/lsp/src/main/resources/META-INF/zigbrains-lsp.xml b/lsp/src/main/resources/META-INF/zigbrains-lsp.xml index 5a749c2c..a9c5f872 100644 --- a/lsp/src/main/resources/META-INF/zigbrains-lsp.xml +++ b/lsp/src/main/resources/META-INF/zigbrains-lsp.xml @@ -45,6 +45,10 @@ /> + + diff --git a/lsp/src/main/resources/zigbrains/lsp/Bundle.properties b/lsp/src/main/resources/zigbrains/lsp/Bundle.properties index a23813e7..9582f4e9 100644 --- a/lsp/src/main/resources/zigbrains/lsp/Bundle.properties +++ b/lsp/src/main/resources/zigbrains/lsp/Bundle.properties @@ -1,7 +1,8 @@ settings.group.title=ZLS Settings settings.zls-path.label=Executable path -settings.zls-path.tooltip=Path to the ZLS Binary +settings.zls-path.tooltip=Path to the ZLS Binary settings.zls-path.browse.title=Path to the ZLS Binary +settings.zls-version.label=Detected ZLS version settings.zls-config-path.label=Config path settings.zls-config-path.tooltip=Leave empty to use built-in config generated from the settings below settings.zls-config-path.browse.title=Path to the Custom ZLS Config File (Optional) @@ -59,6 +60,8 @@ notification.message.zls-config-not-exists.content=ZLS config file does not exis notification.message.zls-config-not-file.content=ZLS config file is not a regular file: {0} notification.message.zls-config-path-invalid.content=ZLS config path could not be parted: {0} notification.message.zls-config-autogen-failed.content=Failed to autogenerate ZLS config from toolchain +notification.banner.zls-not-running=Zig Language Server is not running. Check the [Language Servers] tool menu! +notification.banner.zls-bad-config=Zig Language Server is misconfigured. Check [Settings | Languages \\& Frameworks | Zig]! progress.title.create-connection-provider=Creating ZLS connection provider progress.title.validate=Validating ZLS # suppress inspection "UnusedProperty" diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 5258b9f7..b25173d7 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -5,7 +5,7 @@ com.intellij.modules.platform com.redhat.devtools.lsp4ij - com.intellij.nativeDebug + com.intellij.modules.cidr.debugger com.intellij.cidr.base com.intellij.clion