backport: 22.0.0

This commit is contained in:
FalsePattern 2025-03-11 14:29:48 +01:00
parent ae08287d7e
commit 0162e53b01
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
33 changed files with 378 additions and 270 deletions

View file

@ -17,6 +17,31 @@ Changelog structure reference:
## [Unreleased] ## [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] ## [21.1.0]
### Added ### Added

View file

@ -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/.

View file

@ -39,7 +39,7 @@ class ZigExecConfigBinary(project: Project, factory: ConfigurationFactory) : Zig
get() = ZigDebugBundle.message("configuration.binary.suggested-name") get() = ZigDebugBundle.message("configuration.binary.suggested-name")
override suspend fun buildCommandLineArgs(debug: Boolean): List<String> { override suspend fun buildCommandLineArgs(debug: Boolean): List<String> {
return args.args return args.argsSplit()
} }
override fun getConfigurables(): List<ZigConfigurable<*>> { override fun getConfigurables(): List<ZigConfigurable<*>> {

View file

@ -36,6 +36,6 @@ class ZigDebugParametersBinary @Throws(ExecutionException::class) constructor(dr
ZigDebugParametersBase<ZigProfileStateBinary>(driverConfiguration, toolchain, profileState) { ZigDebugParametersBase<ZigProfileStateBinary>(driverConfiguration, toolchain, profileState) {
private val executableFile = profileState.configuration.exePath.path?.toFile() ?: throw ExecutionException(ZigDebugBundle.message("exception.missing-exe-path")) private val executableFile = profileState.configuration.exePath.path?.toFile() ?: throw ExecutionException(ZigDebugBundle.message("exception.missing-exe-path"))
override fun getInstaller(): Installer { override fun getInstaller(): Installer {
return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, profileState.configuration.args.args) return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, profileState.configuration.args.argsSplit())
} }
} }

View file

@ -53,7 +53,7 @@ class ZigDebugParametersBuild(
private lateinit var executableFile: File private lateinit var executableFile: File
override fun getInstaller(): Installer { 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) @Throws(ExecutionException::class)

View file

@ -32,6 +32,6 @@ import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
class ZigDebugParametersRun(driverConfiguration: DebuggerDriverConfiguration, toolchain: AbstractZigToolchain, profileState: ZigProfileStateRun) : class ZigDebugParametersRun(driverConfiguration: DebuggerDriverConfiguration, toolchain: AbstractZigToolchain, profileState: ZigProfileStateRun) :
ZigDebugParametersEmitBinaryBase<ZigProfileStateRun>(driverConfiguration, toolchain, profileState) { ZigDebugParametersEmitBinaryBase<ZigProfileStateRun>(driverConfiguration, toolchain, profileState) {
override fun getInstaller(): Installer { override fun getInstaller(): Installer {
return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, profileState.configuration.exeArgs.args) return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, profileState.configuration.exeArgs.argsSplit())
} }
} }

View file

@ -1,5 +1,5 @@
<idea-plugin package="com.falsepattern.zigbrains.debugger"> <idea-plugin package="com.falsepattern.zigbrains.debugger">
<depends>com.intellij.nativeDebug</depends> <depends>com.intellij.modules.cidr.debugger</depends>
<extensions defaultExtensionNs="com.intellij"> <extensions defaultExtensionNs="com.intellij">
<configurationType <configurationType

View file

