feat: Descriptive zig environment errors
This commit is contained in:
parent
91ee38e922
commit
d4a1b69172
9 changed files with 60 additions and 47 deletions
|
@ -22,6 +22,9 @@ Changelog structure reference:
|
||||||
- LSP
|
- LSP
|
||||||
- Error/Warning banner at the top of the editor when ZLS is misconfigured/not running
|
- 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
|
### Fixed
|
||||||
|
|
||||||
- Debugging
|
- Debugging
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
@ -67,7 +67,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) {
|
||||||
|
@ -159,35 +159,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 ?: ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -132,7 +132,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"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +156,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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,13 +31,15 @@ import kotlinx.coroutines.runInterruptible
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import java.nio.file.Path
|
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 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) } }
|
||||||
|
|
||||||
val (process, exitCode) = withContext(Dispatchers.IO) {
|
val (process, exitCode) = withContext(Dispatchers.IO) {
|
||||||
val process = cli.createProcess()
|
val process = cli.createProcess()
|
||||||
|
@ -47,28 +49,30 @@ abstract class ZigTool(val toolchain: AbstractZigToolchain) {
|
||||||
process to exit
|
process to exit
|
||||||
}
|
}
|
||||||
return runInterruptible {
|
return runInterruptible {
|
||||||
ProcessOutput(
|
Result.success(ProcessOutput(
|
||||||
process.inputStream.bufferedReader().use { it.readText() },
|
process.inputStream.bufferedReader().use { it.readText() },
|
||||||
process.errorStream.bufferedReader().use { it.readText() },
|
process.errorStream.bufferedReader().use { it.readText() },
|
||||||
exitCode ?: -1,
|
exitCode ?: -1,
|
||||||
exitCode == null,
|
exitCode == null,
|
||||||
false
|
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())
|
if (!exe.exists())
|
||||||
return null
|
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()
|
val cli = GeneralCommandLine()
|
||||||
.withExePath(exe.toString())
|
.withExePath(exe.toString())
|
||||||
.withWorkingDirectory(workingDirectory)
|
.withWorkingDirectory(workingDirectory)
|
||||||
.withParameters(*parameters)
|
.withParameters(*parameters)
|
||||||
.withCharset(Charsets.UTF_8)
|
.withCharset(Charsets.UTF_8)
|
||||||
return toolchain.patchCommandLine(cli)
|
return Result.success(toolchain.patchCommandLine(cli))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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 toolchain 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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue