diff --git a/build.gradle.kts b/build.gradle.kts index 817e703e..df498520 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,9 @@ +import nl.adaptivity.xmlutil.core.impl.multiplatform.name import org.jetbrains.changelog.Changelog import org.jetbrains.changelog.markdownToHTML import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType +import org.jetbrains.intellij.platform.gradle.tasks.ComposedJarTask +import org.jetbrains.intellij.platform.gradle.tasks.InstrumentedJarTask import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension plugins { @@ -14,6 +17,7 @@ plugins { val javaVersion = property("javaVersion").toString().toInt() val lsp4ijVersion: String by project +val runIdeTarget: String by project val lsp4ijNightly = property("lsp4ijNightly").toString().toBoolean() val lsp4ijPluginString = "com.redhat.devtools.lsp4ij:$lsp4ijVersion${if (lsp4ijNightly) "@nightly" else ""}" @@ -74,7 +78,10 @@ allprojects { dependencies { intellijPlatform { - create(IntelliJPlatformType.IntellijIdeaCommunity, providers.gradleProperty("ideaCommunityVersion")) + when(runIdeTarget) { + "ideaCommunity" -> create(IntelliJPlatformType.IntellijIdeaCommunity, providers.gradleProperty("ideaCommunityVersion")) + "clion" -> create(IntelliJPlatformType.CLion, providers.gradleProperty("clionVersion")) + } pluginVerifier() zipSigner() @@ -82,6 +89,7 @@ dependencies { } implementation(project(":core")) + implementation(project(":cidr")) } intellijPlatform { diff --git a/gradle.properties b/gradle.properties index b4d04056..732623cc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,19 +1,22 @@ -pluginName = ZigBrains -pluginRepositoryUrl = https://github.com/FalsePattern/ZigBrains +pluginName=ZigBrains +pluginRepositoryUrl=https://github.com/FalsePattern/ZigBrains -pluginVersion = 20.0.0-dev +pluginVersion=20.0.0-dev -pluginSinceBuild = 242 -pluginUntilBuild = +pluginSinceBuild=242 +pluginUntilBuild= -ideaCommunityVersion = 2024.2.4 +ideaCommunityVersion=2024.2.4 +clionVersion=2024.2.3 javaVersion=21 +# ideaCommunity / clion +runIdeTarget=clion lsp4jVersion=0.21.1 lsp4ijVersion=0.7.0 lsp4ijNightly=false -kotlin.stdlib.default.dependency = false +kotlin.stdlib.default.dependency=false kotlin.code.style=official -org.gradle.configuration-cache = true -org.gradle.caching = true \ No newline at end of file +org.gradle.configuration-cache=true +org.gradle.caching=true \ No newline at end of file diff --git a/modules/cidr/build.gradle.kts b/modules/cidr/build.gradle.kts new file mode 100644 index 00000000..45a98005 --- /dev/null +++ b/modules/cidr/build.gradle.kts @@ -0,0 +1,16 @@ +import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType + +val lsp4jVersion: String by project + +dependencies { + intellijPlatform { + create(IntelliJPlatformType.CLion, providers.gradleProperty("clionVersion")) + bundledPlugins("com.intellij.clion", "com.intellij.cidr.lang", "com.intellij.cidr.base", "com.intellij.nativeDebug") + } + implementation(project(":core")) + implementation("org.eclipse.lsp4j:org.eclipse.lsp4j.debug:$lsp4jVersion") { + exclude("org.eclipse.lsp4j", "org.eclipse.lsp4j") + exclude("org.eclipse.lsp4j", "org.eclipse.lsp4j.jsonrpc") + exclude("com.google.code.gson", "gson") + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugbridge/ZigDebuggerDriverConfigurationProvider.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugbridge/ZigDebuggerDriverConfigurationProvider.kt new file mode 100644 index 00000000..480b2536 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugbridge/ZigDebuggerDriverConfigurationProvider.kt @@ -0,0 +1,34 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugbridge + +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.openapi.project.Project +import kotlinx.coroutines.CoroutineScope + +interface ZigDebuggerDriverConfigurationProvider { + companion object { + val EXTENSION_POINT_NAME = ExtensionPointName.create("com.falsepattern.zigbrains.debuggerDriverProvider") + } + suspend fun getDebuggerConfiguration(project: Project, isElevated: Boolean, emulateTerminal: Boolean, klass: Class): T? +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/DebuggerFeatures.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/DebuggerFeatures.kt new file mode 100644 index 00000000..0a47ff25 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/DebuggerFeatures.kt @@ -0,0 +1,31 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger + +import com.falsepattern.zigbrains.shared.ZBFeatures + +class DebuggerFeatures: ZBFeatures { + override fun getDebug(): Boolean { + return true + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigDebugBundle.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigDebugBundle.kt new file mode 100644 index 00000000..56f363d0 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigDebugBundle.kt @@ -0,0 +1,51 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger + +import com.intellij.DynamicBundle +import org.jetbrains.annotations.Nls +import org.jetbrains.annotations.NonNls +import org.jetbrains.annotations.PropertyKey +import java.util.function.Supplier + + +internal object ZigDebugBundle { + @NonNls + const val BUNDLE = "zigbrains.debugger.Bundle" + + private val INSTANCE = DynamicBundle(ZigDebugBundle::class.java, BUNDLE) + + fun message( + key: @PropertyKey(resourceBundle = BUNDLE) String, + vararg params: Any + ): @Nls String { + return INSTANCE.getMessage(key, *params) + } + + fun lazyMessage( + key: @PropertyKey(resourceBundle = BUNDLE) String, + vararg params: Any + ): Supplier<@Nls String> { + return INSTANCE.getLazyMessage(key, *params) + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigDebuggerEditorsExtension.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigDebuggerEditorsExtension.kt new file mode 100644 index 00000000..bf0b1b98 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigDebuggerEditorsExtension.kt @@ -0,0 +1,28 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger + +import com.jetbrains.cidr.execution.debugger.CidrDebuggerEditorsExtensionBase + +class ZigDebuggerEditorsExtension: CidrDebuggerEditorsExtensionBase() { +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigDebuggerLanguage.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigDebuggerLanguage.kt new file mode 100644 index 00000000..1202a1dc --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigDebuggerLanguage.kt @@ -0,0 +1,31 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger + +import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriver.DebuggerLanguage + +object ZigDebuggerLanguage: DebuggerLanguage { + override fun toString(): String { + return "Zig" + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigDebuggerLanguageSupport.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigDebuggerLanguageSupport.kt new file mode 100644 index 00000000..496bba9f --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigDebuggerLanguageSupport.kt @@ -0,0 +1,41 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger + +import com.falsepattern.zigbrains.project.execution.base.ZigExecConfig +import com.intellij.execution.configurations.RunProfile +import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider +import com.jetbrains.cidr.execution.debugger.CidrDebuggerLanguageSupport +import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriver + +class ZigDebuggerLanguageSupport: CidrDebuggerLanguageSupport() { + override fun getSupportedDebuggerLanguages(): Set { + return setOf(ZigDebuggerLanguage) + } + + override fun createEditor(profile: RunProfile?): XDebuggerEditorsProvider? { + if (profile !is ZigExecConfig<*>) + return null + return createEditorProvider() + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigDefaultDebuggerDriverConfigurationProvider.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigDefaultDebuggerDriverConfigurationProvider.kt new file mode 100644 index 00000000..509f9bf6 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigDefaultDebuggerDriverConfigurationProvider.kt @@ -0,0 +1,164 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger + +import com.falsepattern.zigbrains.debugbridge.ZigDebuggerDriverConfigurationProvider +import com.falsepattern.zigbrains.debugger.settings.ZigDebuggerSettings +import com.falsepattern.zigbrains.debugger.toolchain.* +import com.falsepattern.zigbrains.debugger.win.MSVCDriverConfiguration +import com.falsepattern.zigbrains.shared.coroutine.withEDTContext +import com.falsepattern.zigbrains.zig.ZigLanguage +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DoNotAskOption +import com.intellij.openapi.ui.MessageDialogBuilder +import com.intellij.openapi.ui.Messages +import com.jetbrains.cidr.ArchitectureType +import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration +import com.jetbrains.cidr.execution.debugger.backend.gdb.GDBDriverConfiguration +import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBDriverConfiguration +import java.io.File +import kotlin.io.path.pathString + +class ZigDefaultDebuggerDriverConfigurationProvider: ZigDebuggerDriverConfigurationProvider { + override suspend fun getDebuggerConfiguration(project: Project, isElevated: Boolean, emulateTerminal: Boolean, klass: Class): T? { + if (klass != DebuggerDriverConfiguration::class.java) + return null + @Suppress("UNCHECKED_CAST") + return getDebuggerConfiguration(project, isElevated, emulateTerminal) as T + } + + private suspend fun getDebuggerConfiguration(project: Project, isElevated: Boolean, emulateTerminal: Boolean): DebuggerDriverConfiguration? { + val settings = ZigDebuggerSettings.instance + val service = zigDebuggerToolchainService + val kind = settings.debuggerKind + if (!availabilityCheck(project, kind)) + return null + + return when(val availability = service.debuggerAvailability(kind)) { + DebuggerAvailability.Bundled -> when(kind) { + DebuggerKind.LLDB -> ZigLLDBDriverConfiguration(isElevated, emulateTerminal) + DebuggerKind.GDB -> ZigGDBDriverConfiguration(isElevated, emulateTerminal) + DebuggerKind.MSVC -> throw AssertionError("MSVC is never bundled") + } + is DebuggerAvailability.Binaries -> when(val binary = availability.binaries) { + is LLDBBinaries -> ZigCustomBinariesLLDBDriverConfiguration(binary, isElevated, emulateTerminal) + is GDBBinaries -> ZigCustomBinariesGDBDriverConfiguration(binary, isElevated, emulateTerminal) + is MSVCBinaries -> ZigMSVCDriverConfiguration(binary, isElevated, emulateTerminal) + else -> throw AssertionError("Unreachable") + } + DebuggerAvailability.Unavailable, + DebuggerAvailability.NeedToDownload, + DebuggerAvailability.NeedToUpdate -> throw AssertionError("Unreachable") + } + } +} + +private suspend fun availabilityCheck(project: Project, kind: DebuggerKind): Boolean { + val service = zigDebuggerToolchainService + val availability = service.debuggerAvailability(kind) + val (message, action) = when(availability) { + DebuggerAvailability.Unavailable -> return false + DebuggerAvailability.NeedToDownload -> + ZigDebugBundle.message("debugger.run.unavailable.reason.download") to ZigDebugBundle.message("debugger.run.unavailable.reason.download.button") + DebuggerAvailability.NeedToUpdate -> + ZigDebugBundle.message("debugger.run.unavailable.reason.update") to ZigDebugBundle.message("debugger.run.unavailable.reason.update.button") + DebuggerAvailability.Bundled, + is DebuggerAvailability.Binaries -> return true + } + + val downloadDebugger = if (!ZigDebuggerSettings.instance.downloadAutomatically) { + showDialog(project, message, action) + } else { + true + } + + if (downloadDebugger) { + val result = withEDTContext { + service.downloadDebugger(project, kind) + } + if (result is ZigDebuggerToolchainService.DownloadResult.Ok) { + return true + } + } + return false +} + +private suspend fun showDialog(project: Project, message: String, action: String): Boolean { + val doNotAsk = object: DoNotAskOption.Adapter() { + override fun rememberChoice(isSelected: Boolean, exitCode: Int) { + if (exitCode == Messages.OK) { + ZigDebuggerSettings.instance.downloadAutomatically = isSelected + } + } + } + + return withEDTContext { + MessageDialogBuilder + .okCancel(ZigDebugBundle.message("debugger.run.unavailable"), message) + .yesText(action) + .icon(Messages.getErrorIcon()) + .doNotAsk(doNotAsk) + .ask(project) + } +} + +private open class ZigLLDBDriverConfiguration(private val isElevated: Boolean, private val emulateTerminal: Boolean): LLDBDriverConfiguration() { + override fun getDriverName() = "Zig LLDB" + override fun isElevated() = isElevated + override fun emulateTerminal() = emulateTerminal +} +private class ZigCustomBinariesLLDBDriverConfiguration( + private val binaries: LLDBBinaries, + isElevated: Boolean, + emulateTerminal: Boolean +) : ZigLLDBDriverConfiguration(isElevated, emulateTerminal) { + override fun useSTLRenderers() = false + override fun getLLDBFrameworkFile(architectureType: ArchitectureType): File = binaries.frameworkFile.toFile() + override fun getLLDBFrontendFile(architectureType: ArchitectureType): File = binaries.frontendFile.toFile() +} + +private open class ZigGDBDriverConfiguration(private val isElevated: Boolean, private val emulateTerminal: Boolean): GDBDriverConfiguration() { + override fun getDriverName() = "Zig GDB" + override fun isAttachSupported() = false + override fun isElevated() = isElevated + override fun emulateTerminal() = emulateTerminal +} +private class ZigCustomBinariesGDBDriverConfiguration( + private val binaries: GDBBinaries, + isElevated: Boolean, + emulateTerminal: Boolean +) : ZigGDBDriverConfiguration(isElevated, emulateTerminal) { + override fun getGDBExecutablePath() = binaries.gdbFile.pathString +} + +private class ZigMSVCDriverConfiguration( + private val binaries: MSVCBinaries, + private val isElevated: Boolean, + private val emulateTerminal: Boolean +): MSVCDriverConfiguration() { + override val debuggerExecutable get() = binaries.msvcFile + override fun getDriverName() = "Zig MSVC" + override fun getConsoleLanguage() = ZigLanguage + override fun isElevated() = isElevated + override fun emulateTerminal() = emulateTerminal +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigLineBreakpointFileTypesProvider.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigLineBreakpointFileTypesProvider.kt new file mode 100644 index 00000000..eda6db26 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigLineBreakpointFileTypesProvider.kt @@ -0,0 +1,33 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger + +import com.falsepattern.zigbrains.zig.ZigFileType +import com.intellij.openapi.fileTypes.FileType +import com.jetbrains.cidr.execution.debugger.breakpoints.CidrLineBreakpointFileTypesProvider + +class ZigLineBreakpointFileTypesProvider: CidrLineBreakpointFileTypesProvider { + override fun getFileTypes(): Set { + return setOf(ZigFileType) + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigLocalDebugProcess.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigLocalDebugProcess.kt new file mode 100644 index 00000000..58a74ae5 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigLocalDebugProcess.kt @@ -0,0 +1,30 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger + +import com.intellij.execution.filters.TextConsoleBuilder +import com.intellij.xdebugger.XDebugSession +import com.jetbrains.cidr.execution.RunParameters +import com.jetbrains.cidr.execution.debugger.CidrLocalDebugProcess + +class ZigLocalDebugProcess(parameters: RunParameters, session: XDebugSession, consoleBuilder: TextConsoleBuilder) : CidrLocalDebugProcess(parameters, session, consoleBuilder) \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigLocalVariablesFilterHandler.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigLocalVariablesFilterHandler.kt new file mode 100644 index 00000000..2a0a82b7 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigLocalVariablesFilterHandler.kt @@ -0,0 +1,49 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger + +import com.falsepattern.zigbrains.shared.zigCoroutineScope +import com.falsepattern.zigbrains.zig.ZigFileType +import com.intellij.openapi.project.Project +import com.intellij.xdebugger.XSourcePosition +import com.jetbrains.cidr.execution.debugger.backend.LLValue +import com.jetbrains.cidr.execution.debugger.evaluation.LocalVariablesFilterHandler +import kotlinx.coroutines.async +import kotlinx.coroutines.future.asCompletableFuture +import java.util.concurrent.CompletableFuture + +class ZigLocalVariablesFilterHandler: LocalVariablesFilterHandler { + override fun filterVars(proj: Project, pos: XSourcePosition, vars: List): CompletableFuture> { + return proj.zigCoroutineScope.async { + val vf = pos.file + if (vf.fileType == ZigFileType) { + return@async ArrayList(vars) + } + return@async listOf() + }.asCompletableFuture() + } + + override fun canFilterAtPos(proj: Project, pos: XSourcePosition): Boolean { + return pos.file.fileType == ZigFileType + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/dap/DAPDebuggerDriverConfiguration.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/dap/DAPDebuggerDriverConfiguration.kt new file mode 100644 index 00000000..7fedc341 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/dap/DAPDebuggerDriverConfiguration.kt @@ -0,0 +1,43 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.dap + +import com.falsepattern.zigbrains.zig.ZigLanguage +import com.intellij.lang.Language +import com.intellij.openapi.util.Expirable +import com.intellij.openapi.util.Pair +import com.intellij.openapi.util.UserDataHolderEx +import com.jetbrains.cidr.execution.debugger.backend.* +import org.eclipse.lsp4j.debug.InitializeRequestArguments + +abstract class DAPDebuggerDriverConfiguration: DebuggerDriverConfiguration() { + override fun createEvaluationContext(driver: DebuggerDriver, expirable: Expirable?, llThread: LLThread, llFrame: LLFrame, data: UserDataHolderEx): EvaluationContext { + return object : EvaluationContext(driver, expirable, llThread, llFrame, data) { + override fun convertToRValue(data: LLValueData, pair: Pair): String { + return cast(pair.second, pair.first?.type) + } + } + } + + abstract fun customizeInitializeArguments(initArgs: InitializeRequestArguments) +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/execution/binary/ZigConfigTypeBinary.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/execution/binary/ZigConfigTypeBinary.kt new file mode 100644 index 00000000..774f847d --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/execution/binary/ZigConfigTypeBinary.kt @@ -0,0 +1,52 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.execution.binary + +import com.falsepattern.zigbrains.Icons +import com.falsepattern.zigbrains.debugger.ZigDebugBundle +import com.intellij.execution.configurations.ConfigurationFactory +import com.intellij.execution.configurations.ConfigurationTypeBase +import com.intellij.execution.configurations.RunConfiguration +import com.intellij.openapi.project.Project + +class ZigConfigTypeBinary: ConfigurationTypeBase( + IDENTIFIER, + ZigDebugBundle.message("configuration.binary.name"), + ZigDebugBundle.message("configuration.binary.description"), + Icons.ZIG +) { + init { + addFactory(ConfigFactoryBinary(this)) + } + class ConfigFactoryBinary(type: ZigConfigTypeBinary): ConfigurationFactory(type) { + override fun createTemplateConfiguration(project: Project): RunConfiguration { + return ZigExecConfigBinary(project, this) + } + + override fun getId(): String { + return IDENTIFIER + } + } +} + +private const val IDENTIFIER = "ZIGBRAINS_BINARY" \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/execution/binary/ZigExecConfigBinary.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/execution/binary/ZigExecConfigBinary.kt new file mode 100644 index 00000000..9cdb57a7 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/execution/binary/ZigExecConfigBinary.kt @@ -0,0 +1,59 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.execution.binary + +import com.falsepattern.zigbrains.debugger.ZigDebugBundle +import com.falsepattern.zigbrains.project.execution.base.* +import com.intellij.execution.Executor +import com.intellij.execution.configurations.ConfigurationFactory +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.openapi.project.Project + +class ZigExecConfigBinary(project: Project, factory: ConfigurationFactory) : ZigExecConfig(project, factory, ZigDebugBundle.message("exec.type.binary.label")) { + var exePath = FilePathConfigurable("exePath", ZigDebugBundle.message("exec.option.label.binary.exe-path")) + private set + var args = ArgsConfigurable("args", ZigDebugBundle.message("exec.option.label.binary.args")) + private set + + override val suggestedName: String + get() = ZigDebugBundle.message("configuration.binary.suggested-name") + + override suspend fun buildCommandLineArgs(debug: Boolean): List { + return args.args + } + + override fun getConfigurables(): List> { + return super.getConfigurables() + listOf(exePath, args) + } + + override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState { + return ZigProfileStateBinary(environment, this) + } + + override fun clone(): ZigExecConfigBinary { + val clone = super.clone() + clone.exePath = exePath.clone() + clone.args = args.clone() + return clone + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/execution/binary/ZigProfileStateBinary.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/execution/binary/ZigProfileStateBinary.kt new file mode 100644 index 00000000..b6b1c7fc --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/execution/binary/ZigProfileStateBinary.kt @@ -0,0 +1,43 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.execution.binary + +import com.falsepattern.zigbrains.debugger.ZigDebugBundle +import com.falsepattern.zigbrains.project.execution.base.ZigProfileState +import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.intellij.execution.ExecutionException +import com.intellij.execution.configurations.GeneralCommandLine +import com.intellij.execution.runners.ExecutionEnvironment +import kotlin.io.path.pathString + +class ZigProfileStateBinary(environment: ExecutionEnvironment, configuration: ZigExecConfigBinary) : ZigProfileState(environment, configuration) { + override suspend fun getCommandLine(toolchain: AbstractZigToolchain, debug: Boolean): GeneralCommandLine { + val cli = GeneralCommandLine() + val cfg = configuration + cfg.workingDirectory.path?.let { cli.withWorkingDirectory(it) } + cli.withExePath(cfg.exePath.path?.pathString ?: throw ExecutionException(ZigDebugBundle.message("exception.missing-exe-path"))) + cli.withCharset(Charsets.UTF_8) + cli.addParameters(cfg.args.args) + return cli + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/PreLaunchAware.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/PreLaunchAware.kt new file mode 100644 index 00000000..0ff51606 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/PreLaunchAware.kt @@ -0,0 +1,30 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.runner.base + +import com.intellij.execution.ExecutionException + +interface PreLaunchAware { + @Throws(ExecutionException::class) + suspend fun preLaunch(listener: PreLaunchProcessListener) +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/PreLaunchProcessListener.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/PreLaunchProcessListener.kt new file mode 100644 index 00000000..50d46b5b --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/PreLaunchProcessListener.kt @@ -0,0 +1,57 @@ +package com.falsepattern.zigbrains.debugger.runner.base + +import com.falsepattern.zigbrains.project.run.ZigProcessHandler +import com.intellij.execution.ExecutionException +import com.intellij.execution.configurations.GeneralCommandLine +import com.intellij.execution.process.ProcessEvent +import com.intellij.execution.process.ProcessHandler +import com.intellij.execution.process.ProcessListener +import com.intellij.execution.ui.ConsoleView +import com.intellij.execution.ui.ConsoleViewContentType +import com.intellij.platform.util.progress.withProgressText +import com.intellij.util.io.awaitExit +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runInterruptible +import kotlinx.coroutines.withContext + +class PreLaunchProcessListener(val console: ConsoleView) : ProcessListener { + var isBuildFailed: Boolean = false + private set + lateinit var processHandler: ProcessHandler + private set + + @Throws(ExecutionException::class) + suspend fun executeCommandLineWithHook(commandLine: GeneralCommandLine): Boolean { + return withProgressText(commandLine.commandLineString) { + val processHandler = ZigProcessHandler(commandLine) + this@PreLaunchProcessListener.processHandler = processHandler + hook(processHandler) + processHandler.startNotify() + withContext(Dispatchers.Default) { + processHandler.process.awaitExit() + } + runInterruptible { + processHandler.waitFor() + } + return@withProgressText isBuildFailed + } + } + + fun hook(handler: ProcessHandler) { + console.attachToProcess(handler) + handler.addProcessListener(this) + } + + override fun processTerminated(event: ProcessEvent) { + if (event.exitCode != 0) { + console.print( + "Process finished with exit code " + event.exitCode, + ConsoleViewContentType.NORMAL_OUTPUT + ) + isBuildFailed = true + } else { + isBuildFailed = false + console.print("Build Successful. Starting debug session. \n", ConsoleViewContentType.NORMAL_OUTPUT) + } + } +} diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugEmitBinaryInstaller.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugEmitBinaryInstaller.kt new file mode 100644 index 00000000..d40cfbe8 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugEmitBinaryInstaller.kt @@ -0,0 +1,57 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.runner.base + +import com.falsepattern.zigbrains.project.execution.base.ZigProfileState +import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.falsepattern.zigbrains.shared.coroutine.runModalOrBlocking +import com.falsepattern.zigbrains.shared.zigCoroutineScope +import com.intellij.execution.configurations.GeneralCommandLine +import com.intellij.platform.ide.progress.ModalTaskOwner +import com.jetbrains.cidr.execution.Installer +import kotlinx.coroutines.async +import kotlinx.coroutines.future.asCompletableFuture +import java.io.File + +class ZigDebugEmitBinaryInstaller>( + private val profileState: ProfileState, + private val toolchain: AbstractZigToolchain, + private val executableFile: File, + private val exeArgs: List +): Installer { + override fun install(): GeneralCommandLine { + val cfg = profileState.configuration + val cli = GeneralCommandLine().withExePath(executableFile.absolutePath) + cfg.workingDirectory.path?.let { x -> cli.withWorkingDirectory(x) } + cli.addParameters(exeArgs) + cli.withCharset(Charsets.UTF_8) + cli.withRedirectErrorStream(true) + return profileState.configuration.project.zigCoroutineScope.async{ + profileState.configuration.patchCommandLine(cli, toolchain) + }.asCompletableFuture().join() + } + + override fun getExecutableFile(): File { + return executableFile + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugParametersBase.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugParametersBase.kt new file mode 100644 index 00000000..7cae51d2 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugParametersBase.kt @@ -0,0 +1,44 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.runner.base + +import com.falsepattern.zigbrains.project.execution.base.ZigProfileState +import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.intellij.util.system.CpuArch +import com.jetbrains.cidr.ArchitectureType +import com.jetbrains.cidr.execution.RunParameters +import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration + +abstract class ZigDebugParametersBase>( + private val driverConfiguration: DebuggerDriverConfiguration, + protected val toolchain: AbstractZigToolchain, + protected val profileState: ProfileState +): RunParameters() { + override fun getDebuggerDriverConfiguration(): DebuggerDriverConfiguration { + return driverConfiguration + } + + override fun getArchitectureId(): String { + return ArchitectureType.forVmCpuArch(CpuArch.CURRENT).id + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugParametersEmitBinaryBase.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugParametersEmitBinaryBase.kt new file mode 100644 index 00000000..5b8e8adb --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugParametersEmitBinaryBase.kt @@ -0,0 +1,78 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.runner.base + +import com.falsepattern.zigbrains.debugger.ZigDebugBundle +import com.falsepattern.zigbrains.project.execution.base.ZigProfileState +import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.intellij.execution.ExecutionException +import com.intellij.execution.ui.ConsoleViewContentType +import com.intellij.openapi.util.io.FileUtil +import com.intellij.platform.util.progress.withProgressText +import com.intellij.util.containers.orNull +import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.File +import java.nio.file.Files +import kotlin.io.path.absolutePathString +import kotlin.io.path.isExecutable + +abstract class ZigDebugParametersEmitBinaryBase>( + driverConfiguration: DebuggerDriverConfiguration, + toolchain: AbstractZigToolchain, + profileState: ProfileState, +) : ZigDebugParametersBase(driverConfiguration, toolchain, profileState), PreLaunchAware { + @Volatile + protected lateinit var executableFile: File + private set + + @Throws(ExecutionException::class) + private suspend fun compileExe(listener: PreLaunchProcessListener): File { + val commandLine = profileState.getCommandLine(toolchain, true) + val tmpDir = FileUtil.createTempDirectory("zigbrains_debug", "", true).toPath() + + val exe = tmpDir.resolve("executable") + commandLine.addParameters("-femit-bin=${exe.absolutePathString()}") + + if (listener.executeCommandLineWithHook(commandLine)) + throw ExecutionException(ZigDebugBundle.message("debug.base.compile.failed.generic")) + + return withContext(Dispatchers.IO) { + Files.list(tmpDir).use { stream -> + stream.filter { !it.fileName.endsWith(".o") } + .filter { it.isExecutable() } + .findFirst() + .map { it.toFile() } + .orNull() + } + } ?: throw ExecutionException(ZigDebugBundle.message("debug.base.compile.failed.no-exe")) + } + + @Throws(ExecutionException::class) + override suspend fun preLaunch(listener: PreLaunchProcessListener) { + this.executableFile = withProgressText("Compiling executable") { + compileExe(listener) + } + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugRunnerBase.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugRunnerBase.kt new file mode 100644 index 00000000..0547b1f9 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugRunnerBase.kt @@ -0,0 +1,131 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.runner.base + +import com.falsepattern.zigbrains.debugbridge.ZigDebuggerDriverConfigurationProvider +import com.falsepattern.zigbrains.debugger.ZigLocalDebugProcess +import com.falsepattern.zigbrains.project.execution.base.ZigProfileState +import com.falsepattern.zigbrains.project.run.ZigProgramRunner +import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.falsepattern.zigbrains.shared.coroutine.runInterruptibleEDT +import com.falsepattern.zigbrains.shared.coroutine.withEDTContext +import com.intellij.execution.DefaultExecutionResult +import com.intellij.execution.ExecutionException +import com.intellij.execution.executors.DefaultDebugExecutor +import com.intellij.execution.filters.Filter +import com.intellij.execution.filters.TextConsoleBuilder +import com.intellij.execution.process.ProcessTerminatedListener +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.execution.runners.RunContentBuilder +import com.intellij.execution.ui.ConsoleView +import com.intellij.execution.ui.ConsoleViewContentType +import com.intellij.execution.ui.RunContentDescriptor +import com.intellij.platform.util.progress.reportProgress +import com.intellij.platform.util.progress.withProgressText +import com.intellij.xdebugger.XDebugProcess +import com.intellij.xdebugger.XDebugProcessStarter +import com.intellij.xdebugger.XDebugSession +import com.intellij.xdebugger.XDebuggerManager +import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration +import com.jetbrains.rd.util.string.printToString + +abstract class ZigDebugRunnerBase> : ZigProgramRunner(DefaultDebugExecutor.EXECUTOR_ID) { + @Throws(ExecutionException::class) + override suspend fun execute( + state: ProfileState, + toolchain: AbstractZigToolchain, + environment: ExecutionEnvironment + ): RunContentDescriptor? { + val project = environment.project + val driverProviders = ZigDebuggerDriverConfigurationProvider.EXTENSION_POINT_NAME.extensionList + for (provider in driverProviders) { + val driver = provider.getDebuggerConfiguration(project, isElevated = false, emulateTerminal = false, DebuggerDriverConfiguration::class.java) ?: continue + return executeWithDriver(state, toolchain, environment, driver) ?: continue + } + return null + } + + @Throws(ExecutionException::class) + private suspend fun executeWithDriver( + state: ProfileState, + toolchain: AbstractZigToolchain, + environment: ExecutionEnvironment, + debuggerDriver: DebuggerDriverConfiguration + ): RunContentDescriptor? { + return reportProgress { reporter -> + val runParameters = getDebugParameters(state, debuggerDriver, toolchain) + val console = state.consoleBuilder.console + if (runParameters is PreLaunchAware) { + val listener = PreLaunchProcessListener(console) + try { + reporter.indeterminateStep { + runParameters.preLaunch(listener) + } + } catch (e: ExecutionException) { + console.print("\n", ConsoleViewContentType.ERROR_OUTPUT) + e.message?.let { listener.console.print(it, ConsoleViewContentType.SYSTEM_OUTPUT) } + } + if (listener.isBuildFailed) { + val executionResult = DefaultExecutionResult(console, listener.processHandler) + return@reportProgress withEDTContext { + val runContentBuilder = RunContentBuilder(executionResult, environment) + runContentBuilder.showRunContent(null) + } + } + } + return@reportProgress runInterruptibleEDT { + val debuggerManager = XDebuggerManager.getInstance(environment.project) + debuggerManager.startSession(environment, object: XDebugProcessStarter() { + override fun start(session: XDebugSession): XDebugProcess { + val project = session.project + val textConsoleBuilder = SharedConsoleBuilder(console) + val debugProcess = ZigLocalDebugProcess(runParameters, session, textConsoleBuilder) + ProcessTerminatedListener.attach(debugProcess.processHandler, project) + debugProcess.start() + return debugProcess + } + }).runContentDescriptor + } + } + } + + @Throws(ExecutionException::class) + protected abstract fun getDebugParameters( + state: ProfileState, + debuggerDriver: DebuggerDriverConfiguration, + toolchain: AbstractZigToolchain + ): ZigDebugParametersBase + + private class SharedConsoleBuilder(private val console: ConsoleView) : TextConsoleBuilder() { + override fun getConsole(): ConsoleView { + return console + } + + override fun addFilter(filter: Filter) { + } + + override fun setViewer(isViewer: Boolean) { + } + + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/binary/ZigDebugParametersBinary.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/binary/ZigDebugParametersBinary.kt new file mode 100644 index 00000000..18db6999 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/binary/ZigDebugParametersBinary.kt @@ -0,0 +1,41 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.runner.binary + +import com.falsepattern.zigbrains.debugger.ZigDebugBundle +import com.falsepattern.zigbrains.debugger.execution.binary.ZigProfileStateBinary +import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugEmitBinaryInstaller +import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase +import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.intellij.execution.ExecutionException +import com.jetbrains.cidr.execution.Installer +import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration + + +class ZigDebugParametersBinary @Throws(ExecutionException::class) constructor(driverConfiguration: DebuggerDriverConfiguration, toolchain: AbstractZigToolchain, profileState: ZigProfileStateBinary) : + 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) + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/binary/ZigDebugRunnerBinary.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/binary/ZigDebugRunnerBinary.kt new file mode 100644 index 00000000..d82ba469 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/binary/ZigDebugRunnerBinary.kt @@ -0,0 +1,55 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.runner.binary + +import com.falsepattern.zigbrains.debugger.execution.binary.ZigExecConfigBinary +import com.falsepattern.zigbrains.debugger.execution.binary.ZigProfileStateBinary +import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase +import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugRunnerBase +import com.falsepattern.zigbrains.project.execution.base.ZigProfileState +import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain +import com.intellij.execution.configurations.RunProfile +import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration + +class ZigDebugRunnerBinary: ZigDebugRunnerBase() { + override fun getDebugParameters( + state: ZigProfileStateBinary, + debuggerDriver: DebuggerDriverConfiguration, + toolchain: AbstractZigToolchain + ): ZigDebugParametersBase { + return ZigDebugParametersBinary(debuggerDriver, LocalZigToolchain.ensureLocal(toolchain), state) + } + + override fun castProfileState(state: ZigProfileState<*>): ZigProfileStateBinary? { + return state as? ZigProfileStateBinary + } + + override fun canRun(executorId: String, profile: RunProfile): Boolean { + return this.executorId == executorId && profile is ZigExecConfigBinary + } + + override fun getRunnerId(): String { + return "ZigDebugRunnerBinary" + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/build/ZigDebugParametersBuild.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/build/ZigDebugParametersBuild.kt new file mode 100644 index 00000000..fd1972e4 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/build/ZigDebugParametersBuild.kt @@ -0,0 +1,117 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.runner.build + +import com.falsepattern.zigbrains.debugger.ZigDebugBundle +import com.falsepattern.zigbrains.debugger.runner.base.PreLaunchAware +import com.falsepattern.zigbrains.debugger.runner.base.PreLaunchProcessListener +import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugEmitBinaryInstaller +import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase +import com.falsepattern.zigbrains.project.execution.build.ZigProfileStateBuild +import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.intellij.execution.ExecutionException +import com.intellij.execution.ui.ConsoleViewContentType +import com.intellij.openapi.util.SystemInfo +import com.intellij.platform.util.progress.withProgressText +import com.jetbrains.cidr.execution.Installer +import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.jetbrains.annotations.PropertyKey +import java.io.File +import java.nio.file.Files +import java.nio.file.Path +import java.util.stream.Stream +import kotlin.io.path.isExecutable +import kotlin.io.path.isRegularFile +import kotlin.io.path.notExists + +class ZigDebugParametersBuild( + driverConfiguration: DebuggerDriverConfiguration, + toolchain: AbstractZigToolchain, + profileState: ZigProfileStateBuild +) : ZigDebugParametersBase(driverConfiguration, toolchain, profileState), PreLaunchAware { + @Volatile + private lateinit var executableFile: File + + override fun getInstaller(): Installer { + return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, profileState.configuration.exeArgs.args) + } + + @Throws(ExecutionException::class) + override suspend fun preLaunch(listener: PreLaunchProcessListener) { + withProgressText("Building zig project") { + withContext(Dispatchers.IO) { + val commandLine = profileState.getCommandLine(toolchain, true) + if (listener.executeCommandLineWithHook(commandLine)) + throw ExecutionException(ZigDebugBundle.message("debug.build.compile.failed.generic")) + val cfg = profileState.configuration + val workingDir = cfg.workingDirectory.path + val exe = profileState.configuration.exePath.path ?: run { + //Attempt autodetect, should work for trivial cases, and make most users happy, while advanced + // users can use manual executable paths. + if (workingDir == null) { + fail("debug.build.compile.failed.no-workdir") + } + val expectedOutputDir = workingDir.resolve(Path.of("zig-out", "bin")) + if (expectedOutputDir.notExists()) { + fail("debug.build.compile.failed.autodetect") + } + + withContext(Dispatchers.IO) { + Files.list(expectedOutputDir).use { getExe(it) } + } + } + + if (exe.notExists()) + fail("debug.build.compile.failed.no-file", exe) + else if (!exe.isExecutable()) + fail("debug.build.compile.failed.non-exec-file", exe) + + executableFile = exe.toFile() + } + } + } + +} + +@Throws(ExecutionException::class) +private fun getExe(files: Stream): Path { + var fileStream = files.filter { it.isRegularFile() } + if (SystemInfo.isWindows) { + fileStream = fileStream.filter { it.fileName.endsWith(".exe") } + } else { + fileStream = fileStream.filter { it.isExecutable() } + } + val executables = fileStream.toList() + return when(executables.size) { + 0 -> fail("debug.base.compile.failed.no-exe") + 1 -> executables[0] + else -> fail("debug.build.compile.failed.multiple-exe") + } +} + +@Throws(ExecutionException::class) +private fun fail(@PropertyKey(resourceBundle = ZigDebugBundle.BUNDLE) messageKey: String, vararg params: Any): Nothing { + throw ExecutionException(ZigDebugBundle.message("debug.build.compile.failed.boilerplate", ZigDebugBundle.message(messageKey, params))) +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/build/ZigDebugRunnerBuild.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/build/ZigDebugRunnerBuild.kt new file mode 100644 index 00000000..0098c8be --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/build/ZigDebugRunnerBuild.kt @@ -0,0 +1,55 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.runner.build + +import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase +import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugRunnerBase +import com.falsepattern.zigbrains.project.execution.base.ZigProfileState +import com.falsepattern.zigbrains.project.execution.build.ZigExecConfigBuild +import com.falsepattern.zigbrains.project.execution.build.ZigProfileStateBuild +import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain +import com.intellij.execution.configurations.RunProfile +import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration + +class ZigDebugRunnerBuild: ZigDebugRunnerBase() { + override fun getDebugParameters( + state: ZigProfileStateBuild, + debuggerDriver: DebuggerDriverConfiguration, + toolchain: AbstractZigToolchain + ): ZigDebugParametersBase { + return ZigDebugParametersBuild(debuggerDriver, LocalZigToolchain.ensureLocal(toolchain), state) + } + + override fun castProfileState(state: ZigProfileState<*>): ZigProfileStateBuild? { + return state as? ZigProfileStateBuild + } + + override fun canRun(executorId: String, profile: RunProfile): Boolean { + return this.executorId == executorId && profile is ZigExecConfigBuild + } + + override fun getRunnerId(): String { + return "ZigDebugRunnerBuild" + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/run/ZigDebugParametersRun.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/run/ZigDebugParametersRun.kt new file mode 100644 index 00000000..f840f45e --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/run/ZigDebugParametersRun.kt @@ -0,0 +1,37 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.runner.run + +import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugEmitBinaryInstaller +import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersEmitBinaryBase +import com.falsepattern.zigbrains.project.execution.run.ZigProfileStateRun +import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.jetbrains.cidr.execution.Installer +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) + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/run/ZigDebugRunnerRun.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/run/ZigDebugRunnerRun.kt new file mode 100644 index 00000000..6c3e4b13 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/run/ZigDebugRunnerRun.kt @@ -0,0 +1,55 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.runner.run + +import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase +import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugRunnerBase +import com.falsepattern.zigbrains.project.execution.base.ZigProfileState +import com.falsepattern.zigbrains.project.execution.run.ZigExecConfigRun +import com.falsepattern.zigbrains.project.execution.run.ZigProfileStateRun +import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain +import com.intellij.execution.configurations.RunProfile +import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration + +class ZigDebugRunnerRun: ZigDebugRunnerBase() { + override fun getDebugParameters( + state: ZigProfileStateRun, + debuggerDriver: DebuggerDriverConfiguration, + toolchain: AbstractZigToolchain + ): ZigDebugParametersBase { + return ZigDebugParametersRun(debuggerDriver, LocalZigToolchain.ensureLocal(toolchain), state) + } + + override fun castProfileState(state: ZigProfileState<*>): ZigProfileStateRun? { + return state as? ZigProfileStateRun + } + + override fun canRun(executorId: String, profile: RunProfile): Boolean { + return this.executorId == executorId && (profile is ZigExecConfigRun) + } + + override fun getRunnerId(): String { + return "ZigDebugRunnerRun" + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/test/ZigDebugParametersTest.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/test/ZigDebugParametersTest.kt new file mode 100644 index 00000000..e263037c --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/test/ZigDebugParametersTest.kt @@ -0,0 +1,37 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.runner.test + +import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugEmitBinaryInstaller +import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersEmitBinaryBase +import com.falsepattern.zigbrains.project.execution.test.ZigProfileStateTest +import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.jetbrains.cidr.execution.Installer +import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration + +class ZigDebugParametersTest(driverConfiguration: DebuggerDriverConfiguration, toolchain: AbstractZigToolchain, profileState: ZigProfileStateTest) : + ZigDebugParametersEmitBinaryBase(driverConfiguration, toolchain, profileState) { + override fun getInstaller(): Installer { + return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, listOf()) + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/test/ZigDebugRunnerTest.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/test/ZigDebugRunnerTest.kt new file mode 100644 index 00000000..caab3d25 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/test/ZigDebugRunnerTest.kt @@ -0,0 +1,55 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.runner.test + +import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase +import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugRunnerBase +import com.falsepattern.zigbrains.project.execution.base.ZigProfileState +import com.falsepattern.zigbrains.project.execution.test.ZigExecConfigTest +import com.falsepattern.zigbrains.project.execution.test.ZigProfileStateTest +import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain +import com.intellij.execution.configurations.RunProfile +import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration + +class ZigDebugRunnerTest: ZigDebugRunnerBase() { + override fun getDebugParameters( + state: ZigProfileStateTest, + debuggerDriver: DebuggerDriverConfiguration, + toolchain: AbstractZigToolchain + ): ZigDebugParametersBase { + return ZigDebugParametersTest(debuggerDriver, LocalZigToolchain.ensureLocal(toolchain), state) + } + + override fun castProfileState(state: ZigProfileState<*>): ZigProfileStateTest? { + return state as? ZigProfileStateTest + } + + override fun canRun(executorId: String, profile: RunProfile): Boolean { + return this.executorId == executorId && profile is ZigExecConfigTest + } + + override fun getRunnerId(): String { + return "ZigDebugRunnerTest" + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/settings/ZigDebuggerGeneralSettingsConfigurableUi.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/settings/ZigDebuggerGeneralSettingsConfigurableUi.kt new file mode 100644 index 00000000..7c6d3101 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/settings/ZigDebuggerGeneralSettingsConfigurableUi.kt @@ -0,0 +1,54 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.settings + +import com.intellij.openapi.Disposable +import com.intellij.openapi.options.ConfigurableUi +import com.intellij.openapi.util.Disposer +import com.intellij.ui.dsl.builder.panel +import javax.swing.JComponent + +class ZigDebuggerGeneralSettingsConfigurableUi: ConfigurableUi, Disposable { + private val components = listOf(ZigDebuggerToolchainConfigurableUi()).onEach { Disposer.register(this, it) } + + override fun reset(settings: ZigDebuggerSettings) { + components.forEach { it.reset(settings) } + } + + override fun isModified(settings: ZigDebuggerSettings): Boolean { + return components.any { it.isModified(settings) } + } + + override fun apply(settings: ZigDebuggerSettings) { + components.forEach { it.apply(settings) } + } + + override fun getComponent(): JComponent { + return panel { + components.forEach { it.buildUi(this) } + } + } + + override fun dispose() { + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/settings/ZigDebuggerSettings.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/settings/ZigDebuggerSettings.kt new file mode 100644 index 00000000..82dbfbcd --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/settings/ZigDebuggerSettings.kt @@ -0,0 +1,70 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.settings + +import com.falsepattern.zigbrains.debugger.ZigDebugBundle +import com.falsepattern.zigbrains.debugger.toolchain.DebuggerKind +import com.intellij.openapi.options.Configurable +import com.intellij.openapi.options.SimpleConfigurable +import com.intellij.util.xmlb.XmlSerializerUtil +import com.intellij.xdebugger.settings.DebuggerSettingsCategory +import com.intellij.xdebugger.settings.XDebuggerSettings + +class ZigDebuggerSettings: XDebuggerSettings("Zig") { + var debuggerKind = DebuggerKind.default + + var downloadAutomatically = false + var useClion = true + + override fun getState(): ZigDebuggerSettings? { + return this + } + + override fun loadState(p0: ZigDebuggerSettings) { + XmlSerializerUtil.copyBean(p0, this) + } + + override fun createConfigurables(category: DebuggerSettingsCategory): Collection { + val configurable = when(category) { + DebuggerSettingsCategory.GENERAL -> createGeneralSettingsConfigurable() + else -> null + } + return configurable?.let { listOf(configurable) } ?: emptyList() + } + + private fun createGeneralSettingsConfigurable(): Configurable { + return SimpleConfigurable.create( + GENERAL_SETTINGS_ID, + ZigDebugBundle.message("settings.debugger.title"), + ZigDebuggerGeneralSettingsConfigurableUi::class.java, + ) { + instance + } + } + + companion object { + val instance: ZigDebuggerSettings get() = getInstance(ZigDebuggerSettings::class.java) + } +} + +private const val GENERAL_SETTINGS_ID = "Debugger.Zig.General" \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/settings/ZigDebuggerToolchainConfigurableUi.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/settings/ZigDebuggerToolchainConfigurableUi.kt new file mode 100644 index 00000000..d7804ba5 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/settings/ZigDebuggerToolchainConfigurableUi.kt @@ -0,0 +1,161 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.settings + +import com.falsepattern.zigbrains.debugger.ZigDebugBundle +import com.falsepattern.zigbrains.debugger.toolchain.DebuggerAvailability +import com.falsepattern.zigbrains.debugger.toolchain.DebuggerKind +import com.falsepattern.zigbrains.debugger.toolchain.ZigDebuggerToolchainService +import com.falsepattern.zigbrains.debugger.toolchain.zigDebuggerToolchainService +import com.falsepattern.zigbrains.shared.coroutine.launchWithEDT +import com.falsepattern.zigbrains.shared.coroutine.runModalOrBlocking +import com.falsepattern.zigbrains.shared.zigCoroutineScope +import com.intellij.ide.plugins.PluginManager +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.EDT +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.extensions.PluginId +import com.intellij.openapi.observable.util.whenItemSelected +import com.intellij.openapi.options.ConfigurableUi +import com.intellij.openapi.ui.ComboBox +import com.intellij.openapi.util.SystemInfo +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.components.JBCheckBox +import com.intellij.ui.dsl.builder.DEFAULT_COMMENT_WIDTH +import com.intellij.ui.dsl.builder.Panel +import com.intellij.util.concurrency.annotations.RequiresEdt +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.asContextElement +import kotlinx.coroutines.job +import kotlinx.coroutines.launch +import javax.swing.ComboBoxModel +import javax.swing.DefaultComboBoxModel +import javax.swing.JEditorPane + +class ZigDebuggerToolchainConfigurableUi : ZigDebuggerUiComponent { + private val debuggerKindComboBox = ComboBox( + runModalOrBlocking({ ModalTaskOwner.guess() }, { "ZigDebuggerToolchainConfigurableUi" }) { + createDebuggerKindComboBoxModel() + } + ) + private val downloadAutomaticallyCheckBox = JBCheckBox( + ZigDebugBundle.message("settings.debugger.toolchain.download.debugger.automatically.checkbox"), + ZigDebuggerSettings.instance.downloadAutomatically + ) + + private val useClion = JBCheckBox( + ZigDebugBundle.message("settings.debugger.toolchain.use.clion.toolchains"), + ZigDebuggerSettings.instance.useClion + ) + + private var comment: JEditorPane? = null + + private val currentDebuggerKind get() = debuggerKindComboBox.item + + override fun reset(settings: ZigDebuggerSettings) { + debuggerKindComboBox.item = settings.debuggerKind + downloadAutomaticallyCheckBox.isSelected = settings.downloadAutomatically + useClion.isSelected = settings.useClion + } + + override fun isModified(settings: ZigDebuggerSettings): Boolean { + return settings.debuggerKind != debuggerKindComboBox.item || + settings.downloadAutomatically != downloadAutomaticallyCheckBox.isSelected || + settings.useClion != useClion.isSelected + } + + override fun apply(settings: ZigDebuggerSettings) { + settings.debuggerKind = debuggerKindComboBox.item + settings.downloadAutomatically = downloadAutomaticallyCheckBox.isSelected + settings.useClion = useClion.isSelected + } + + override fun buildUi(panel: Panel): Unit = with(panel) { + row(ZigDebugBundle.message("settings.debugger.toolchain.debugger.label")) { + comment = cell(debuggerKindComboBox) + .comment("", DEFAULT_COMMENT_WIDTH) { + zigCoroutineScope.launchWithEDT { + withModalProgress(ModalTaskOwner.component(debuggerKindComboBox), "Downloading debugger", TaskCancellation.cancellable()) { + downloadDebugger() + } + } + } + .applyToComponent { + whenItemSelected(null) { + zigCoroutineScope.launchWithEDT { + this@ZigDebuggerToolchainConfigurableUi.update() + } + } + } + .comment + } + row { + cell(downloadAutomaticallyCheckBox) + } +// if (PluginManager.isPluginInstalled(PluginId.getId("com.intellij.modules.clion")) && !SystemInfo.isWindows) { +// row { +// cell(useClion) +// } +// } + zigCoroutineScope.launchWithEDT { + update() + } + } + + override fun dispose() { + + } + + @RequiresEdt + private suspend fun downloadDebugger() { + val result = zigDebuggerToolchainService.downloadDebugger(null, currentDebuggerKind) + if (result is ZigDebuggerToolchainService.DownloadResult.Ok) { + update() + } + } + + @RequiresEdt + private suspend fun update() { + val availability = zigDebuggerToolchainService.debuggerAvailability(currentDebuggerKind) + val text = when (availability) { + is DebuggerAvailability.NeedToDownload -> ZigDebugBundle.message("settings.debugger.toolchain.download.comment") + is DebuggerAvailability.NeedToUpdate -> ZigDebugBundle.message("settings.debugger.toolchain.update.comment") + else -> null + } + comment?.let { + it.text = text + it.isVisible = text != null + } + } + + companion object { + private suspend fun createDebuggerKindComboBoxModel(): ComboBoxModel { + val toolchainService = zigDebuggerToolchainService + val availableKinds = DebuggerKind.entries.filter { toolchainService.debuggerAvailability(it) !is DebuggerAvailability.Unavailable } + return DefaultComboBoxModel(availableKinds.toTypedArray()).also { it.selectedItem = ZigDebuggerSettings.instance.debuggerKind } + } + + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/settings/ZigDebuggerUiComponent.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/settings/ZigDebuggerUiComponent.kt new file mode 100644 index 00000000..2019d17c --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/settings/ZigDebuggerUiComponent.kt @@ -0,0 +1,39 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.settings + +import com.intellij.openapi.Disposable +import com.intellij.openapi.options.ConfigurableUi +import com.intellij.ui.dsl.builder.Panel +import com.intellij.ui.dsl.builder.panel +import javax.swing.JComponent + +interface ZigDebuggerUiComponent: ConfigurableUi, Disposable { + fun buildUi(panel: Panel) + + override fun getComponent(): JComponent { + return panel { + buildUi(this) + } + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/toolchain/DebuggerAvailability.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/toolchain/DebuggerAvailability.kt new file mode 100644 index 00000000..a662e645 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/toolchain/DebuggerAvailability.kt @@ -0,0 +1,37 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.toolchain + +import java.nio.file.Path + +sealed class DebuggerAvailability { + data object Unavailable: DebuggerAvailability() + data object NeedToDownload: DebuggerAvailability() + data object NeedToUpdate: DebuggerAvailability() + data object Bundled: DebuggerAvailability() + data class Binaries (val binaries: T): DebuggerAvailability() +} + +data class LLDBBinaries(val frameworkFile: Path, val frontendFile: Path) +data class GDBBinaries(val gdbFile: Path) +data class MSVCBinaries(val msvcFile: Path) \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/toolchain/DebuggerKind.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/toolchain/DebuggerKind.kt new file mode 100644 index 00000000..09d8fc38 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/toolchain/DebuggerKind.kt @@ -0,0 +1,35 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.toolchain + +import com.intellij.openapi.util.SystemInfo + +enum class DebuggerKind { + LLDB, + GDB, + MSVC; + + companion object { + val default get() = if (SystemInfo.isWindows) MSVC else LLDB + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/toolchain/MSVCMetadataProvider.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/toolchain/MSVCMetadataProvider.kt new file mode 100644 index 00000000..03b71b77 --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/toolchain/MSVCMetadataProvider.kt @@ -0,0 +1,131 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.toolchain + +import com.falsepattern.zigbrains.debugger.ZigDebugBundle +import com.falsepattern.zigbrains.debugger.settings.ZigDebuggerSettings +import com.falsepattern.zigbrains.debugger.toolchain.ZigDebuggerToolchainService.Companion.downloadPath +import com.falsepattern.zigbrains.shared.coroutine.withEDTContext +import com.falsepattern.zigbrains.shared.zigCoroutineScope +import com.intellij.notification.Notification +import com.intellij.notification.NotificationType +import com.intellij.openapi.application.EDT +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.application.asContextElement +import com.intellij.openapi.progress.coroutineToIndicator +import com.intellij.openapi.ui.DialogBuilder +import com.intellij.platform.ide.progress.ModalTaskOwner +import com.intellij.platform.ide.progress.TaskCancellation +import com.intellij.platform.ide.progress.withModalProgress +import com.intellij.platform.util.progress.withProgressText +import com.intellij.ui.components.JBLabel +import com.intellij.ui.components.JBPanel +import com.intellij.util.concurrency.annotations.RequiresEdt +import com.intellij.util.download.DownloadableFileService +import com.intellij.util.suspendingLazy +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runInterruptible +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext +import kotlinx.coroutines.withTimeoutOrNull +import java.io.IOException +import java.util.* +import javax.swing.BoxLayout + +private val mutex = Mutex() +private var cache: Properties? = null + +suspend fun msvcMetadata(): Properties { + cache?.let { return it } + mutex.withLock { + cache?.let { return it } + val allowDownload = withEDTContext(ModalityState.current()) { + val dialog = DialogBuilder() + dialog.setTitle(ZigDebugBundle.message("msvc.consent.title")) + dialog.addCancelAction().setText(ZigDebugBundle.message("msvc.consent.deny")) + dialog.addOkAction().setText(ZigDebugBundle.message("msvc.consent.allow")) + val centerPanel = JBPanel>() + centerPanel.setLayout(BoxLayout(centerPanel, BoxLayout.Y_AXIS)) + val lines = ZigDebugBundle.message("msvc.consent.body").split('\n') + for (line in lines) { + centerPanel.add(JBLabel(line)) + } + dialog.centerPanel(centerPanel) + dialog.showAndGet() + } + val data = if (allowDownload) { + withTimeoutOrNull(3000L) { + downloadMSVCProps() + } ?: run { + Notification( + "zigbrains", + ZigDebugBundle.message("notification.title.debugger"), + ZigDebugBundle.message("notification.content.debugger.metadata.downloading.failed"), + NotificationType.ERROR + ).notify(null) + fetchBuiltinMSVCProps() + } + } else { + fetchBuiltinMSVCProps() + } + cache = data + return data + } +} + +private suspend fun downloadMSVCProps(): Properties { + return withProgressText("Downloading debugger metadata") { + val service = DownloadableFileService.getInstance() + val desc = service.createFileDescription("https://falsepattern.com/zigbrains/msvc.properties", "msvc.properties") + val downloader = service.createDownloader(listOf(desc), "Debugger metadata downloading") + val downloadDirectory = downloadPath().toFile() + val prop = Properties() + val downloadResults = coroutineToIndicator { + downloader.download(downloadDirectory) + } + for (result in downloadResults) { + if (result.second.defaultFileName == "msvc.properties") { + result.first.reader().use { prop.load(it) } + } + } + return@withProgressText prop + } +} + +private fun fetchBuiltinMSVCProps(): Properties { + val prop = Properties() + try { + val resource = ZigDebuggerToolchainService::class.java.getResourceAsStream("/msvc.properties") ?: throw IOException("null") + resource.reader().use { prop.load(it) } + } catch (ex: IOException) { + ex.printStackTrace() + Notification( + "zigbrains", + ZigDebugBundle.message("notification.title.debugger"), + ZigDebugBundle.message("notification.content.debugger.metadata.fallback.parse.failed"), + NotificationType.ERROR + ).notify(null) + } + return prop +} \ No newline at end of file diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/toolchain/ZigDebuggerToolchainService.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/toolchain/ZigDebuggerToolchainService.kt new file mode 100644 index 00000000..55fc883b --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/toolchain/ZigDebuggerToolchainService.kt @@ -0,0 +1,383 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.toolchain + +import com.falsepattern.zigbrains.debugger.ZigDebugBundle +import com.falsepattern.zigbrains.shared.coroutine.runInterruptibleEDT +import com.falsepattern.zigbrains.shared.coroutine.runModalOrBlocking +import com.intellij.execution.ExecutionModes.ModalProgressMode +import com.intellij.notification.Notification +import com.intellij.notification.NotificationType +import com.intellij.openapi.application.EDT +import com.intellij.openapi.application.PathManager +import com.intellij.openapi.components.Service +import com.intellij.openapi.components.service +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.progress.coroutineToIndicator +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogBuilder +import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.util.io.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.BrowserHyperlinkListener +import com.intellij.ui.HyperlinkLabel +import com.intellij.ui.components.JBPanel +import com.intellij.util.application +import com.intellij.util.concurrency.annotations.RequiresEdt +import com.intellij.util.download.DownloadableFileService +import com.intellij.util.io.Decompressor +import com.intellij.util.system.CpuArch +import com.intellij.util.system.OS +import com.jetbrains.cidr.execution.debugger.CidrDebuggerPathManager +import com.jetbrains.cidr.execution.debugger.backend.bin.UrlProvider +import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBDriverConfiguration +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runInterruptible +import kotlinx.coroutines.withContext +import java.io.File +import java.io.IOException +import java.net.URL +import java.nio.file.Path +import java.util.Properties +import kotlin.io.path.name +import kotlin.io.path.notExists + +@Service +class ZigDebuggerToolchainService { + suspend fun debuggerAvailability(kind: DebuggerKind): DebuggerAvailability<*> { + return when(kind) { + DebuggerKind.LLDB -> lldbAvailability() + DebuggerKind.GDB -> gdbAvailability() + DebuggerKind.MSVC -> msvcAvailability() + } + } + + fun lldbAvailability(): DebuggerAvailability { +// if (LLDBDriverConfiguration.hasBundledLLDB()) return DebuggerAvailability.Bundled + + val (frameworkPath, frontendPath) = when { + SystemInfo.isMac -> "LLDB.framework" to "LLDBFrontend" + SystemInfo.isUnix -> "lib/liblldb.so" to "bin/LLDBFrontend" + SystemInfo.isWindows -> "bin/liblldb.dll" to "bin/LLDBFrontend.exe" + else -> return DebuggerAvailability.Unavailable + } + + val lldbPath = lldbPath() + val frameworkFile = lldbPath.resolve(frameworkPath) + val frontendFile = lldbPath.resolve(frontendPath) + if (frameworkFile.notExists() || frontendFile.notExists()) return DebuggerAvailability.NeedToDownload + + val versions = loadDebuggerVersions(DebuggerKind.LLDB) + val (lldbFrameworkUrl, lldbFrontendUrl) = lldbUrls() ?: return DebuggerAvailability.Unavailable + + val lldbFrameworkVersion = fileNameWithoutExtension(lldbFrameworkUrl.toString()) + val lldbFrontendVersion = fileNameWithoutExtension(lldbFrontendUrl.toString()) + + if (versions[LLDB_FRAMEWORK_PROPERTY_NAME] != lldbFrameworkVersion || + versions[LLDB_FRONTEND_PROPERTY_NAME] != lldbFrontendVersion) return DebuggerAvailability.NeedToUpdate + + return DebuggerAvailability.Binaries(LLDBBinaries(frameworkFile, frontendFile)) + } + + fun gdbAvailability(): DebuggerAvailability { + if (SystemInfo.isMac) return DebuggerAvailability.Unavailable +// if (CidrDebuggerPathManager.getBundledGDBBinary().exists()) return DebuggerAvailability.Bundled + + val gdbBinaryPath = when { + SystemInfo.isUnix -> "bin/gdb" + SystemInfo.isWindows -> "bin/gdb.exe" + else -> return DebuggerAvailability.Unavailable + } + + val gdbFile = gdbPath().resolve(gdbBinaryPath) + if (gdbFile.notExists()) return DebuggerAvailability.NeedToDownload + + val versions = loadDebuggerVersions(DebuggerKind.GDB) + val gdbUrl = gdbUrl() ?: return DebuggerAvailability.Unavailable + + val gdbVersion = fileNameWithoutExtension(gdbUrl.toString()) + + if (versions[GDB_PROPERTY_NAME] != gdbVersion) return DebuggerAvailability.NeedToUpdate + + return DebuggerAvailability.Binaries(GDBBinaries(gdbFile)) + } + + suspend fun msvcAvailability(): DebuggerAvailability { +// if (!SystemInfo.isWindows) return DebuggerAvailability.Unavailable + + val msvcBinaryPath = "vsdbg.exe" + + val msvcFile = msvcPath().resolve(msvcBinaryPath) + if (msvcFile.notExists()) return DebuggerAvailability.NeedToDownload + + val msvcUrl = msvcUrl() ?: return DebuggerAvailability.Binaries(MSVCBinaries(msvcFile)) + + val versions = loadDebuggerVersions(DebuggerKind.MSVC) + + if (versions[MSVC_PROPERTY_NAME] != msvcUrl.version) return DebuggerAvailability.NeedToUpdate + + return DebuggerAvailability.Binaries(MSVCBinaries(msvcFile)) + } + + @RequiresEdt + private suspend fun doDownloadDebugger(project: Project? = null, debuggerKind: DebuggerKind): DownloadResult { + val baseDir = debuggerKind.basePath() + val downloadableBinaries = when(debuggerKind) { + DebuggerKind.LLDB -> { + val (lldbFrameworkUrl, lldbFrontendUrl) = lldbUrls()?.run { first.toString() to second.toString() } ?: return DownloadResult.NoUrls + listOf( + DownloadableDebuggerBinary(lldbFrameworkUrl, LLDB_FRAMEWORK_PROPERTY_NAME, fileNameWithoutExtension(lldbFrameworkUrl)), + DownloadableDebuggerBinary(lldbFrontendUrl, LLDB_FRONTEND_PROPERTY_NAME, fileNameWithoutExtension(lldbFrontendUrl)) + ) + } + DebuggerKind.GDB -> { + val gdbUrl = gdbUrl()?.run { toString() } ?: return DownloadResult.NoUrls + listOf(DownloadableDebuggerBinary(gdbUrl, GDB_PROPERTY_NAME, fileNameWithoutExtension(gdbUrl))) + } + DebuggerKind.MSVC -> { + val msvcUrl = msvcUrl() ?: return DownloadResult.NoUrls + + val dialog = DialogBuilder() + dialog.setTitle(msvcUrl.dialogTitle) + dialog.addCancelAction().setText("Reject") + dialog.addOkAction().setText("Accept") + val centerPanel = JBPanel>() + val hyperlink = HyperlinkLabel() + hyperlink.setTextWithHyperlink(msvcUrl.dialogBody) + hyperlink.setHyperlinkText(msvcUrl.dialogLink) + hyperlink.addHyperlinkListener(BrowserHyperlinkListener()) + centerPanel.add(hyperlink) + dialog.centerPanel(centerPanel) + if (!dialog.showAndGet()) return DownloadResult.NoUrls + + listOf(DownloadableDebuggerBinary(msvcUrl.url, MSVC_PROPERTY_NAME, msvcUrl.version, "extension/debugAdapters/vsdbg/bin")) + } + } + + try { + downloadAndUnArchive(project, baseDir, downloadableBinaries) + return DownloadResult.Ok(baseDir) + } catch (e: IOException) { + //TODO logging + e.printStackTrace() + return DownloadResult.Failed(e.message) + } + } + + @RequiresEdt + suspend fun downloadDebugger(project: Project? = null, debuggerKind: DebuggerKind): DownloadResult { + val result = doDownloadDebugger(project, debuggerKind) + + when(result) { + is DownloadResult.Ok -> { + Notification( + "zigbrains", + ZigDebugBundle.message("notification.title.debugger"), + ZigDebugBundle.message("notification.content.debugger.successfully.downloaded"), + NotificationType.INFORMATION + ).notify(project) + } + is DownloadResult.Failed -> { + Notification( + "zigbrains", + ZigDebugBundle.message("notification.title.debugger"), + ZigDebugBundle.message("notification.content.debugger.downloading.failed"), + NotificationType.ERROR + ).notify(project) + } + else -> Unit + } + + return result + } + + @Throws(IOException::class) + @RequiresEdt + private suspend fun downloadAndUnArchive(project: Project?, baseDir: Path, binariesToDownload: List) { + val service = DownloadableFileService.getInstance() + + val downloadDir = baseDir.toFile() + downloadDir.deleteRecursively() + + val descriptions = binariesToDownload.map { + service.createFileDescription(it.url, fileName(it.url)) + } + + val downloader = service.createDownloader(descriptions, "Debugger downloading") + val downloadDirectory = downloadPath().toFile() + val downloadResults = withContext(Dispatchers.IO) { + coroutineToIndicator { + downloader.download(downloadDirectory) + } + } + val versions = Properties() + for (result in downloadResults) { + val downloadUrl = result.second.downloadUrl + val binaryToDownload = binariesToDownload.first { it.url == downloadUrl } + val propertyName = binaryToDownload.propertyName + val archiveFile = result.first + Unarchiver.unarchive(archiveFile, downloadDir) + archiveFile.delete() + versions[propertyName] = binaryToDownload.version + } + + saveVersionsFile(baseDir, versions) + } + + private fun lldbUrls(): Pair? { + val lldb = UrlProvider.lldb(OS.CURRENT, CpuArch.CURRENT) ?: return null + val lldbFrontend = UrlProvider.lldbFrontend(OS.CURRENT, CpuArch.CURRENT) ?: return null + return lldb to lldbFrontend + } + + private fun gdbUrl(): URL? = UrlProvider.gdb(OS.CURRENT, CpuArch.CURRENT) + + private suspend fun msvcUrl(): MSVCUrl? { + val dlKey = when(CpuArch.CURRENT) { + CpuArch.X86 -> "downloadX86" + CpuArch.X86_64 -> "downloadX86_64" + CpuArch.ARM64 -> "downloadARM64" + else -> return null + } + + val props = msvcMetadata() + val version = props.getProperty("version") ?: return null + val url = props.getProperty(dlKey) ?: return null + return MSVCUrl(url, version, props.getProperty("dialogTitle")!!, props.getProperty("dialogBody")!!, props.getProperty("dialogLink")!!) + } + + private data class MSVCUrl( + val url: String, + val version: String, + val dialogTitle: String, + val dialogBody: String, + val dialogLink: String + ) + + private fun loadDebuggerVersions(kind: DebuggerKind): Properties = loadVersions(kind.basePath()) + + private fun saveVersionsFile(basePath: Path, versions: Properties) { + val file = basePath.resolve(DEBUGGER_VERSIONS).toFile() + try { + versions.store(file.bufferedWriter(), "") + } catch (e: IOException) { + LOG.warn("Failed to save `${basePath.name}/${file.name}`", e) + } + } + + private fun loadVersions(basePath: Path): Properties { + val versions = Properties() + val versionsFile = basePath.resolve(DEBUGGER_VERSIONS).toFile() + + if (versionsFile.exists()) { + try { + versionsFile.bufferedReader().use { versions.load(it) } + } catch (e: IOException) { + LOG.warn("Failed to load `${basePath.name}/${versionsFile.name}`", e) + } + } + + return versions + } + + private fun DebuggerKind.basePath(): Path { + return when(this) { + DebuggerKind.LLDB -> lldbPath() + DebuggerKind.GDB -> gdbPath() + DebuggerKind.MSVC -> msvcPath() + } + } + + companion object { + private val LOG = logger() + + private const val DEBUGGER_VERSIONS: String = "versions.properties" + + private const val LLDB_FRONTEND_PROPERTY_NAME = "lldbFrontend" + private const val LLDB_FRAMEWORK_PROPERTY_NAME = "lldbFramework" + private const val GDB_PROPERTY_NAME = "gdb" + private const val MSVC_PROPERTY_NAME = "msvc" + + fun downloadPath() = tempPluginDir + private fun lldbPath() = pluginDir.resolve("lldb") + private fun gdbPath() = pluginDir.resolve("gdb") + private fun msvcPath() = pluginDir.resolve("msvc") + + private val pluginDir get() = PathManager.getSystemDir().resolve("zigbrains") + + private val tempPluginDir get() = PathManager.getTempPath().toNioPathOrNull()!!.resolve("zigbrains") + + private fun fileNameWithoutExtension(url: String): String { + return url.substringAfterLast("/").removeSuffix(".zip").removeSuffix(".tar.gz") + } + + private fun fileName(url: String): String { + return url.substringAfterLast("/") + } + } + + private enum class Unarchiver { + ZIP { + override val extension = "zip" + override fun createDecompressor(file: File) = Decompressor.Zip(file) + }, + TAR { + override val extension = "tar.gz" + override fun createDecompressor(file: File) = Decompressor.Tar(file) + }, + VSIX { + override val extension = "vsix" + override fun createDecompressor(file: File) = Decompressor.Zip(file) + }; + + protected abstract val extension: String + protected abstract fun createDecompressor(file: File): Decompressor + + companion object { + @Throws(IOException::class) + suspend fun unarchive(archivePath: File, dst: File, prefix: String? = null) { + runInterruptible { + val unarchiver = entries.find { archivePath.name.endsWith(it.extension) } ?: error("Unexpected archive type: $archivePath") + val dec = unarchiver.createDecompressor(archivePath) + if (prefix != null) { + dec.removePrefixPath(prefix) + } + dec.extract(dst) + } + } + } + } + + sealed class DownloadResult { + class Ok(val baseDir: Path): DownloadResult() + data object NoUrls: DownloadResult() + class Failed(val message: String?): DownloadResult() + } + + @JvmRecord + private data class DownloadableDebuggerBinary(val url: String, val propertyName: String, val version: String, val prefix: String? = null) +} + +val zigDebuggerToolchainService get() = application.service() diff --git a/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/win/MSVCDriverConfiguration.kt b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/win/MSVCDriverConfiguration.kt new file mode 100644 index 00000000..d998e02b --- /dev/null +++ b/modules/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/win/MSVCDriverConfiguration.kt @@ -0,0 +1,53 @@ +/* + * This file is part of ZigBrains. + * + * Copyright (C) 2023-2024 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.debugger.win + +import com.falsepattern.zigbrains.debugger.dap.DAPDebuggerDriverConfiguration +import com.intellij.execution.configurations.GeneralCommandLine +import com.jetbrains.cidr.ArchitectureType +import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriver +import org.eclipse.lsp4j.debug.InitializeRequestArguments +import java.nio.file.Path +import kotlin.io.path.pathString + +abstract class MSVCDriverConfiguration: DAPDebuggerDriverConfiguration() { + protected abstract val debuggerExecutable: Path + + override fun createDriver(handler: DebuggerDriver.Handler, arch: ArchitectureType): DebuggerDriver { + TODO("Not yet implemented") + } + + override fun createDriverCommandLine(driver: DebuggerDriver, arch: ArchitectureType): GeneralCommandLine { + val path = debuggerExecutable + val cli = GeneralCommandLine() + cli.exePath = path.pathString + cli.addParameters("--interpreter=vscode", "--extconfigdir=%USERPROFILE\\.cppvsdbg\\extensions") + cli.withWorkingDirectory(path.parent) + return cli + } + + override fun customizeInitializeArguments(initArgs: InitializeRequestArguments) { + initArgs.pathFormat = "path" + initArgs.adapterID = "cppvsdbg" + } +} \ No newline at end of file diff --git a/modules/cidr/src/main/resources/META-INF/zigbrains-debugger.xml b/modules/cidr/src/main/resources/META-INF/zigbrains-debugger.xml new file mode 100644 index 00000000..86bfdd93 --- /dev/null +++ b/modules/cidr/src/main/resources/META-INF/zigbrains-debugger.xml @@ -0,0 +1,60 @@ + + com.intellij.modules.cidr.debugger + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/cidr/src/main/resources/zigbrains/debugger/Bundle.properties b/modules/cidr/src/main/resources/zigbrains/debugger/Bundle.properties new file mode 100644 index 00000000..43918aef --- /dev/null +++ b/modules/cidr/src/main/resources/zigbrains/debugger/Bundle.properties @@ -0,0 +1,43 @@ +dialog.title.download.debugger=Download Debugger +notification.nativedebug.title=Zig native debugger +notification.nativedebug.text=You need to install the "Native Debugging Support" plugin for Zig debugging in this IDE! +notification.nativedebug.market=Install from Marketplace +notification.nativedebug.browser=Open in Browser +notification.title.debugger=Debugger +notification.content.debugger.successfully.downloaded=Debugger successfully downloaded +notification.content.debugger.downloading.failed=Debugger downloading failed +notification.content.debugger.metadata.downloading.failed=Debugger metadata downloading failed, switching to fallback +notification.content.debugger.metadata.fallback.fetch.failed=Debugger fallback metadata fetch failed +notification.content.debugger.metadata.fallback.parse.failed=Debugger fallback metadata parse failed +settings.debugger.toolchain.download.debugger.automatically.checkbox=Download and update the debugger automatically +settings.debugger.title=Zig +settings.debugger.toolchain.debugger.label=Debugger: +settings.debugger.toolchain.download.comment=Need to be downloaded +settings.debugger.toolchain.update.comment=Need to be updated +settings.debugger.toolchain.use.clion.toolchains=Use Clion toolchains instead +notification.content.debugger.need.download=You need to download/update the debugger before you can run debugging! (Settings | Build, Execution, Deployment | Debugging -> Zig) +debugger.run.unavailable=Unable to Run Debugger +debugger.run.unavailable.reason.download=Debugger is not downloaded yet +debugger.run.unavailable.reason.download.button=Download +debugger.run.unavailable.reason.update=Debugger is outdated +debugger.run.unavailable.reason.update.button=Update +debug.build.compile.failed.boilerplate={0}\nPlease edit this intellij build configuration and specify the path of the executable created by "zig build" directly! +debug.base.compile.failed.generic=Failed to compile executable +debug.base.compile.failed.no-exe=Failed to find compiled binary +debug.build.compile.failed.multiple-exe=Multiple compiled binaries found +debug.build.compile.failed.no-workdir=Cannot find working directory to run debugged executable +debug.build.compile.failed.autodetect=Could not auto-detect default executable output directory "zig-out/bin" +debug.build.compile.failed.no-file=File `{0}` does not exist +debug.build.compile.failed.non-exec-file=File `{0}` is not executable +debug.build.compile.failed.generic=Failed to build project +exec.type.binary.label=Zig-compiled native executable +exec.option.label.binary.exe-path=Executable program path (not the zig compiler) +exec.option.label.binary.args=Command line arguments +exception.missing-exe-path=Missing executable path +configuration.binary.name=Native Application (Zig) +configuration.binary.suggested-name=Executable +configuration.binary.description=Binary executable compiled from zig code (useful for debugging on Windows) +msvc.consent.title=Network Request Consent +msvc.consent.deny=Use Bundled +msvc.consent.allow=Download +msvc.consent.body=Debugging on Windows requires downloading extra metadata from the internet.\nThe bundled fallback metadata will be used if the request is denied. diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/direnv/DirenvCmd.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/direnv/DirenvCmd.kt index 0a142733..0aa48b15 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/direnv/DirenvCmd.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/direnv/DirenvCmd.kt @@ -32,6 +32,7 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.progress.runBlockingCancellable import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir +import com.intellij.platform.util.progress.withProgressText import com.intellij.util.io.awaitExit import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.sync.withLock @@ -72,12 +73,14 @@ object DirenvCmd { private suspend fun run(project: Project, workDir: Path, vararg args: String): DirenvOutput { val cli = GeneralCommandLine("direnv", *args).withWorkingDirectory(workDir) - val process: Process - val exitCode: Int - - project.direnvService.mutex.withLock { - process = cli.createProcess() - exitCode = process.awaitExit() + val (process, exitCode) = withProgressText("Running ${cli.commandLineString}") { + withContext(Dispatchers.IO) { + project.direnvService.mutex.withLock { + val process = cli.createProcess() + val exitCode = process.awaitExit() + process to exitCode + } + } } if (exitCode != 0) { diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZLSStreamConnectionProvider.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZLSStreamConnectionProvider.kt index 466a4bf9..b73433ca 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZLSStreamConnectionProvider.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZLSStreamConnectionProvider.kt @@ -80,30 +80,6 @@ class ZLSStreamConnectionProvider private constructor(private val project: Proje return ZLSStreamConnectionProvider(project, commandLine) } - suspend fun validate(project: Project): Boolean { - val svc = project.zlsSettings - val state = svc.state - val zlsPath: Path = state.zlsPath.let { zlsPath -> - if (zlsPath.isEmpty()) { - val env = if (state.direnv) project.getDirenv() else emptyEnv - env.findExecutableOnPATH("zls") ?: run { - return false - } - } else { - zlsPath.toNioPathOrNull() ?: run { - return false - } - } - } - if (zlsPath.notExists()) { - return false - } - if (!zlsPath.isRegularFile() || !zlsPath.isExecutable()) { - return false - } - return true - } - @OptIn(ExperimentalSerializationApi::class) suspend fun getCommand(project: Project): List? { val svc = project.zlsSettings diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLanguageServerFactory.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLanguageServerFactory.kt index 0f639125..7e648348 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLanguageServerFactory.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/ZigLanguageServerFactory.kt @@ -22,6 +22,7 @@ package com.falsepattern.zigbrains.lsp +import com.falsepattern.zigbrains.lsp.settings.zlsSettings import com.falsepattern.zigbrains.shared.zigCoroutineScope import com.intellij.openapi.components.service import com.intellij.openapi.project.Project @@ -51,19 +52,11 @@ class ZigLanguageServerFactory: LanguageServerFactory, LanguageServerEnablementS } override fun isEnabled(project: Project): Boolean { - return (project.getUserData(ENABLED_KEY) ?: true) && if (application.isDispatchThread) { - runWithModalProgressBlocking(ModalTaskOwner.project(project), ZLSBundle.message("progress.title.validate")) { - ZLSStreamConnectionProvider.validate(project) - } - } else { - runBlocking { - ZLSStreamConnectionProvider.validate(project) - } - } + return (project.getUserData(ENABLED_KEY) ?: true) && project.zlsSettings.validate() } override fun setEnabled(enabled: Boolean, project: Project) { - project.putUserData(ENABLED_KEY, true) + project.putUserData(ENABLED_KEY, enabled) } } diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSProjectSettingsService.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSProjectSettingsService.kt index 1c8eaf22..d52f8674 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSProjectSettingsService.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSProjectSettingsService.kt @@ -22,32 +22,101 @@ 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.ZLSStreamConnectionProvider import com.intellij.openapi.components.* import com.intellij.openapi.project.Project +import com.intellij.openapi.util.io.toNioPathOrNull +import com.intellij.platform.ide.progress.ModalTaskOwner +import com.intellij.platform.ide.progress.runWithModalProgressBlocking +import com.intellij.util.application +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.sync.Mutex +import java.nio.file.Path +import java.util.concurrent.locks.ReentrantLock +import kotlin.concurrent.withLock +import kotlin.io.path.isExecutable +import kotlin.io.path.isRegularFile +import kotlin.io.path.notExists @Service(Service.Level.PROJECT) @State( name = "ZLSSettings", storages = [Storage(value = "zigbrains.xml")] ) -class ZLSProjectSettingsService: PersistentStateComponent { +class ZLSProjectSettingsService(val project: Project): PersistentStateComponent { @Volatile private var state = ZLSSettings() + @Volatile + private var dirty = true + @Volatile + private var valid = false + + private val mutex = ReentrantLock() override fun getState(): ZLSSettings { return state.copy() } fun setState(value: ZLSSettings) { - this.state = value + mutex.withLock { + this.state = value + dirty = true + } } override fun loadState(state: ZLSSettings) { - this.state = state + mutex.withLock { + this.state = state + dirty = true + } } fun isModified(otherData: ZLSSettings): Boolean { return state != otherData } + + fun validate(): 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) + } + } + dirty = false + } + return valid + } + } +} + +private suspend fun doValidate(project: Project, state: ZLSSettings): Boolean { + val zlsPath: Path = state.zlsPath.let { zlsPath -> + if (zlsPath.isEmpty()) { + val env = if (state.direnv) project.getDirenv() else emptyEnv + env.findExecutableOnPATH("zls") ?: run { + return false + } + } else { + zlsPath.toNioPathOrNull() ?: run { + return false + } + } + } + if (zlsPath.notExists()) { + return false + } + if (!zlsPath.isRegularFile() || !zlsPath.isExecutable()) { + return false + } + return true } val Project.zlsSettings get() = service() \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsPanel.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsPanel.kt index 1b9432f9..a50ba0ee 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsPanel.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/lsp/settings/ZLSSettingsPanel.kt @@ -24,18 +24,26 @@ package com.falsepattern.zigbrains.lsp.settings import com.falsepattern.zigbrains.direnv.* import com.falsepattern.zigbrains.lsp.ZLSBundle +import com.falsepattern.zigbrains.shared.coroutine.launchWithEDT import com.falsepattern.zigbrains.shared.zigCoroutineScope import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.application.asContextElement import com.intellij.openapi.components.service import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer +import com.intellij.platform.ide.progress.ModalTaskOwner +import com.intellij.platform.ide.progress.TaskCancellation +import com.intellij.platform.ide.progress.runWithModalProgressBlocking +import com.intellij.platform.ide.progress.withModalProgress import com.intellij.ui.components.JBCheckBox 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.util.application +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlin.io.path.pathString @@ -66,12 +74,14 @@ class ZLSSettingsPanel(private val project: Project?) : Disposable { group(ZLSBundle.message("settings.group.title")) { row(ZLSBundle.message("settings.zls-path.label")) { cell(zlsPath).resizableColumn().align(AlignX.FILL) - if (DirenvCmd.direnvInstalled() && project != null) { + if (DirenvCmd.direnvInstalled() && project != null && !project.isDefault) { cell(direnv) } button(ZLSBundle.message("settings.zls-path.autodetect.label")) { - project.zigCoroutineScope.launch { - autodetect() + project.zigCoroutineScope.launchWithEDT { + withModalProgress(ModalTaskOwner.component(zlsPath), "Detecting ZLS...", TaskCancellation.cancellable()) { + autodetect() + } } } } diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigExecConfig.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigExecConfig.kt index 20ba08db..38bec30e 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigExecConfig.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigExecConfig.kt @@ -25,6 +25,7 @@ package com.falsepattern.zigbrains.project.execution.base import com.falsepattern.zigbrains.ZigBrainsBundle import com.falsepattern.zigbrains.direnv.DirenvCmd import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.intellij.execution.ExecutionException import com.intellij.execution.Executor import com.intellij.execution.configurations.ConfigurationFactory import com.intellij.execution.configurations.GeneralCommandLine @@ -37,8 +38,9 @@ import com.intellij.openapi.project.guessProjectDir import com.intellij.openapi.util.NlsActions.ActionText import com.intellij.openapi.vfs.toNioPathOrNull import org.jdom.Element +import org.jetbrains.annotations.Nls -abstract class ZigExecConfig>(project: Project, factory: ConfigurationFactory, name: String): LocatableConfigurationBase>(project, factory, name) { +abstract class ZigExecConfig>(project: Project, factory: ConfigurationFactory, @Nls name: String): LocatableConfigurationBase>(project, factory, name) { var workingDirectory = WorkDirectoryConfigurable("workingDirectory").apply { path = project.guessProjectDir()?.toNioPathOrNull() } private set var pty = CheckboxConfigurable("pty", ZigBrainsBundle.message("exec.option.label.emulate-terminal"), false) @@ -47,6 +49,7 @@ abstract class ZigExecConfig>(project: Project, factory: Con private set abstract val suggestedName: @ActionText String + @Throws(ExecutionException::class) abstract suspend fun buildCommandLineArgs(debug: Boolean): List abstract override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigProfileState.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigProfileState.kt index aa679c67..59123175 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigProfileState.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigProfileState.kt @@ -58,7 +58,7 @@ abstract class ZigProfileState> ( } @Throws(ExecutionException::class) - suspend fun getCommandLine(toolchain: AbstractZigToolchain, debug: Boolean): GeneralCommandLine { + open suspend fun getCommandLine(toolchain: AbstractZigToolchain, debug: Boolean): GeneralCommandLine { val workingDir = configuration.workingDirectory val zigExePath = toolchain.zig.path() diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigConfigTypeBuild.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigConfigTypeBuild.kt index 3e01a0b6..ac5c41f1 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigConfigTypeBuild.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigConfigTypeBuild.kt @@ -36,10 +36,10 @@ class ZigConfigTypeBuild : ConfigurationTypeBase( Icons.ZIG ) { init { - addFactory(ConfigFactoryRun()) + addFactory(ConfigFactoryRun(this)) } - inner class ConfigFactoryRun: ConfigurationFactory(this@ZigConfigTypeBuild) { + class ConfigFactoryRun(type: ZigConfigTypeBuild): ConfigurationFactory(type) { override fun createTemplateConfiguration(project: Project): RunConfiguration { return ZigExecConfigBuild(project, this) } diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigExecConfigBuild.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigExecConfigBuild.kt index cf86b9b3..792f73c0 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigExecConfigBuild.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigExecConfigBuild.kt @@ -32,7 +32,7 @@ import com.intellij.execution.configurations.ConfigurationFactory import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.openapi.project.Project -class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigExecConfig(project, factory, "Zig Run") { +class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigExecConfig(project, factory, ZigBrainsBundle.message("exec.type.build.label")) { var buildSteps = ArgsConfigurable("buildSteps", ZigBrainsBundle.message("exec.option.label.build.steps")) private set var extraArgs = ArgsConfigurable("compilerArgs", ZigBrainsBundle.message("exec.option.label.build.args")) @@ -44,10 +44,11 @@ class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigEx var exeArgs = ArgsConfigurable("exeArgs", ZigBrainsBundle.message("exec.option.label.build.exe-args-debug")) private set + @Throws(ExecutionException::class) override suspend fun buildCommandLineArgs(debug: Boolean): List { val result = ArrayList() result.add("build") - var steps = buildSteps.args + val steps = buildSteps.args if (debug) { val truncatedSteps = ArrayList() for (step in steps) { diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigConfigTypeRun.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigConfigTypeRun.kt index 4bc1d6c6..bf975e0c 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigConfigTypeRun.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigConfigTypeRun.kt @@ -36,10 +36,10 @@ class ZigConfigTypeRun : ConfigurationTypeBase( Icons.ZIG ) { init { - addFactory(ConfigFactoryRun()) + addFactory(ConfigFactoryRun(this)) } - inner class ConfigFactoryRun: ConfigurationFactory(this@ZigConfigTypeRun) { + class ConfigFactoryRun(type: ZigConfigTypeRun): ConfigurationFactory(type) { override fun createTemplateConfiguration(project: Project): RunConfiguration { return ZigExecConfigRun(project, this) } diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigExecConfigRun.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigExecConfigRun.kt index c01bbf6e..5b0862e3 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigExecConfigRun.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigExecConfigRun.kt @@ -25,13 +25,14 @@ package com.falsepattern.zigbrains.project.execution.run import com.falsepattern.zigbrains.ZigBrainsBundle import com.falsepattern.zigbrains.project.execution.base.* import com.falsepattern.zigbrains.shared.cli.coloredCliFlags +import com.intellij.execution.ExecutionException import com.intellij.execution.Executor import com.intellij.execution.configurations.ConfigurationFactory import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.openapi.project.Project import kotlin.io.path.pathString -class ZigExecConfigRun(project: Project, factory: ConfigurationFactory): ZigExecConfig(project, factory, "Zig Run") { +class ZigExecConfigRun(project: Project, factory: ConfigurationFactory): ZigExecConfig(project, factory, ZigBrainsBundle.message("exec.type.run.label")) { var filePath = FilePathConfigurable("filePath", ZigBrainsBundle.message("exec.option.label.file-path")) private set var colored = ColoredConfigurable("colored") @@ -47,7 +48,7 @@ class ZigExecConfigRun(project: Project, factory: ConfigurationFactory): ZigExec val result = ArrayList() result.add(if (debug) "build-exe" else "run") result.addAll(coloredCliFlags(colored.value, debug)) - result.add(filePath.path?.pathString ?: throw IllegalArgumentException("Empty file path!")) + result.add(filePath.path?.pathString ?: throw ExecutionException(ZigBrainsBundle.message("exception.zig.empty-file-path"))) if (!debug || optimization.forced) { result.addAll(listOf("-O", optimization.level.name)) } diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigConfigTypeTest.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigConfigTypeTest.kt index 49d7684e..0bee020f 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigConfigTypeTest.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigConfigTypeTest.kt @@ -36,10 +36,10 @@ class ZigConfigTypeTest : ConfigurationTypeBase( Icons.ZIG ) { init { - addFactory(ConfigFactoryRun()) + addFactory(ConfigFactoryRun(this)) } - inner class ConfigFactoryRun: ConfigurationFactory(this@ZigConfigTypeTest) { + class ConfigFactoryRun(type: ZigConfigTypeTest): ConfigurationFactory(type) { override fun createTemplateConfiguration(project: Project): RunConfiguration { return ZigExecConfigTest(project, this) } diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigExecConfigTest.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigExecConfigTest.kt index 576e1bf8..df48ad57 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigExecConfigTest.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigExecConfigTest.kt @@ -25,13 +25,14 @@ package com.falsepattern.zigbrains.project.execution.test import com.falsepattern.zigbrains.ZigBrainsBundle import com.falsepattern.zigbrains.project.execution.base.* import com.falsepattern.zigbrains.shared.cli.coloredCliFlags +import com.intellij.execution.ExecutionException import com.intellij.execution.Executor import com.intellij.execution.configurations.ConfigurationFactory import com.intellij.execution.runners.ExecutionEnvironment import com.intellij.openapi.project.Project import kotlin.io.path.pathString -class ZigExecConfigTest(project: Project, factory: ConfigurationFactory): ZigExecConfig(project, factory, "Zig Run") { +class ZigExecConfigTest(project: Project, factory: ConfigurationFactory): ZigExecConfig(project, factory, ZigBrainsBundle.message("exec.type.test.label")) { var filePath = FilePathConfigurable("filePath", ZigBrainsBundle.message("exec.option.label.file-path")) private set var colored = ColoredConfigurable("colored") @@ -41,11 +42,12 @@ class ZigExecConfigTest(project: Project, factory: ConfigurationFactory): ZigExe var compilerArgs = ArgsConfigurable("compilerArgs", ZigBrainsBundle.message("exec.option.label.compiler-args")) private set + @Throws(ExecutionException::class) override suspend fun buildCommandLineArgs(debug: Boolean): List { val result = ArrayList() result.add(if (debug) "build-exe" else "run") result.addAll(coloredCliFlags(colored.value, debug)) - result.add(filePath.path?.pathString ?: throw IllegalArgumentException("Empty file path!")) + result.add(filePath.path?.pathString ?: throw ExecutionException(ZigBrainsBundle.message("exception.zig.empty-file-path"))) if (!debug || optimization.forced) { result.addAll(listOf("-O", optimization.level.name)) } diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/module/ZigModuleBuilder.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/module/ZigModuleBuilder.kt index b1376cef..7e2d2450 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/module/ZigModuleBuilder.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/module/ZigModuleBuilder.kt @@ -24,6 +24,7 @@ package com.falsepattern.zigbrains.project.module import com.falsepattern.zigbrains.project.newproject.ZigProjectConfigurationData import com.falsepattern.zigbrains.project.newproject.ZigProjectGeneratorPeer +import com.falsepattern.zigbrains.shared.coroutine.withEDTContext import com.intellij.ide.util.projectWizard.ModuleBuilder import com.intellij.ide.util.projectWizard.ModuleWizardStep import com.intellij.ide.util.projectWizard.WizardContext @@ -61,7 +62,7 @@ class ZigModuleBuilder: ModuleBuilder() { val root = contentEntry.file ?: return val config = configurationData ?: return config.generateProject(this, rootModel.project, root, forceGitignore) - withContext(Dispatchers.EDT) { + withEDTContext { root.refresh(false, true) } } diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectConfigurationData.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectConfigurationData.kt index 27fa1f2b..ef42fb33 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectConfigurationData.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/newproject/ZigProjectConfigurationData.kt @@ -28,6 +28,7 @@ import com.falsepattern.zigbrains.project.settings.ZigProjectSettings import com.falsepattern.zigbrains.project.settings.zigProjectSettings import com.falsepattern.zigbrains.project.template.ZigInitTemplate import com.falsepattern.zigbrains.project.template.ZigProjectTemplate +import com.falsepattern.zigbrains.shared.coroutine.withEDTContext import com.intellij.notification.Notification import com.intellij.notification.NotificationType import com.intellij.openapi.GitRepositoryInitializer @@ -36,6 +37,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.toNioPathOrNull +import com.intellij.platform.util.progress.withProgressText import com.intellij.util.ResourceUtil import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -48,7 +50,7 @@ data class ZigProjectConfigurationData( val selectedTemplate: ZigProjectTemplate ) { suspend fun generateProject(requestor: Any, project: Project, baseDir: VirtualFile, forceGitignore: Boolean): Boolean { - withContext(Dispatchers.EDT) { + withEDTContext { project.zigProjectSettings.loadState(projConf) project.zlsSettings.loadState(zlsConf) } @@ -89,7 +91,7 @@ data class ZigProjectConfigurationData( val (fileName, parentDir) = fileTemplate.key.let { if (it.contains("/")) { val slashIndex = it.indexOf("/") - val parentDir = withContext(Dispatchers.EDT) { + val parentDir = withEDTContext { baseDir.createChildDirectory(requestor, it.substring(0, slashIndex)) } Pair(it.substring(slashIndex + 1), parentDir) @@ -101,7 +103,7 @@ data class ZigProjectConfigurationData( val resourceData = getResourceString("project-gen/$templateDir/$fileName.template") ?.replace("@@PROJECT_NAME@@", projectName) ?: continue - withContext(Dispatchers.EDT) { + withEDTContext { val targetFile = parentDir.createChildData(requestor, fileName) VfsUtil.saveText(targetFile, resourceData) } diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigProgramRunner.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigProgramRunner.kt index 737358f7..5f286c75 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigProgramRunner.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigProgramRunner.kt @@ -25,7 +25,9 @@ package com.falsepattern.zigbrains.project.run import com.falsepattern.zigbrains.project.execution.base.ZigProfileState import com.falsepattern.zigbrains.project.settings.zigProjectSettings import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain +import com.falsepattern.zigbrains.shared.coroutine.withEDTContext import com.falsepattern.zigbrains.shared.zigCoroutineScope +import com.intellij.execution.ExecutionException import com.intellij.execution.configurations.RunProfileState import com.intellij.execution.configurations.RunnerSettings import com.intellij.execution.runners.AsyncProgramRunner @@ -34,6 +36,13 @@ import com.intellij.execution.ui.RunContentDescriptor import com.intellij.openapi.application.EDT import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.rd.util.toPromise +import com.intellij.platform.ide.progress.ModalTaskOwner +import com.intellij.platform.ide.progress.TaskCancellation +import com.intellij.platform.ide.progress.withModalProgress +import com.intellij.platform.util.progress.ProgressReporter +import com.intellij.platform.util.progress.SequentialProgressReporter +import com.intellij.platform.util.progress.reportProgress +import com.intellij.platform.util.progress.withProgressText import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.async @@ -44,26 +53,35 @@ abstract class ZigProgramRunner>(protected val @OptIn(ExperimentalCoroutinesApi::class) override fun execute(environment: ExecutionEnvironment, state: RunProfileState): Promise { return environment.project.zigCoroutineScope.async { - executeAsync(environment, state) + withModalProgress(ModalTaskOwner.project(environment.project), "Starting zig program...", TaskCancellation.cancellable()) { + executeAsync(environment, state) + } }.toPromise() } - private suspend inline fun executeAsync(environment: ExecutionEnvironment, state: RunProfileState): RunContentDescriptor? { - if (state !is ZigProfileState<*>) + private suspend inline fun executeAsync(environment: ExecutionEnvironment, baseState: RunProfileState): RunContentDescriptor? { + if (baseState !is ZigProfileState<*>) return null - val state = castProfileState(state) ?: return null + val state = castProfileState(baseState) ?: return null val toolchain = environment.project.zigProjectSettings.state.toolchain ?: return null - withContext(Dispatchers.EDT) { - FileDocumentManager.getInstance().saveAllDocuments() + return reportProgress { reporter -> + reporter.indeterminateStep("Saving all documents") { + withEDTContext { + FileDocumentManager.getInstance().saveAllDocuments() + } + } + return@reportProgress reporter.indeterminateStep { + return@indeterminateStep execute(state, toolchain, environment) + } } - return execute(state, toolchain, environment) } protected abstract fun castProfileState(state: ZigProfileState<*>): ProfileState? + @Throws(ExecutionException::class) abstract suspend fun execute(state: ProfileState, toolchain: AbstractZigToolchain, environment: ExecutionEnvironment): RunContentDescriptor? } \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigRegularRunner.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigRegularRunner.kt index f2e690bc..9c00194c 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigRegularRunner.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigRegularRunner.kt @@ -26,23 +26,23 @@ import com.falsepattern.zigbrains.project.execution.base.ZigExecConfig import com.falsepattern.zigbrains.project.execution.base.ZigProfileState import com.falsepattern.zigbrains.project.execution.base.executeCommandLine import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain -import com.falsepattern.zigbrains.shared.zigCoroutineScope +import com.falsepattern.zigbrains.shared.coroutine.withEDTContext import com.intellij.execution.configurations.RunProfile import com.intellij.execution.executors.DefaultRunExecutor import com.intellij.execution.runners.ExecutionEnvironment -import com.intellij.execution.runners.showRunContent +import com.intellij.execution.runners.RunContentBuilder import com.intellij.execution.ui.RunContentDescriptor import com.intellij.openapi.application.EDT import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext class ZigRegularRunner: ZigProgramRunner>(DefaultRunExecutor.EXECUTOR_ID) { override suspend fun execute(state: ZigProfileState<*>, toolchain: AbstractZigToolchain, environment: ExecutionEnvironment): RunContentDescriptor? { val cli = state.getCommandLine(toolchain, false) val exec = executeCommandLine(cli, environment) - return withContext(Dispatchers.EDT) { - showRunContent(exec, environment) + return withEDTContext { + val runContentBuilder = RunContentBuilder(exec, environment) + runContentBuilder.showRunContent(null) } } diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettingsPanel.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettingsPanel.kt index 717d04ad..e3547541 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettingsPanel.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/settings/ZigProjectSettingsPanel.kt @@ -26,6 +26,8 @@ import com.falsepattern.zigbrains.ZigBrainsBundle 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.EDT @@ -35,6 +37,9 @@ 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 +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 @@ -115,8 +120,10 @@ class ZigProjectSettingsPanel(private val project: Project?) : Disposable { cell(direnv) } button(ZigBrainsBundle.message("settings.project.label.toolchain-autodetect")) { - this@ZigProjectSettingsPanel.project.zigCoroutineScope.launch { - autodetect() + project.zigCoroutineScope.launchWithEDT { + withModalProgress(ModalTaskOwner.component(pathToToolchain), "Detecting Zig...", TaskCancellation.cancellable()) { + autodetect() + } } } } @@ -146,7 +153,7 @@ class ZigProjectSettingsPanel(private val project: Project?) : Disposable { val env = zig?.getEnv(project) val version = env?.version val stdPath = env?.stdPath(toolchain) - withContext(Dispatchers.EDT) { + withEDTContext { toolchainVersion.text = version ?: "" toolchainVersion.foreground = JBColor.foreground() diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigStepDiscoveryService.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigStepDiscoveryService.kt index 59b8179a..82499ac6 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigStepDiscoveryService.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/discovery/ZigStepDiscoveryService.kt @@ -24,6 +24,7 @@ package com.falsepattern.zigbrains.project.steps.discovery import com.falsepattern.zigbrains.project.settings.zigProjectSettings import com.falsepattern.zigbrains.project.steps.discovery.ZigStepDiscoveryListener.ErrorType +import com.falsepattern.zigbrains.shared.coroutine.withEDTContext import com.falsepattern.zigbrains.shared.zigCoroutineScope import com.intellij.openapi.Disposable import com.intellij.openapi.application.EDT @@ -120,7 +121,7 @@ class ZigStepDiscoveryService(private val project: Project) { } private suspend fun dispatchReload() { - withContext(Dispatchers.EDT) { + withEDTContext { FileDocumentManager.getInstance().saveAllDocuments() } doReload() diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BuildToolWindowContext.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BuildToolWindowContext.kt index 6424712e..8f313c68 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BuildToolWindowContext.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/steps/ui/BuildToolWindowContext.kt @@ -29,6 +29,7 @@ import com.falsepattern.zigbrains.project.execution.build.ZigExecConfigBuild import com.falsepattern.zigbrains.project.execution.firstConfigFactory import com.falsepattern.zigbrains.project.steps.discovery.ZigStepDiscoveryListener import com.falsepattern.zigbrains.project.steps.discovery.zigStepDiscovery +import com.falsepattern.zigbrains.shared.coroutine.withEDTContext import com.intellij.execution.ProgramRunnerUtil import com.intellij.execution.RunManager import com.intellij.execution.RunnerAndConfigurationSettings @@ -137,7 +138,7 @@ class BuildToolWindowContext(private val project: Project): Disposable { companion object { suspend fun create(project: Project, window: ToolWindow) { - withContext(Dispatchers.EDT) { + withEDTContext { val context = BuildToolWindowContext(project) Disposer.register(context, project.zigStepDiscovery.register(context.BuildReloadListener())) Disposer.register(window.disposable, context) @@ -161,13 +162,13 @@ class BuildToolWindowContext(private val project: Project): Disposable { } buildZig.add(DefaultMutableTreeNode(StepNodeDescriptor(project, task, icon, description))) } - withContext(Dispatchers.EDT) { + withEDTContext { getViewport(project)?.let { setViewportTree(it) } } } override suspend fun errorReload(type: ZigStepDiscoveryListener.ErrorType, details: String?) { - withContext(Dispatchers.EDT) { + withEDTContext { getViewport(project)?.setViewportError(ZigBrainsBundle.message(when(type) { ZigStepDiscoveryListener.ErrorType.MissingToolchain -> "build.tool.window.status.error.missing-toolchain" ZigStepDiscoveryListener.ErrorType.MissingBuildZig -> "build.tool.window.status.error.missing-build-zig" @@ -177,7 +178,7 @@ class BuildToolWindowContext(private val project: Project): Disposable { } override suspend fun timeoutReload(seconds: Int) { - withContext(Dispatchers.EDT) { + withEDTContext { getViewport(project)?.setViewportError(ZigBrainsBundle.message("build.tool.window.status.timeout", seconds), null) } } diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchain.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchain.kt index 111876ec..b8d276d8 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchain.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/LocalZigToolchain.kt @@ -24,6 +24,7 @@ package com.falsepattern.zigbrains.project.toolchain import com.falsepattern.zigbrains.direnv.DirenvCmd import com.falsepattern.zigbrains.project.settings.zigProjectSettings +import com.intellij.execution.ExecutionException import com.intellij.execution.configurations.GeneralCommandLine import com.intellij.openapi.project.Project import com.intellij.openapi.project.guessProjectDir @@ -52,5 +53,15 @@ class LocalZigToolchain(val location: Path): AbstractZigToolchain() { companion object { val DIRENV_KEY = KeyWithDefaultValue.create("ZIG_LOCAL_DIRENV") + + @Throws(ExecutionException::class) + fun ensureLocal(toolchain: AbstractZigToolchain): LocalZigToolchain { + if (toolchain is LocalZigToolchain) { + return toolchain + } else { + // TODO + throw ExecutionException("The debugger only supports local zig toolchain") + } + } } } \ No newline at end of file diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigTool.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigTool.kt index 6271f29a..133dbcb6 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigTool.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/project/toolchain/tools/ZigTool.kt @@ -26,7 +26,9 @@ import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain 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 @@ -34,10 +36,14 @@ 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 process = createBaseCommandLine(workingDirectory, *parameters).createProcess() + val cli = createBaseCommandLine(workingDirectory, *parameters) - val exitCode = withTimeoutOrNull(timeoutMillis) { - process.awaitExit() + val (process, exitCode) = withContext(Dispatchers.IO) { + val process = cli.createProcess() + val exit = withTimeoutOrNull(timeoutMillis) { + process.awaitExit() + } + process to exit } return runInterruptible { ProcessOutput( @@ -50,8 +56,10 @@ abstract class ZigTool(val toolchain: AbstractZigToolchain) { } } - private suspend fun createBaseCommandLine(workingDirectory: Path?, - vararg parameters: String): GeneralCommandLine { + private suspend fun createBaseCommandLine( + workingDirectory: Path?, + vararg parameters: String + ): GeneralCommandLine { val cli = GeneralCommandLine() .withExePath(toolchain.pathToExecutable(toolName).toString()) .withWorkDirectory(workingDirectory?.toString()) diff --git a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/coroutine/CoroutinesUtil.kt b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/coroutine/CoroutinesUtil.kt index 618637c7..ad21a6f6 100644 --- a/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/coroutine/CoroutinesUtil.kt +++ b/modules/core/src/main/kotlin/com/falsepattern/zigbrains/shared/coroutine/CoroutinesUtil.kt @@ -22,13 +22,17 @@ package com.falsepattern.zigbrains.shared.coroutine +import com.intellij.openapi.application.EDT +import com.intellij.openapi.application.ModalityState +import com.intellij.openapi.application.asContextElement import com.intellij.platform.ide.progress.ModalTaskOwner import com.intellij.platform.ide.progress.TaskCancellation import com.intellij.platform.ide.progress.runWithModalProgressBlocking +import com.intellij.platform.ide.progress.withModalProgress +import com.intellij.platform.util.progress.withProgressText import com.intellij.util.SuspendingLazy import com.intellij.util.application -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.* inline fun runModalOrBlocking(taskOwnerFactory: () -> ModalTaskOwner, titleFactory: () -> String, cancellationFactory: () -> TaskCancellation = TaskCancellation::cancellable, noinline action: suspend CoroutineScope.() -> T): T { return if (application.isDispatchThread) { @@ -46,4 +50,16 @@ inline fun SuspendingLazy.getOrAwaitModalOrBlocking(taskOwnerFactory: () getValue() } } +} + +suspend inline fun withEDTContext(state: ModalityState = ModalityState.defaultModalityState(), noinline block: suspend CoroutineScope.() -> T): T { + return withContext(Dispatchers.EDT + state.asContextElement(), block = block) +} + +suspend inline fun runInterruptibleEDT(state: ModalityState = ModalityState.defaultModalityState(), noinline targetAction: () -> T): T { + return runInterruptible(Dispatchers.EDT + state.asContextElement(), block = targetAction) +} + +fun CoroutineScope.launchWithEDT(state: ModalityState = ModalityState.defaultModalityState(), block: suspend CoroutineScope.() -> Unit) { + launch(Dispatchers.EDT + state.asContextElement(), block = block) } \ No newline at end of file diff --git a/modules/core/src/main/resources/zigbrains/Bundle.properties b/modules/core/src/main/resources/zigbrains/Bundle.properties index f8809b62..0faa0150 100644 --- a/modules/core/src/main/resources/zigbrains/Bundle.properties +++ b/modules/core/src/main/resources/zigbrains/Bundle.properties @@ -71,6 +71,9 @@ notification.content.native-debug.browser=Open in Browser dialog.title.working-directory=Select Working Directory dialog.title.zig-toolchain=Path to the Zig Toolchain dialog.title.zig-std=Path to the Standard Library +exec.type.run.label=Zig Run +exec.type.test.label=Zig Test +exec.type.build.label=Zig Build exec.option.label.working-directory=&Working directory: exec.option.label.colored-terminal=Colored terminal exec.option.label.direnv=Use direnv @@ -84,6 +87,7 @@ exec.option.label.build.steps=Build steps exec.option.label.build.args=Extra command line arguments exec.option.label.build.exe-args-debug=Output program command line arguments (debug only) exec.option.label.build.exe-path-debug=Output executable created by the build (debug only, autodetect if empty) +exception.zig.empty-file-path=Empty file path exception.translate-command-line.unbalanced-quotes=Unbalanced quotes in {0} exception.zig-profile-state.start-process.no-toolchain=Failed to get zig toolchain from project exception.zig-build.debug.test-not-supported=Debugging "zig build test" is not yet supported diff --git a/settings.gradle.kts b/settings.gradle.kts index 0d1e0c3c..066b7da9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -3,7 +3,7 @@ plugins { } rootProject.name = "ZigBrains" -for (module in arrayOf("core")) { +for (module in arrayOf("core", "cidr")) { include(module) project(":$module").projectDir = file("modules/$module") } diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index f9140bda..f011c137 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -5,6 +5,7 @@ com.intellij.modules.platform com.redhat.devtools.lsp4ij + com.intellij.modules.cidr.debugger zigbrains.Bundle @@ -24,5 +25,10 @@ dynamic="true" name="featureProvider" /> + \ No newline at end of file