@ -347,14 +347,18 @@ class ArgsConfigurable(
@Transient private val serializedName: String, @Transient private val serializedName: String,
@Transient @Nls private val guiName: String @Transient @Nls private val guiName: String
) : ZigConfigurable<ArgsConfigurable>, Cloneable { ) : ZigConfigurable<ArgsConfigurable>, Cloneable {
var args: List<String> = emptyList() var args: String = ""
override fun readExternal(element: Element) { 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<String> {
return translateCommandline(args)
} }
override fun writeExternal(element: Element) { override fun writeExternal(element: Element) {
element.writeStrings(serializedName, args) element.writeString(serializedName, args)
} }
override fun createEditor(): ZigConfigModule<ArgsConfigurable> { override fun createEditor(): ZigConfigModule<ArgsConfigurable> {
@ -376,12 +380,12 @@ class ArgsConfigurable(
} }
override fun apply(configurable: ArgsConfigurable): Boolean { override fun apply(configurable: ArgsConfigurable): Boolean {
configurable.args = translateCommandline(argsField.text) configurable.args = argsField.text ?: ""
return true return true
} }
override fun reset(configurable: ArgsConfigurable) { override fun reset(configurable: ArgsConfigurable) {
argsField.text = configurable.args.joinToString(separator = " ") argsField.text = configurable.args
} }
override fun construct(p: Panel): Unit = with(p) { override fun construct(p: Panel): Unit = with(p) {

View file

@ -48,9 +48,10 @@ class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigEx
override suspend fun buildCommandLineArgs(debug: Boolean): List<String> { override suspend fun buildCommandLineArgs(debug: Boolean): List<String> {
val result = ArrayList<String>() val result = ArrayList<String>()
result.add("build") result.add("build")
val argsSplit = buildSteps.argsSplit()
val steps = if (debug) { val steps = if (debug) {
val truncatedSteps = ArrayList<String>() val truncatedSteps = ArrayList<String>()
for (step in buildSteps.args) { for (step in argsSplit) {
if (step == "run") if (step == "run")
continue continue
@ -60,10 +61,10 @@ class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigEx
truncatedSteps.add(step) truncatedSteps.add(step)
} }
truncatedSteps truncatedSteps
} else buildSteps.args } else argsSplit
result.addAll(steps) result.addAll(steps)
result.addAll(coloredCliFlags(colored.value, debug)) result.addAll(coloredCliFlags(colored.value, debug))
result.addAll(extraArgs.args) result.addAll(extraArgs.argsSplit())
return result return result
} }

View file

@ -52,10 +52,10 @@ class ZigExecConfigRun(project: Project, factory: ConfigurationFactory): ZigExec
if (!debug || optimization.forced) { if (!debug || optimization.forced) {
result.addAll(listOf("-O", optimization.level.name)) result.addAll(listOf("-O", optimization.level.name))
} }
result.addAll(compilerArgs.args) result.addAll(compilerArgs.argsSplit())
if (!debug) { if (!debug) {
result.add("--") result.add("--")
result.addAll(exeArgs.args) result.addAll(exeArgs.argsSplit())
} }
return result return result
} }

View file

@ -51,7 +51,7 @@ class ZigExecConfigTest(project: Project, factory: ConfigurationFactory): ZigExe
if (!debug || optimization.forced) { if (!debug || optimization.forced) {
result.addAll(listOf("-O", optimization.level.name)) result.addAll(listOf("-O", optimization.level.name))
} }
result.addAll(compilerArgs.args) result.addAll(compilerArgs.argsSplit())
if (debug) { if (debug) {
result.add("--test-no-exec") result.add("--test-no-exec")
} }

View file

@ -73,11 +73,10 @@ data class ZigProjectConfigurationData(
).notify(project) ).notify(project)
return@indeterminateStep false return@indeterminateStep false
} }
val result = zig.callWithArgs(workDir, "init") val result = zig.callWithArgs(workDir, "init").getOrElse { throwable ->
if (result == null) {
Notification( Notification(
"zigbrains", "zigbrains",
"\"zig init\" could not run because the zig executable was missing!", "Failed to run \"zig init\": ${throwable.message}",
NotificationType.ERROR NotificationType.ERROR
).notify(project) ).notify(project)
return@indeterminateStep false return@indeterminateStep false

View file

@ -27,11 +27,11 @@ import com.falsepattern.zigbrains.direnv.DirenvCmd
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider
import com.falsepattern.zigbrains.shared.coroutine.launchWithEDT 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.Disposable import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.UserDataHolderBase import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.util.io.toNioPathOrNull 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.DocumentAdapter
import com.intellij.ui.JBColor import com.intellij.ui.JBColor
import com.intellij.ui.components.JBCheckBox 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.components.textFieldWithBrowseButton
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
@ -68,7 +68,7 @@ class ZigProjectSettingsPanel(private val project: Project) : ZigProjectConfigur
}) })
Disposer.register(this, it) 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 { private val stdFieldOverride = JBCheckBox(ZigBrainsBundle.message("settings.project.label.override-std")).apply {
addChangeListener { addChangeListener {
if (isSelected) { if (isSelected) {
@ -161,35 +161,39 @@ class ZigProjectSettingsPanel(private val project: Project) : ZigProjectConfigur
} }
} }
private suspend fun updateUI() { private suspend fun updateUI() {
val pathToToolchain = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull()
delay(200) delay(200)
val toolchain = pathToToolchain?.let { LocalZigToolchain(it) } val pathToToolchain = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull()
val zig = toolchain?.zig if (pathToToolchain == null) {
if (zig?.path()?.toFile()?.exists() != true) { withEDTContext(ModalityState.any()) {
toolchainVersion.text = "[zig binary not found]" toolchainVersion.text = "[toolchain path empty or invalid]"
if (!stdFieldOverride.isSelected) {
if (!stdFieldOverride.isSelected) { pathToStd.text = ""
pathToStd.text = "" }
} }
return return
} }
val env = zig.getEnv(project) val toolchain = LocalZigToolchain(pathToToolchain)
if (env == null) { val zig = toolchain.zig
toolchainVersion.text = "[failed to run zig env]" val env = zig.getEnv(project).getOrElse { throwable ->
if (!stdFieldOverride.isSelected) { throwable.printStackTrace()
pathToStd.text = "" withEDTContext(ModalityState.any()) {
toolchainVersion.text = "[failed to run \"zig env\"]\n${throwable.message}"
if (!stdFieldOverride.isSelected) {
pathToStd.text = ""
}
} }
return return
} }
val version = env.version val version = env.version
val stdPath = env.stdPath(toolchain, project) val stdPath = env.stdPath(toolchain, project)
toolchainVersion.text = version
toolchainVersion.foreground = JBColor.foreground()
if (!stdFieldOverride.isSelected) { withEDTContext(ModalityState.any()) {
pathToStd.text = stdPath?.pathString ?: "" toolchainVersion.text = version
toolchainVersion.foreground = JBColor.foreground()
if (!stdFieldOverride.isSelected) {
pathToStd.text = stdPath?.pathString ?: ""
}
} }
} }

View file

@ -84,9 +84,12 @@ class ZigStepDiscoveryService(private val project: Project) {
project.guessProjectDir()?.toNioPathOrNull(), project.guessProjectDir()?.toNioPathOrNull(),
"build", "-l", "build", "-l",
timeoutMillis = currentTimeoutSec * 1000L timeoutMillis = currentTimeoutSec * 1000L
) ).getOrElse { throwable ->
errorReload(ErrorType.MissingZigExe, throwable.message)
null
}
if (result == null) { if (result == null) {
errorReload(ErrorType.MissingZigExe) {}
} else if (result.checkSuccess(LOG)) { } else if (result.checkSuccess(LOG)) {
currentTimeoutSec = DEFAULT_TIMEOUT_SEC currentTimeoutSec = DEFAULT_TIMEOUT_SEC
val lines = result.stdoutLines val lines = result.stdoutLines

View file

@ -87,7 +87,7 @@ class BuildToolWindowContext(private val project: Project): Disposable {
val factory = firstConfigFactory<ZigConfigTypeBuild>() val factory = firstConfigFactory<ZigConfigTypeBuild>()
val newConfig = manager.createConfiguration("zig build $stepName", factory) val newConfig = manager.createConfiguration("zig build $stepName", factory)
val config = newConfig.configuration as ZigExecConfigBuild val config = newConfig.configuration as ZigExecConfigBuild
config.buildSteps.args = listOf(stepName) config.buildSteps.args = stepName
manager.addConfiguration(newConfig) manager.addConfiguration(newConfig)
return@run newConfig return@run newConfig
} }
@ -213,7 +213,7 @@ private fun getViewport(project: Project): JBScrollPane? {
private fun getExistingRunConfig(manager: RunManager, stepName: String): RunnerAndConfigurationSettings? { private fun getExistingRunConfig(manager: RunManager, stepName: String): RunnerAndConfigurationSettings? {
for (config in manager.getConfigurationSettingsList(ZigConfigTypeBuild::class.java)) { for (config in manager.getConfigurationSettingsList(ZigConfigTypeBuild::class.java)) {
val build = config.configuration as? ZigExecConfigBuild ?: continue val build = config.configuration as? ZigExecConfigBuild ?: continue
val steps = build.buildSteps.args val steps = build.buildSteps.argsSplit()
if (steps.size != 1) if (steps.size != 1)
continue continue
if (steps[0] != stepName) if (steps[0] != stepName)

View file

@ -131,7 +131,7 @@ private fun getName(
project: Project project: Project
): String { ): String {
val tc = state.toolchain ?: return "Zig" 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" return "Zig $version"
} }
@ -155,7 +155,7 @@ suspend fun getRoot(
} }
} }
if (toolchain != null) { 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 val roots = stdPath.refreshAndFindVirtualDirectory() ?: return null
return roots return roots
} }

View file

@ -27,6 +27,7 @@ import com.falsepattern.zigbrains.project.toolchain.ZigToolchainEnvironmentSeria
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import kotlinx.serialization.SerializationException import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.lang.IllegalStateException
import java.nio.file.Path import java.nio.file.Path
class ZigCompilerTool(toolchain: AbstractZigToolchain) : ZigTool(toolchain) { class ZigCompilerTool(toolchain: AbstractZigToolchain) : ZigTool(toolchain) {
@ -37,12 +38,12 @@ class ZigCompilerTool(toolchain: AbstractZigToolchain) : ZigTool(toolchain) {
return toolchain.pathToExecutable(toolName) return toolchain.pathToExecutable(toolName)
} }
suspend fun getEnv(project: Project?): ZigToolchainEnvironmentSerializable? { suspend fun getEnv(project: Project?): Result<ZigToolchainEnvironmentSerializable> {
val stdout = callWithArgs(toolchain.workingDirectory(project), "env")?.stdout ?: return null val stdout = callWithArgs(toolchain.workingDirectory(project), "env").getOrElse { throwable -> return Result.failure(throwable) }.stdout
return try { return try {
envJson.decodeFromString<ZigToolchainEnvironmentSerializable>(stdout) Result.success(envJson.decodeFromString<ZigToolchainEnvironmentSerializable>(stdout))
} catch (e: SerializationException) { } catch (e: SerializationException) {
null Result.failure(IllegalStateException("could not deserialize zig env", e))
} }
} }
} }

View file

@ -23,52 +23,26 @@
package com.falsepattern.zigbrains.project.toolchain.tools package com.falsepattern.zigbrains.project.toolchain.tools
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain 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.configurations.GeneralCommandLine
import com.intellij.execution.process.ProcessOutput 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 java.nio.file.Path
import kotlin.io.path.isRegularFile
abstract class ZigTool(val toolchain: AbstractZigToolchain) { abstract class ZigTool(val toolchain: AbstractZigToolchain) {
abstract val toolName: String abstract val toolName: String
suspend fun callWithArgs(workingDirectory: Path?, vararg parameters: String, timeoutMillis: Long = Long.MAX_VALUE): ProcessOutput? { suspend fun callWithArgs(workingDirectory: Path?, vararg parameters: String, timeoutMillis: Long = Long.MAX_VALUE): Result<ProcessOutput> {
val cli = createBaseCommandLine(workingDirectory, *parameters) ?: return null val cli = createBaseCommandLine(workingDirectory, *parameters).let { it.getOrElse { return Result.failure(it) } }
return cli.call(timeoutMillis)
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
)
}
} }
private suspend fun createBaseCommandLine( private suspend fun createBaseCommandLine(
workingDirectory: Path?, workingDirectory: Path?,
vararg parameters: String vararg parameters: String
): GeneralCommandLine? { ): Result<GeneralCommandLine> {
val exe = toolchain.pathToExecutable(toolName) val exe = toolchain.pathToExecutable(toolName)
if (!exe.isRegularFile()) return createCommandLineSafe(workingDirectory, exe, *parameters)
return null .mapCatching { toolchain.patchCommandLine(it) }
val cli = GeneralCommandLine()
.withExePath(exe.toString())
.withWorkingDirectory(workingDirectory)
.withParameters(*parameters)
.withCharset(Charsets.UTF_8)
return toolchain.patchCommandLine(cli)
} }
} }

