diff --git a/CHANGELOG.md b/CHANGELOG.md index cd2b9630..2c0769a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ Changelog structure reference: - LSP - Error/Warning banner at the top of the editor when ZLS is misconfigured/not running +- Toolchain + - More descriptive error messages when toolchain detection fails + ### Fixed - Debugging 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 80d1dae8..d787e0bd 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 @@ -67,7 +67,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) { @@ -159,35 +159,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/toolchain/stdlib/ZigSyntheticLibrary.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/stdlib/ZigSyntheticLibrary.kt index 40d3f088..3ac2ad89 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 @@ -132,7 +132,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" } @@ -156,7 +156,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..3e75d32b 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 @@ -31,13 +31,15 @@ import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull import java.nio.file.Path -import kotlin.io.path.isRegularFile +import kotlin.io.path.exists +import kotlin.io.path.isDirectory +import kotlin.io.path.pathString 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 + 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) } } val (process, exitCode) = withContext(Dispatchers.IO) { val process = cli.createProcess() @@ -47,28 +49,30 @@ abstract class ZigTool(val toolchain: AbstractZigToolchain) { process to exit } return runInterruptible { - ProcessOutput( + Result.success(ProcessOutput( process.inputStream.bufferedReader().use { it.readText() }, process.errorStream.bufferedReader().use { it.readText() }, exitCode ?: -1, exitCode == null, false - ) + )) } } private suspend fun createBaseCommandLine( workingDirectory: Path?, vararg parameters: String - ): GeneralCommandLine? { + ): Result { val exe = toolchain.pathToExecutable(toolName) - if (!exe.isRegularFile()) - return null + 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 toolchain.patchCommandLine(cli) + return Result.success(toolchain.patchCommandLine(cli)) } } \ 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..5876891d 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 toolchain 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/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