Debugging and some coroutine fixes here and there

This commit is contained in:
FalsePattern 2024-11-03 16:17:30 +01:00
parent a7e3f3e161
commit 3b628467f7
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
68 changed files with 2866 additions and 102 deletions

View file

@ -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 {

View file

@ -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
org.gradle.configuration-cache=true
org.gradle.caching=true

View file

@ -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")
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ZigDebuggerDriverConfigurationProvider>("com.falsepattern.zigbrains.debuggerDriverProvider")
}
suspend fun <T> getDebuggerConfiguration(project: Project, isElevated: Boolean, emulateTerminal: Boolean, klass: Class<T>): T?
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.debugger
import com.falsepattern.zigbrains.shared.ZBFeatures
class DebuggerFeatures: ZBFeatures {
override fun getDebug(): Boolean {
return true
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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)
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.debugger
import com.jetbrains.cidr.execution.debugger.CidrDebuggerEditorsExtensionBase
class ZigDebuggerEditorsExtension: CidrDebuggerEditorsExtensionBase() {
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.debugger
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriver.DebuggerLanguage
object ZigDebuggerLanguage: DebuggerLanguage {
override fun toString(): String {
return "Zig"
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<DebuggerDriver.DebuggerLanguage> {
return setOf(ZigDebuggerLanguage)
}
override fun createEditor(profile: RunProfile?): XDebuggerEditorsProvider? {
if (profile !is ZigExecConfig<*>)
return null
return createEditorProvider()
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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 <T> getDebuggerConfiguration(project: Project, isElevated: Boolean, emulateTerminal: Boolean, klass: Class<T>): 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
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<FileType> {
return setOf(ZigFileType)
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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)

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<LLValue>): CompletableFuture<List<LLValue>> {
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
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<LLValue, String>): String {
return cast(pair.second, pair.first?.type)
}
}
}
abstract fun customizeInitializeArguments(initArgs: InitializeRequestArguments)
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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"

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ZigExecConfigBinary>(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<String> {
return args.args
}
override fun getConfigurables(): List<ZigConfigurable<*>> {
return super.getConfigurables() + listOf(exePath, args)
}
override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState<ZigExecConfigBinary> {
return ZigProfileStateBinary(environment, this)
}
override fun clone(): ZigExecConfigBinary {
val clone = super.clone()
clone.exePath = exePath.clone()
clone.args = args.clone()
return clone
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ZigExecConfigBinary>(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
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.debugger.runner.base
import com.intellij.execution.ExecutionException
interface PreLaunchAware {
@Throws(ExecutionException::class)
suspend fun preLaunch(listener: PreLaunchProcessListener)
}

View file

@ -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)
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ProfileState: ZigProfileState<*>>(
private val profileState: ProfileState,
private val toolchain: AbstractZigToolchain,
private val executableFile: File,
private val exeArgs: List<String>
): 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
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ProfileState: ZigProfileState<*>>(
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
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ProfileState: ZigProfileState<*>>(
driverConfiguration: DebuggerDriverConfiguration,
toolchain: AbstractZigToolchain,
profileState: ProfileState,
) : ZigDebugParametersBase<ProfileState>(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)
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ProfileState : ZigProfileState<*>> : ZigProgramRunner<ProfileState>(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<ProfileState>
private class SharedConsoleBuilder(private val console: ConsoleView) : TextConsoleBuilder() {
override fun getConsole(): ConsoleView {
return console
}
override fun addFilter(filter: Filter) {
}
override fun setViewer(isViewer: Boolean) {
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ZigProfileStateBinary>(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)
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ZigProfileStateBinary>() {
override fun getDebugParameters(
state: ZigProfileStateBinary,
debuggerDriver: DebuggerDriverConfiguration,
toolchain: AbstractZigToolchain
): ZigDebugParametersBase<ZigProfileStateBinary> {
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"
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ZigProfileStateBuild>(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>): 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)))
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ZigProfileStateBuild>() {
override fun getDebugParameters(
state: ZigProfileStateBuild,
debuggerDriver: DebuggerDriverConfiguration,
toolchain: AbstractZigToolchain
): ZigDebugParametersBase<ZigProfileStateBuild> {
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"
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ZigProfileStateRun>(driverConfiguration, toolchain, profileState) {
override fun getInstaller(): Installer {
return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, profileState.configuration.exeArgs.args)
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ZigProfileStateRun>() {
override fun getDebugParameters(
state: ZigProfileStateRun,
debuggerDriver: DebuggerDriverConfiguration,
toolchain: AbstractZigToolchain
): ZigDebugParametersBase<ZigProfileStateRun> {
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"
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ZigProfileStateTest>(driverConfiguration, toolchain, profileState) {
override fun getInstaller(): Installer {
return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, listOf())
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ZigProfileStateTest>() {
override fun getDebugParameters(
state: ZigProfileStateTest,
debuggerDriver: DebuggerDriverConfiguration,
toolchain: AbstractZigToolchain
): ZigDebugParametersBase<ZigProfileStateTest> {
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"
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ZigDebuggerSettings>, Disposable {
private val components = listOf<ZigDebuggerUiComponent>(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() {
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ZigDebuggerSettings>("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<Configurable> {
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"

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<DebuggerKind> {
val toolchainService = zigDebuggerToolchainService
val availableKinds = DebuggerKind.entries.filter { toolchainService.debuggerAvailability(it) !is DebuggerAvailability.Unavailable }
return DefaultComboBoxModel(availableKinds.toTypedArray()).also { it.selectedItem = ZigDebuggerSettings.instance.debuggerKind }
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<ZigDebuggerSettings>, Disposable {
fun buildUi(panel: Panel)
override fun getComponent(): JComponent {
return panel {
buildUi(this)
}
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.debugger.toolchain
import java.nio.file.Path
sealed class DebuggerAvailability<out T> {
data object Unavailable: DebuggerAvailability<Nothing>()
data object NeedToDownload: DebuggerAvailability<Nothing>()
data object NeedToUpdate: DebuggerAvailability<Nothing>()
data object Bundled: DebuggerAvailability<Nothing>()
data class Binaries<T> (val binaries: T): DebuggerAvailability<T>()
}
data class LLDBBinaries(val frameworkFile: Path, val frontendFile: Path)
data class GDBBinaries(val gdbFile: Path)
data class MSVCBinaries(val msvcFile: Path)

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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
}
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<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
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<LLDBBinaries> {
// 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<GDBBinaries> {
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<MSVCBinaries> {
// 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<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<DownloadableDebuggerBinary>) {
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<URL, URL>? {
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<ZigDebuggerToolchainService>()
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<ZigDebuggerToolchainService>()

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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"
}
}

View file

@ -0,0 +1,60 @@
<idea-plugin package="com.falsepattern.zigbrains.debugger">
<depends>com.intellij.modules.cidr.debugger</depends>
<extensions defaultExtensionNs="com.intellij">
<configurationType
implementation="com.falsepattern.zigbrains.debugger.execution.binary.ZigConfigTypeBinary"
/>
<programRunner
id="ZigDebugRunnerRun"
implementation="com.falsepattern.zigbrains.debugger.runner.run.ZigDebugRunnerRun"
/>
<programRunner
id="ZigDebugRunnerTest"
implementation="com.falsepattern.zigbrains.debugger.runner.test.ZigDebugRunnerTest"
/>
<programRunner
id="ZigDebugRunnerBuild"
implementation="com.falsepattern.zigbrains.debugger.runner.build.ZigDebugRunnerBuild"
/>
<programRunner
id="ZigDebugRunnerBinary"
implementation="com.falsepattern.zigbrains.debugger.runner.binary.ZigDebugRunnerBinary"
/>
<xdebugger.settings
implementation="com.falsepattern.zigbrains.debugger.settings.ZigDebuggerSettings"
/>
</extensions>
<extensions defaultExtensionNs="com.falsepattern.zigbrains">
<featureProvider
implementation="com.falsepattern.zigbrains.debugger.DebuggerFeatures"
/>
<debuggerDriverProvider
id="DefaultProvider"
implementation="com.falsepattern.zigbrains.debugger.ZigDefaultDebuggerDriverConfigurationProvider"
order="last"
/>
</extensions>
<extensions defaultExtensionNs="cidr.debugger">
<languageSupport
language="Zig"
implementationClass="com.falsepattern.zigbrains.debugger.ZigDebuggerLanguageSupport"
/>
<editorsExtension
language="Zig"
implementationClass="com.falsepattern.zigbrains.debugger.ZigDebuggerEditorsExtension"
/>
<lineBreakpointFileTypesProvider
implementation="com.falsepattern.zigbrains.debugger.ZigLineBreakpointFileTypesProvider"
/>
<localVariablesFilterHandler
implementation="com.falsepattern.zigbrains.debugger.ZigLocalVariablesFilterHandler"
/>
</extensions>
<extensionPoints>
</extensionPoints>
</idea-plugin>

View file

@ -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 <a>downloaded</a>
settings.debugger.toolchain.update.comment=Need to be <a>updated</a>
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.

View file

@ -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) {

View file

@ -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<String>? {
val svc = project.zlsSettings

View file

@ -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)
}
}

View file

@ -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<ZLSSettings> {
class ZLSProjectSettingsService(val project: Project): PersistentStateComponent<ZLSSettings> {
@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<ZLSProjectSettingsService>()

View file

@ -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()
}
}
}
}

View file

@ -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<T: ZigExecConfig<T>>(project: Project, factory: ConfigurationFactory, name: String): LocatableConfigurationBase<ZigProfileState<T>>(project, factory, name) {
abstract class ZigExecConfig<T: ZigExecConfig<T>>(project: Project, factory: ConfigurationFactory, @Nls name: String): LocatableConfigurationBase<ZigProfileState<T>>(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<T: ZigExecConfig<T>>(project: Project, factory: Con
private set
abstract val suggestedName: @ActionText String
@Throws(ExecutionException::class)
abstract suspend fun buildCommandLineArgs(debug: Boolean): List<String>
abstract override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState<T>

View file

@ -58,7 +58,7 @@ abstract class ZigProfileState<T: ZigExecConfig<T>> (
}
@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()

View file

@ -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)
}

View file

@ -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<ZigExecConfigBuild>(project, factory, "Zig Run") {
class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigExecConfig<ZigExecConfigBuild>(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<String> {
val result = ArrayList<String>()
result.add("build")
var steps = buildSteps.args
val steps = buildSteps.args
if (debug) {
val truncatedSteps = ArrayList<String>()
for (step in steps) {

View file

@ -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)
}

View file

@ -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<ZigExecConfigRun>(project, factory, "Zig Run") {
class ZigExecConfigRun(project: Project, factory: ConfigurationFactory): ZigExecConfig<ZigExecConfigRun>(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<String>()
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))
}

View file

@ -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)
}

View file

@ -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<ZigExecConfigTest>(project, factory, "Zig Run") {
class ZigExecConfigTest(project: Project, factory: ConfigurationFactory): ZigExecConfig<ZigExecConfigTest>(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<String> {
val result = ArrayList<String>()
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))
}

View file

@ -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)
}
}

View file

@ -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)
}

View file

@ -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<ProfileState: ZigProfileState<*>>(protected val
@OptIn(ExperimentalCoroutinesApi::class)
override fun execute(environment: ExecutionEnvironment, state: RunProfileState): Promise<RunContentDescriptor?> {
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?
}

View file

@ -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<ZigProfileState<*>>(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)
}
}

View file

@ -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()

View file

@ -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()

View file

@ -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)
}
}

View file

@ -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<Boolean>("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")
}
}
}
}

View file

@ -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())

View file

@ -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 <T> 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 <T> SuspendingLazy<T>.getOrAwaitModalOrBlocking(taskOwnerFactory: ()
getValue()
}
}
}
suspend inline fun <T> withEDTContext(state: ModalityState = ModalityState.defaultModalityState(), noinline block: suspend CoroutineScope.() -> T): T {
return withContext(Dispatchers.EDT + state.asContextElement(), block = block)
}
suspend inline fun <T> 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)
}

View file

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

View file

@ -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")
}

View file

@ -5,6 +5,7 @@
<depends config-file="zigbrains-core.xml">com.intellij.modules.platform</depends>
<depends config-file="zigbrains-lsp.xml">com.redhat.devtools.lsp4ij</depends>
<depends config-file="zigbrains-debugger.xml">com.intellij.modules.cidr.debugger</depends>
<resource-bundle>zigbrains.Bundle</resource-bundle>
@ -24,5 +25,10 @@
dynamic="true"
name="featureProvider"
/>
<extensionPoint
interface="com.falsepattern.zigbrains.debugbridge.ZigDebuggerDriverConfigurationProvider"
dynamic="true"
name="debuggerDriverProvider"
/>
</extensionPoints>
</idea-plugin>