View file

@ -23,8 +23,19 @@
package com.falsepattern.zigbrains.shared.cli package com.falsepattern.zigbrains.shared.cli
import com.falsepattern.zigbrains.ZigBrainsBundle 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.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 java.util.*
import kotlin.io.path.exists
import kotlin.io.path.isDirectory
import kotlin.io.path.pathString
//From Apache Ant //From Apache Ant
@ -100,4 +111,40 @@ fun coloredCliFlags(colored: Boolean, debug: Boolean): List<String> {
} else { } else {
listOf("--color", if (colored) "on" else "off") listOf("--color", if (colored) "on" else "off")
} }
}
fun createCommandLineSafe(
workingDirectory: Path?,
exe: Path,
vararg parameters: String,
): Result<GeneralCommandLine> {
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<ProcessOutput> {
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
))
}
} }

View file

@ -105,7 +105,7 @@ configuration.build.marker-name=Build and Run
settings.project.group.title=Zig Settings settings.project.group.title=Zig Settings
settings.project.label.direnv=Use direnv settings.project.label.direnv=Use direnv
settings.project.label.toolchain=Toolchain location 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.override-std=Override standard library
settings.project.label.std-location=Standard library location settings.project.label.std-location=Standard library location
build.tool.window.tree.steps.label=Steps build.tool.window.tree.steps.label=Steps

View file

@ -1,7 +1,7 @@
pluginName=ZigBrains pluginName=ZigBrains
pluginRepositoryUrl=https://github.com/FalsePattern/ZigBrains pluginRepositoryUrl=https://github.com/FalsePattern/ZigBrains
pluginVersion=21.1.0 pluginVersion=22.0.0
pluginSinceBuild=242 pluginSinceBuild=242
pluginUntilBuild=242.* pluginUntilBuild=242.*

View file

@ -26,8 +26,12 @@ import com.falsepattern.zigbrains.direnv.DirenvCmd
import com.falsepattern.zigbrains.direnv.emptyEnv import com.falsepattern.zigbrains.direnv.emptyEnv
import com.falsepattern.zigbrains.direnv.getDirenv import com.falsepattern.zigbrains.direnv.getDirenv
import com.falsepattern.zigbrains.lsp.settings.zlsSettings import com.falsepattern.zigbrains.lsp.settings.zlsSettings
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 kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.io.path.pathString import kotlin.io.path.pathString
class ZLSStartup: ProjectActivity { class ZLSStartup: ProjectActivity {
@ -43,5 +47,16 @@ class ZLSStartup: ProjectActivity {
project.zlsSettings.state = zlsState 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)
}
}
} }
} }

View file

@ -71,15 +71,43 @@ class ZigLanguageServerFactory: LanguageServerFactory, LanguageServerEnablementS
return features return features
} }
override fun isEnabled(project: Project): Boolean { override fun isEnabled(project: Project) = project.zlsEnabledSync()
return (project.getUserData(ENABLED_KEY) != false) && project.zlsSettings.validate()
}
override fun setEnabled(enabled: Boolean, project: Project) { 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<LanguageServerManager>()
val status = manager.getServerStatus("ZigBrains")
return status == ServerStatus.started || status == ServerStatus.starting
}
class ZLSStarter: LanguageServerStarter { class ZLSStarter: LanguageServerStarter {
override fun startLSP(project: Project, restart: Boolean) { override fun startLSP(project: Project, restart: Boolean) {
project.zigCoroutineScope.launch { project.zigCoroutineScope.launch {
@ -87,7 +115,10 @@ class ZLSStarter: LanguageServerStarter {
val status = manager.getServerStatus("ZigBrains") val status = manager.getServerStatus("ZigBrains")
if ((status == ServerStatus.started || status == ServerStatus.starting) && !restart) if ((status == ServerStatus.started || status == ServerStatus.starting) && !restart)
return@launch return@launch
manager.start("ZigBrains") manager.stop("ZigBrains")
if (project.zlsSettings.validateAsync()) {
manager.start("ZigBrains")
}
} }
} }

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<in FileEditor, out JComponent?>? {
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 }
}
}
}

View file

@ -25,6 +25,7 @@ package com.falsepattern.zigbrains.lsp.settings
import com.falsepattern.zigbrains.direnv.emptyEnv import com.falsepattern.zigbrains.direnv.emptyEnv
import com.falsepattern.zigbrains.direnv.getDirenv import com.falsepattern.zigbrains.direnv.getDirenv
import com.falsepattern.zigbrains.lsp.ZLSBundle import com.falsepattern.zigbrains.lsp.ZLSBundle
import com.falsepattern.zigbrains.lsp.startLSP
import com.intellij.openapi.components.* import com.intellij.openapi.components.*
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.util.io.toNioPathOrNull 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.platform.ide.progress.runWithModalProgressBlocking
import com.intellij.util.application import com.intellij.util.application
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.nio.file.Path import java.nio.file.Path
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
import kotlin.io.path.isExecutable import kotlin.io.path.isExecutable
import kotlin.io.path.isRegularFile import kotlin.io.path.isRegularFile
@ -51,47 +52,45 @@ class ZLSProjectSettingsService(val project: Project): PersistentStateComponent<
@Volatile @Volatile
private var valid = false private var valid = false
private val mutex = ReentrantLock() private val mutex = Mutex()
override fun getState(): ZLSSettings { override fun getState(): ZLSSettings {
return state.copy() return state.copy()
} }
fun setState(value: ZLSSettings) { fun setState(value: ZLSSettings) {
mutex.withLock { runBlocking {
this.state = value mutex.withLock {
dirty = true this@ZLSProjectSettingsService.state = value
dirty = true
}
} }
startLSP(project, true)
} }
override fun loadState(state: ZLSSettings) { override fun loadState(state: ZLSSettings) {
mutex.withLock { setState(state)
this.state = state
dirty = true
}
} }
fun isModified(otherData: ZLSSettings): Boolean { suspend fun validateAsync(): Boolean {
return state != otherData
}
fun validate(): Boolean {
mutex.withLock { mutex.withLock {
if (dirty) { if (dirty) {
val state = this.state val state = this.state
valid = if (application.isDispatchThread) { valid = doValidate(project, state)
runWithModalProgressBlocking(ModalTaskOwner.project(project), ZLSBundle.message("progress.title.validate")) {
doValidate(project, state)
}
} else {
runBlocking {
doValidate(project, state)
}
}
dirty = false dirty = false
} }
return valid 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 { private suspend fun doValidate(project: Project, state: ZLSSettings): Boolean {

View file

@ -38,7 +38,7 @@ data class ZLSSettings(
val enable_argument_placeholders: Boolean = true, val enable_argument_placeholders: Boolean = true,
val completion_label_details: Boolean = true, val completion_label_details: Boolean = true,
val enable_build_on_save: Boolean = false, val enable_build_on_save: Boolean = false,
val build_on_save_args: List<String> = emptyList(), val build_on_save_args: String = "",
val semantic_tokens: SemanticTokens = SemanticTokens.full, val semantic_tokens: SemanticTokens = SemanticTokens.full,
val inlay_hints_show_variable_type_hints: Boolean = true, val inlay_hints_show_variable_type_hints: Boolean = true,
val inlay_hints_show_struct_literal_field_type: Boolean = true, val inlay_hints_show_struct_literal_field_type: Boolean = true,

View file

@ -24,6 +24,7 @@ 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.shared.cli.translateCommandline
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
class ZLSSettingsConfigProvider: ZLSConfigProvider { class ZLSSettingsConfigProvider: ZLSConfigProvider {
@ -34,7 +35,14 @@ class ZLSSettingsConfigProvider: ZLSConfigProvider {
enable_argument_placeholders = state.enable_argument_placeholders, enable_argument_placeholders = state.enable_argument_placeholders,
completion_label_details = state.completion_label_details, completion_label_details = state.completion_label_details,
enable_build_on_save = state.enable_build_on_save, 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, semantic_tokens = state.semantic_tokens,
inlay_hints_show_variable_type_hints = state.inlay_hints_show_variable_type_hints, 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, inlay_hints_show_struct_literal_field_type = state.inlay_hints_show_struct_literal_field_type,

View file

@ -42,11 +42,7 @@ class ZLSSettingsConfigurable(private val project: Project): SubConfigurable {
override fun apply() { override fun apply() {
val data = appSettingsComponent?.data ?: return val data = appSettingsComponent?.data ?: return
val settings = project.zlsSettings val settings = project.zlsSettings
val reloadZLS = settings.isModified(data)
settings.state = data settings.state = data
if (reloadZLS) {
startLSP(project, true)
}
} }
override fun reset() { override fun reset() {

View file

@ -29,24 +29,38 @@ import com.falsepattern.zigbrains.direnv.getDirenv
import com.falsepattern.zigbrains.lsp.ZLSBundle import com.falsepattern.zigbrains.lsp.ZLSBundle
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.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.launchWithEDT
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
import com.falsepattern.zigbrains.shared.zigCoroutineScope 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.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.ui.ComboBox import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.util.Disposer 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.ModalTaskOwner
import com.intellij.platform.ide.progress.TaskCancellation import com.intellij.platform.ide.progress.TaskCancellation
import com.intellij.platform.ide.progress.withModalProgress 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.JBCheckBox
import com.intellij.ui.components.JBTextArea
import com.intellij.ui.components.fields.ExtendableTextField import com.intellij.ui.components.fields.ExtendableTextField
import com.intellij.ui.components.textFieldWithBrowseButton import com.intellij.ui.components.textFieldWithBrowseButton
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.dsl.builder.Row 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 org.jetbrains.annotations.PropertyKey
import java.lang.IllegalArgumentException import javax.swing.event.DocumentEvent
import java.util.*
import kotlin.io.path.pathString import kotlin.io.path.pathString
@Suppress("PrivatePropertyName") @Suppress("PrivatePropertyName")
@ -55,13 +69,24 @@ class ZLSSettingsPanel(private val project: Project) : ZigProjectConfigurationPr
project, project,
ZLSBundle.message("settings.zls-path.browse.title"), ZLSBundle.message("settings.zls-path.browse.title"),
FileChooserDescriptorFactory.createSingleFileDescriptor(), 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( private val zlsConfigPath = textFieldWithBrowseButton(
project, project,
ZLSBundle.message("settings.zls-config-path.browse.title"), ZLSBundle.message("settings.zls-config-path.browse.title"),
FileChooserDescriptorFactory.createSingleFileDescriptor() FileChooserDescriptorFactory.createSingleFileDescriptor()
).also { Disposer.register(this, it) } ).also { Disposer.register(this, it) }
private val zlsVersion = JBTextArea().also { it.isEditable = false }
private var debounce: Job? = null
private val inlayHints = JBCheckBox() private val inlayHints = JBCheckBox()
private val enable_snippets = JBCheckBox() private val enable_snippets = JBCheckBox()
private val enable_argument_placeholders = JBCheckBox() private val enable_argument_placeholders = JBCheckBox()
@ -102,6 +127,9 @@ class ZLSSettingsPanel(private val project: Project) : ZigProjectConfigurationPr
cell(direnv) cell(direnv)
} }
} }
row(ZLSBundle.message("settings.zls-version.label")) {
cell(zlsVersion)
}
fancyRow( fancyRow(
"settings.zls-config-path.label", "settings.zls-config-path.label",
"settings.zls-config-path.tooltip" "settings.zls-config-path.tooltip"
@ -207,14 +235,7 @@ class ZLSSettingsPanel(private val project: Project) : ZigProjectConfigurationPr
enable_argument_placeholders.isSelected, enable_argument_placeholders.isSelected,
completion_label_details.isSelected, completion_label_details.isSelected,
enable_build_on_save.isSelected, enable_build_on_save.isSelected,
run { build_on_save_args.text,
val args = build_on_save_args.text ?: ""
return@run if (args.isEmpty()) {
emptyList()
} else {
translateCommandline(args).toList()
}
},
semantic_tokens.item ?: SemanticTokens.full, semantic_tokens.item ?: SemanticTokens.full,
inlay_hints_show_variable_type_hints.isSelected, inlay_hints_show_variable_type_hints.isSelected,
inlay_hints_show_struct_literal_field_type.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 enable_argument_placeholders.isSelected = value.enable_argument_placeholders
completion_label_details.isSelected = value.completion_label_details completion_label_details.isSelected = value.completion_label_details
enable_build_on_save.isSelected = value.enable_build_on_save 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 semantic_tokens.item = value.semantic_tokens
inlay_hints_show_variable_type_hints.isSelected = value.inlay_hints_show_variable_type_hints 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_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 ?: "" builtin_path.text = value.builtin_path ?: ""
build_runner_path.text = value.build_runner_path ?: "" build_runner_path.text = value.build_runner_path ?: ""
global_cache_path.text = value.global_cache_path ?: "" global_cache_path.text = value.global_cache_path ?: ""
dispatchUpdateUI()
} }
private fun dispatchAutodetect(force: Boolean) { private fun dispatchAutodetect(force: Boolean) {
@ -272,12 +294,14 @@ class ZLSSettingsPanel(private val project: Project) : ZigProjectConfigurationPr
getDirenv().findExecutableOnPATH("zls")?.let { getDirenv().findExecutableOnPATH("zls")?.let {
if (force || zlsPath.text.isBlank()) { if (force || zlsPath.text.isBlank()) {
zlsPath.text = it.pathString zlsPath.text = it.pathString
dispatchUpdateUI()
} }
} }
} }
} }
override fun dispose() { override fun dispose() {
debounce?.cancel("Disposed")
} }
private suspend fun getDirenv(): Env { private suspend fun getDirenv(): Env {
@ -285,6 +309,42 @@ class ZLSSettingsPanel(private val project: Project) : ZigProjectConfigurationPr
return project.getDirenv() return project.getDirenv()
return emptyEnv 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( private fun Panel.fancyRow(
@ -294,80 +354,4 @@ private fun Panel.fancyRow(
) = row(ZLSBundle.message(label)) { ) = row(ZLSBundle.message(label)) {
contextHelp(ZLSBundle.message(tooltip)) contextHelp(ZLSBundle.message(tooltip))
cb() cb()
} }
@Throws(Exception::class)
private fun translateCommandline(toProcess: String): List<String> {
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<String>()
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")
}
}

View file

@ -39,12 +39,11 @@ class ToolchainZLSConfigProvider: SuspendingZLSConfigProvider {
var state = svc.state var state = svc.state
val toolchain = state.toolchain ?: ZigToolchainProvider.suggestToolchain(project, UserDataHolderBase()) ?: return previous val toolchain = state.toolchain ?: ZigToolchainProvider.suggestToolchain(project, UserDataHolderBase()) ?: return previous
val env = toolchain.zig.getEnv(project) val env = toolchain.zig.getEnv(project).getOrElse { throwable ->
throwable.printStackTrace()
if (env == null) {
Notification( Notification(
"zigbrains-lsp", "zigbrains-lsp",
"Failed to evaluate zig env", "Failed to evaluate \"zig env\": ${throwable.message}",
NotificationType.ERROR NotificationType.ERROR
).notify(project) ).notify(project)
return previous return previous

View file

@ -45,6 +45,10 @@
/> />
<postStartupActivity <postStartupActivity
implementation="com.falsepattern.zigbrains.lsp.ZLSStartup"/> implementation="com.falsepattern.zigbrains.lsp.ZLSStartup"/>
<editorNotificationProvider
implementation="com.falsepattern.zigbrains.lsp.notification.ZigEditorNotificationProvider"
/>
</extensions> </extensions>
<extensions defaultExtensionNs="com.falsepattern.zigbrains"> <extensions defaultExtensionNs="com.falsepattern.zigbrains">

View file

@ -1,7 +1,8 @@
settings.group.title=ZLS Settings settings.group.title=ZLS Settings
settings.zls-path.label=Executable path 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-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.label=Config path
settings.zls-config-path.tooltip=Leave empty to use built-in config generated from the settings below 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) 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-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-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.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.create-connection-provider=Creating ZLS connection provider
progress.title.validate=Validating ZLS progress.title.validate=Validating ZLS
# suppress inspection "UnusedProperty" # suppress inspection "UnusedProperty"

View file

@ -5,7 +5,7 @@
<depends config-file="zigbrains-core.xml">com.intellij.modules.platform</depends> <depends config-file="zigbrains-core.xml">com.intellij.modules.platform</depends>
<depends config-file="zigbrains-lsp.xml">com.redhat.devtools.lsp4ij</depends> <depends config-file="zigbrains-lsp.xml">com.redhat.devtools.lsp4ij</depends>
<depends config-file="zigbrains-debugger.xml" optional="true">com.intellij.nativeDebug</depends> <depends config-file="zigbrains-debugger.xml" optional="true">com.intellij.modules.cidr.debugger</depends>
<depends config-file="zigbrains-cidr.xml" optional="true">com.intellij.cidr.base</depends> <depends config-file="zigbrains-cidr.xml" optional="true">com.intellij.cidr.base</depends>
<depends config-file="zigbrains-clion.xml" optional="true">com.intellij.clion</depends> <depends config-file="zigbrains-clion.xml" optional="true">com.intellij.clion</depends>