backport: 24.0.0

This commit is contained in:
FalsePattern 2025-03-27 20:40:47 +01:00
parent 3954ff8ff5
commit db85b56084
Signed by: falsepattern
GPG key ID: E930CDEC50C50E23
30 changed files with 179 additions and 209 deletions

View file

@ -17,6 +17,30 @@ Changelog structure reference:
## [Unreleased]
## [24.0.0]
### Added
- Project, Debugging
- TTY support for zig processes
### Removed
- Project
- "Emulate terminal" and "colored output" config options have been removed from zig run/test/build tasks, as they are no longer required for ZigBrains to work.
### Fixed
- Debugger
- Build errors didn't get shown in the console
- Project
- File path browse buttons in zig run configurations didn't work
- Occasional GUI deadlocks
- Zig
- IPC wrapper wasn't passing exit code
## [23.1.2]
### Fixed

View file

@ -57,8 +57,8 @@ class ZigClionDebuggerDriverConfigurationProvider: ZigDebuggerDriverConfiguratio
}
return when(toolchain.debuggerKind) {
CPPDebugger.Kind.BUNDLED_GDB,
CPPDebugger.Kind.CUSTOM_GDB -> CLionGDBDriverConfiguration(project, toolchain)
CPPDebugger.Kind.BUNDLED_LLDB -> CLionLLDBDriverConfiguration(project, toolchain)
CPPDebugger.Kind.CUSTOM_GDB -> CLionGDBDriverConfiguration(project, toolchain, isEmulateTerminal = emulateTerminal)
CPPDebugger.Kind.BUNDLED_LLDB -> CLionLLDBDriverConfiguration(project, toolchain, isEmulateTerminal = emulateTerminal)
}
}
}

View file

@ -22,9 +22,10 @@
package com.falsepattern.zigbrains.debugger
import com.intellij.execution.filters.Filter
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)
class ZigLocalDebugProcess(parameters: RunParameters, session: XDebugSession, consoleBuilder: TextConsoleBuilder) : CidrLocalDebugProcess(parameters, session, consoleBuilder, { Filter.EMPTY_ARRAY }, true)

View file

@ -23,6 +23,7 @@
package com.falsepattern.zigbrains.debugger.runner.base
import com.falsepattern.zigbrains.project.run.ZigProcessHandler
import com.falsepattern.zigbrains.shared.cli.startIPCAwareProcess
import com.falsepattern.zigbrains.shared.ipc.IPCUtil
import com.falsepattern.zigbrains.shared.ipc.ipc
import com.intellij.execution.ExecutionException
@ -48,12 +49,7 @@ class PreLaunchProcessListener(val console: ConsoleView) : ProcessListener {
@Throws(ExecutionException::class)
suspend fun executeCommandLineWithHook(project: Project, commandLine: GeneralCommandLine): Boolean {
return withProgressText(commandLine.commandLineString) {
val ipc = IPCUtil.wrapWithIPC(commandLine)
val cli = ipc?.cli ?: commandLine
val processHandler = ZigProcessHandler(cli)
if (ipc != null) {
project.ipc?.launchWatcher(ipc, processHandler.process)
}
val processHandler = commandLine.startIPCAwareProcess(project)
this@PreLaunchProcessListener.processHandler = processHandler
hook(processHandler)
processHandler.startNotify()
@ -74,10 +70,6 @@ class PreLaunchProcessListener(val console: ConsoleView) : ProcessListener {
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

View file

@ -26,6 +26,7 @@ import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.configurations.PtyCommandLine
import com.jetbrains.cidr.execution.Installer
import kotlinx.coroutines.async
import kotlinx.coroutines.future.asCompletableFuture
@ -39,7 +40,7 @@ class ZigDebugEmitBinaryInstaller<ProfileState: ZigProfileState<*>>(
): Installer {
override fun install(): GeneralCommandLine {
val cfg = profileState.configuration
val cli = GeneralCommandLine().withExePath(executableFile.absolutePath)
val cli = PtyCommandLine().withConsoleMode(false).withExePath(executableFile.absolutePath)
cfg.workingDirectory.path?.let { x -> cli.withWorkDirectory(x.toFile()) }
cli.addParameters(exeArgs)
cli.withCharset(Charsets.UTF_8)

View file

@ -58,7 +58,7 @@ abstract class ZigDebugRunnerBase<ProfileState : ZigProfileState<*>> : ZigProgra
val project = environment.project
val driverProviders = ZigDebuggerDriverConfigurationProviderBase.EXTENSION_POINT_NAME.extensionList
for (provider in driverProviders) {
val driver = provider.getDebuggerConfiguration(project, isElevated = false, emulateTerminal = false, DebuggerDriverConfiguration::class.java) ?: continue
val driver = provider.getDebuggerConfiguration(project, isElevated = false, emulateTerminal = true, DebuggerDriverConfiguration::class.java) ?: continue
return executeWithDriver(state, toolchain, environment, driver) ?: continue
}
return null
@ -93,12 +93,12 @@ abstract class ZigDebugRunnerBase<ProfileState : ZigProfileState<*>> : ZigProgra
}
}
}
return@reportProgress runInterruptibleEDT {
return@reportProgress runInterruptibleEDT(ModalityState.any()) {
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 textConsoleBuilder = state.consoleBuilder
val debugProcess = ZigLocalDebugProcess(runParameters, session, textConsoleBuilder)
ProcessTerminatedListener.attach(debugProcess.processHandler, project)
debugProcess.start()

View file

@ -62,7 +62,7 @@ class ZigDebugParametersBuild(
withContext(Dispatchers.IO) {
val commandLine = profileState.getCommandLine(toolchain, true)
if (listener.executeCommandLineWithHook(profileState.environment.project, commandLine))
throw ExecutionException(ZigDebugBundle.message("debug.build.compile.failed.generic"))
return@withContext
val cfg = profileState.configuration
val workingDir = cfg.workingDirectory.path
val exe = profileState.configuration.exePath.path ?: run {

View file

@ -31,6 +31,7 @@ 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.application.ModalityState
import com.intellij.openapi.extensions.PluginId
import com.intellij.openapi.observable.util.whenItemSelected
import com.intellij.openapi.ui.ComboBox
@ -88,7 +89,7 @@ class ZigDebuggerToolchainConfigurableUi : ZigDebuggerUiComponent {
row(ZigDebugBundle.message("settings.debugger.toolchain.debugger.label")) {
comment = cell(debuggerKindComboBox)
.comment("", DEFAULT_COMMENT_WIDTH) {
zigCoroutineScope.launchWithEDT {
zigCoroutineScope.launchWithEDT(ModalityState.any()) {
withModalProgress(ModalTaskOwner.component(debuggerKindComboBox), "Downloading debugger", TaskCancellation.cancellable()) {
downloadDebugger()
}
@ -96,7 +97,7 @@ class ZigDebuggerToolchainConfigurableUi : ZigDebuggerUiComponent {
}
.applyToComponent {
whenItemSelected(null) {
zigCoroutineScope.launchWithEDT {
zigCoroutineScope.launchWithEDT(ModalityState.any()) {
this@ZigDebuggerToolchainConfigurableUi.update()
}
}
@ -111,7 +112,7 @@ class ZigDebuggerToolchainConfigurableUi : ZigDebuggerUiComponent {
cell(useClion)
}
}
zigCoroutineScope.launchWithEDT {
zigCoroutineScope.launchWithEDT(ModalityState.any()) {
update()
}
}

View file

@ -0,0 +1,38 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2025 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution
import com.intellij.execution.filters.TextConsoleBuilderImpl
import com.intellij.execution.ui.ConsoleView
import com.intellij.openapi.project.Project
import com.intellij.terminal.TerminalExecutionConsole
import java.nio.file.Path
class ZigConsoleBuilder(private val project: Project, private val emulateTerminal: Boolean = false): TextConsoleBuilderImpl(project) {
override fun createConsole(): ConsoleView {
return if (emulateTerminal)
TerminalExecutionConsole(project, null)
else
super.createConsole()
}
}

View file

@ -33,11 +33,11 @@ import com.intellij.openapi.options.SettingsEditor
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.ui.TextBrowseFolderListener
import com.intellij.openapi.ui.TextFieldWithBrowseButton
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.ui.components.JBCheckBox
import com.intellij.ui.components.JBTextField
import com.intellij.ui.components.textFieldWithBrowseButton
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.Panel
import com.intellij.ui.dsl.builder.panel
@ -161,12 +161,11 @@ class WorkDirectoryConfigurable(@Transient override val serializedName: String)
}
class WorkDirectoryConfigModule(private val serializedName: String) : PathConfigModule<WorkDirectoryConfigurable>() {
private val field = TextFieldWithBrowseButton(
TextBrowseFolderListener(
private val field = textFieldWithBrowseButton(
null,
ZigBrainsBundle.message("dialog.title.working-directory"),
FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.working-directory"))
),
this
)
).also { Disposer.register(this, it) }
override var stringValue by field::text
@ -201,9 +200,10 @@ class FilePathConfigurable(
}
class FilePathConfigModule(private val serializedName: String, @Nls private val label: String) : PathConfigModule<FilePathConfigurable>() {
private val field = TextFieldWithBrowseButton(
TextBrowseFolderListener(FileChooserDescriptorFactory.createSingleFileDescriptor()),
this
private val field = textFieldWithBrowseButton(
null,
null,
FileChooserDescriptorFactory.createSingleFileDescriptor(),
)
override var stringValue by field::text
@ -274,12 +274,6 @@ open class CheckboxConfigurable(
}
}
class ColoredConfigurable(serializedName: String): CheckboxConfigurable(serializedName, ZigBrainsBundle.message("exec.option.label.colored-terminal"), true) {
override fun clone(): ColoredConfigurable {
return super.clone() as ColoredConfigurable
}
}
class OptimizationConfigurable(
@Transient private val serializedName: String,
var level: OptimizationLevel = OptimizationLevel.Debug,

View file

@ -43,8 +43,6 @@ import org.jetbrains.annotations.Nls
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)
private set
abstract val suggestedName: @ActionText String
@Throws(ExecutionException::class)
@ -73,17 +71,12 @@ abstract class ZigExecConfig<T: ZigExecConfig<T>>(project: Project, factory: Con
return commandLine
}
fun emulateTerminal(): Boolean {
return pty.value
}
override fun clone(): T {
val myClone = super.clone() as ZigExecConfig<*>
myClone.workingDirectory = workingDirectory.clone()
myClone.pty = pty.clone()
@Suppress("UNCHECKED_CAST")
return myClone as T
}
open fun getConfigurables(): List<ZigConfigurable<*>> = listOf(workingDirectory, pty)
open fun getConfigurables(): List<ZigConfigurable<*>> = listOf(workingDirectory)
}

View file

@ -23,9 +23,11 @@
package com.falsepattern.zigbrains.project.execution.base
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.execution.ZigConsoleBuilder
import com.falsepattern.zigbrains.project.run.ZigProcessHandler
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
import com.falsepattern.zigbrains.shared.cli.startIPCAwareProcess
import com.falsepattern.zigbrains.shared.coroutine.runModalOrBlocking
import com.falsepattern.zigbrains.shared.ipc.IPCUtil
import com.falsepattern.zigbrains.shared.ipc.ipc
@ -35,11 +37,15 @@ import com.intellij.execution.ExecutionException
import com.intellij.execution.configurations.CommandLineState
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.configurations.PtyCommandLine
import com.intellij.execution.filters.TextConsoleBuilder
import com.intellij.execution.process.ProcessHandler
import com.intellij.execution.process.ProcessTerminatedListener
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.openapi.project.Project
import com.intellij.platform.ide.progress.ModalTaskOwner
import com.intellij.terminal.TerminalExecutionConsole
import com.intellij.util.system.OS
import kotlin.collections.contains
import kotlin.io.path.pathString
abstract class ZigProfileState<T: ZigExecConfig<T>> (
@ -47,6 +53,10 @@ abstract class ZigProfileState<T: ZigExecConfig<T>> (
val configuration: T
): CommandLineState(environment) {
init {
consoleBuilder = ZigConsoleBuilder(environment.project, true)
}
@Throws(ExecutionException::class)
override fun startProcess(): ProcessHandler {
return runModalOrBlocking({ModalTaskOwner.project(environment.project)}, {"ZigProfileState.startProcess"}) {
@ -57,7 +67,7 @@ abstract class ZigProfileState<T: ZigExecConfig<T>> (
@Throws(ExecutionException::class)
suspend fun startProcessSuspend(): ProcessHandler {
val toolchain = environment.project.zigProjectSettings.state.toolchain ?: throw ExecutionException(ZigBrainsBundle.message("exception.zig-profile-state.start-process.no-toolchain"))
return startProcess(getCommandLine(toolchain, false), environment.project)
return getCommandLine(toolchain, false).startIPCAwareProcess(environment.project, emulateTerminal = true)
}
@Throws(ExecutionException::class)
@ -65,9 +75,7 @@ abstract class ZigProfileState<T: ZigExecConfig<T>> (
val workingDir = configuration.workingDirectory
val zigExePath = toolchain.zig.path()
// TODO remove this check once JetBrains implements colored terminal in the debugger
// https://youtrack.jetbrains.com/issue/CPP-11622/ANSI-color-codes-not-honored-in-Debug-Run-Configuration-output-window
val cli = if (configuration.emulateTerminal() && !debug) PtyCommandLine().withConsoleMode(true).withParentEnvironmentType(GeneralCommandLine.ParentEnvironmentType.CONSOLE) else GeneralCommandLine()
val cli = PtyCommandLine().withConsoleMode(false)
cli.withExePath(zigExePath.pathString)
workingDir.path?.let { cli.withWorkDirectory(it.toFile()) }
cli.withCharset(Charsets.UTF_8)
@ -75,22 +83,3 @@ abstract class ZigProfileState<T: ZigExecConfig<T>> (
return configuration.patchCommandLine(cli)
}
}
@Throws(ExecutionException::class)
suspend fun executeCommandLine(commandLine: GeneralCommandLine, environment: ExecutionEnvironment): DefaultExecutionResult {
val handler = startProcess(commandLine, environment.project)
val console = BuildTextConsoleView(environment.project, false, emptyList())
console.attachToProcess(handler)
return DefaultExecutionResult(console, handler)
}
@Throws(ExecutionException::class)
suspend fun startProcess(commandLine: GeneralCommandLine, project: Project): ProcessHandler {
val ipc = IPCUtil.wrapWithIPC(commandLine)
val handler = ZigProcessHandler(ipc?.cli ?: commandLine)
ProcessTerminatedListener.attach(handler)
if (ipc != null) {
project.ipc?.launchWatcher(ipc, handler.process)
}
return handler
}

View file

@ -25,7 +25,6 @@ package com.falsepattern.zigbrains.project.execution.build
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.execution.base.*
import com.falsepattern.zigbrains.shared.ZBFeatures
import com.falsepattern.zigbrains.shared.cli.coloredCliFlags
import com.intellij.execution.ExecutionException
import com.intellij.execution.Executor
import com.intellij.execution.configurations.ConfigurationFactory
@ -37,8 +36,6 @@ class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigEx
private set
var extraArgs = ArgsConfigurable("compilerArgs", ZigBrainsBundle.message("exec.option.label.build.args"))
private set
var colored = ColoredConfigurable("colored")
private set
var debugBuildSteps = ArgsConfigurable("debugBuildSteps", ZigBrainsBundle.message("exec.option.label.build.steps-debug"))
private set
var debugExtraArgs = ArgsConfigurable("debugCompilerArgs", ZigBrainsBundle.message("exec.option.label.build.args-debug"))
@ -54,7 +51,6 @@ class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigEx
result.add("build")
val steps = if (debug) debugBuildSteps.argsSplit() else buildSteps.argsSplit()
result.addAll(steps)
result.addAll(coloredCliFlags(colored.value, debug))
result.addAll(if (debug) debugExtraArgs.argsSplit() else extraArgs.argsSplit())
return result
}
@ -66,14 +62,13 @@ class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigEx
val clone = super.clone()
clone.buildSteps = buildSteps.clone()
clone.exeArgs = exeArgs.clone()
clone.colored = colored.clone()
clone.exePath = exePath.clone()
clone.exeArgs = exeArgs.clone()
return clone
}
override fun getConfigurables(): List<ZigConfigurable<*>> {
val baseCfg = super.getConfigurables() + listOf(buildSteps, extraArgs, colored)
val baseCfg = super.getConfigurables() + listOf(buildSteps, extraArgs)
return if (ZBFeatures.debug()) {
baseCfg + listOf(debugBuildSteps, debugExtraArgs, exePath, exeArgs)
} else {

View file

@ -24,7 +24,6 @@ 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
@ -35,8 +34,6 @@ import kotlin.io.path.pathString
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")
private set
var optimization = OptimizationConfigurable("optimization")
private set
var compilerArgs = ArgsConfigurable("compilerArgs", ZigBrainsBundle.message("exec.option.label.compiler-args"))
@ -47,7 +44,6 @@ class ZigExecConfigRun(project: Project, factory: ConfigurationFactory): ZigExec
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 ExecutionException(ZigBrainsBundle.message("exception.zig.empty-file-path")))
if (!debug || optimization.forced) {
result.addAll(listOf("-O", optimization.level.name))
@ -66,7 +62,6 @@ class ZigExecConfigRun(project: Project, factory: ConfigurationFactory): ZigExec
override fun clone(): ZigExecConfigRun {
val clone = super.clone()
clone.filePath = filePath.clone()
clone.colored = colored.clone()
clone.compilerArgs = compilerArgs.clone()
clone.optimization = optimization.clone()
clone.exeArgs = exeArgs.clone()
@ -74,7 +69,7 @@ class ZigExecConfigRun(project: Project, factory: ConfigurationFactory): ZigExec
}
override fun getConfigurables(): List<ZigConfigurable<*>> {
return super.getConfigurables() + listOf(filePath, optimization, colored, compilerArgs, exeArgs)
return super.getConfigurables() + listOf(filePath, optimization, compilerArgs, exeArgs)
}
override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState<ZigExecConfigRun> {

View file

@ -24,7 +24,6 @@ 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
@ -35,8 +34,6 @@ import kotlin.io.path.pathString
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")
private set
var optimization = OptimizationConfigurable("optimization")
private set
var compilerArgs = ArgsConfigurable("compilerArgs", ZigBrainsBundle.message("exec.option.label.compiler-args"))
@ -46,7 +43,6 @@ class ZigExecConfigTest(project: Project, factory: ConfigurationFactory): ZigExe
override suspend fun buildCommandLineArgs(debug: Boolean): List<String> {
val result = ArrayList<String>()
result.add("test")
result.addAll(coloredCliFlags(colored.value, debug))
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))
@ -64,14 +60,13 @@ class ZigExecConfigTest(project: Project, factory: ConfigurationFactory): ZigExe
override fun clone(): ZigExecConfigTest {
val clone = super.clone()
clone.filePath = filePath.clone()
clone.colored = colored.clone()
clone.compilerArgs = compilerArgs.clone()
clone.optimization = optimization.clone()
return clone
}
override fun getConfigurables(): List<ZigConfigurable<*>> {
return super.getConfigurables() + listOf(filePath, optimization, colored, compilerArgs)
return super.getConfigurables() + listOf(filePath, optimization, compilerArgs)
}
override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState<ZigExecConfigTest> {

View file

@ -29,6 +29,7 @@ import com.intellij.ide.util.projectWizard.ModuleBuilder
import com.intellij.ide.util.projectWizard.ModuleWizardStep
import com.intellij.ide.util.projectWizard.WizardContext
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.module.ModuleType
import com.intellij.openapi.roots.ModifiableRootModel
import com.intellij.openapi.util.Disposer
@ -58,7 +59,7 @@ class ZigModuleBuilder: ModuleBuilder() {
val root = contentEntry.file ?: return
val config = configurationData ?: return
config.generateProject(this, rootModel.project, root, forceGitignore)
withEDTContext {
withEDTContext(ModalityState.defaultModalityState()) {
root.refresh(false, true)
}
}

View file

@ -119,10 +119,10 @@ data class ZigProjectConfigurationData(
if (git) {
project.zigCoroutineScope.launch {
GitRepositoryInitializer.getInstance()?.initRepository(project, baseDir)
createGitIgnoreFile(project, baseDir, requestor)
createGitIgnoreFile(baseDir, requestor)
}
} else if (forceGitignore) {
createGitIgnoreFile(project, baseDir, requestor)
createGitIgnoreFile(baseDir, requestor)
}
return@reportProgress true
@ -131,7 +131,7 @@ data class ZigProjectConfigurationData(
}
private suspend fun createGitIgnoreFile(project: Project, projectDir: VirtualFile, requestor: Any) {
private suspend fun createGitIgnoreFile(projectDir: VirtualFile, requestor: Any) {
if (projectDir.findChild(".gitignore") != null) {
return
}

View file

@ -26,11 +26,12 @@ import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.configurations.PtyCommandLine
import com.intellij.execution.process.AnsiEscapeDecoder.ColoredTextAcceptor
import com.intellij.execution.process.KillableColoredProcessHandler
import com.intellij.execution.process.KillableProcessHandler
import com.intellij.openapi.util.Key
import com.pty4j.PtyProcess
import java.nio.charset.Charset
class ZigProcessHandler : KillableColoredProcessHandler, ColoredTextAcceptor {
class ZigProcessHandler : KillableProcessHandler {
constructor(commandLine: GeneralCommandLine) : super(commandLine) {
setHasPty(commandLine is PtyCommandLine)
setShouldDestroyProcessRecursively(!hasPty())
@ -40,57 +41,4 @@ class ZigProcessHandler : KillableColoredProcessHandler, ColoredTextAcceptor {
setHasPty(process is PtyProcess)
setShouldDestroyProcessRecursively(!hasPty())
}
override fun coloredTextAvailable(text: String, attributes: Key<*>) {
super.coloredTextAvailable(text.translateVT100Escapes(), attributes)
}
}
private val VT100_CHARS = CharArray(256).apply {
this.fill(' ')
this[0x6A] = '┘'
this[0x6B] = '┐'
this[0x6C] = '┌'
this[0x6D] = '└'
this[0x6E] = '┼'
this[0x71] = '─'
this[0x74] = '├'
this[0x75] = '┤'
this[0x76] = '┴'
this[0x77] = '┬'
this[0x78] = '│'
}
private const val VT100_BEGIN_SEQ = "\u001B(0"
private const val VT100_END_SEQ = "\u001B(B"
private const val VT100_BEGIN_SEQ_LENGTH: Int = VT100_BEGIN_SEQ.length
private const val VT100_END_SEQ_LENGTH: Int = VT100_END_SEQ.length
private fun String.translateVT100Escapes(): String {
var offset = 0
val result = StringBuilder()
val textLength = length
while (offset < textLength) {
val startIndex = indexOf(VT100_BEGIN_SEQ, offset)
if (startIndex < 0) {
result.append(substring(offset, textLength).replace(VT100_END_SEQ, ""))
break
}
result.append(this, offset, startIndex)
val blockOffset = startIndex + VT100_BEGIN_SEQ_LENGTH
var endIndex = indexOf(VT100_END_SEQ, blockOffset)
if (endIndex < 0) {
endIndex = textLength
}
for (i in blockOffset until endIndex) {
val c = this[i].code
if (c >= 256) {
result.append(c)
} else {
result.append(VT100_CHARS[c])
}
}
offset = endIndex + VT100_END_SEQ_LENGTH
}
return result.toString()
}

View file

@ -24,7 +24,6 @@ package com.falsepattern.zigbrains.project.run
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.coroutine.withEDTContext
import com.intellij.execution.configurations.RunProfile
@ -36,8 +35,7 @@ import com.intellij.openapi.application.ModalityState
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)
val exec = state.execute(environment.executor, this)
return withEDTContext(ModalityState.any()) {
val runContentBuilder = RunContentBuilder(exec, environment)
runContentBuilder.showRunContent(null)

View file

@ -97,7 +97,7 @@ class ZigProjectSettingsPanel(private val holder: ZigProjectConfigurationProvide
}
private fun dispatchAutodetect(force: Boolean) {
project.zigCoroutineScope.launchWithEDT {
project.zigCoroutineScope.launchWithEDT(ModalityState.any()) {
withModalProgress(ModalTaskOwner.component(pathToToolchain), "Detecting Zig...", TaskCancellation.cancellable()) {
autodetect(force)
}

View file

@ -27,6 +27,7 @@ import com.falsepattern.zigbrains.project.steps.discovery.ZigStepDiscoveryListen
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.Logger
@ -90,7 +91,7 @@ class ZigStepDiscoveryService(private val project: Project) {
null
}
if (result == null) {
{}
} else if (result.checkSuccess(LOG)) {
currentTimeoutSec = DEFAULT_TIMEOUT_SEC
val lines = result.stdoutLines
@ -124,7 +125,7 @@ class ZigStepDiscoveryService(private val project: Project) {
}
private suspend fun dispatchReload() {
withEDTContext {
withEDTContext(ModalityState.defaultModalityState()) {
FileDocumentManager.getInstance().saveAllDocuments()
}
doReload()

View file

@ -81,18 +81,18 @@ class BuildToolWindowContext(private val project: Project): Disposable {
val tree = Tree(model).also { it.isRootVisible = false }
}
private val viewPanel = JPanel(VerticalLayout(0))
private val steps = TreeBox()
private val build = if (IPCUtil.haveIPC) TreeBox() else null
private val stepsBox = TreeBox()
private val buildBox = if (IPCUtil.haveIPC) TreeBox() else null
private var live = AtomicBoolean(true)
init {
viewPanel.add(JBLabel(ZigBrainsBundle.message("build.tool.window.tree.steps.label")))
viewPanel.add(steps.panel)
steps.panel.setNotScanned()
viewPanel.add(stepsBox.panel)
stepsBox.panel.setNotScanned()
steps.tree.addMouseListener(object : MouseAdapter() {
stepsBox.tree.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
if (e.clickCount == 2) {
val node = steps.tree.lastSelectedPathComponent as? DefaultMutableTreeNode ?: return
val node = stepsBox.tree.lastSelectedPathComponent as? DefaultMutableTreeNode ?: return
val step = node.userObject as? StepNodeDescriptor ?: return
val stepName = step.element?.name ?: return
@ -112,10 +112,10 @@ class BuildToolWindowContext(private val project: Project): Disposable {
}
})
if (build != null) {
if (buildBox != null) {
viewPanel.add(JBLabel(ZigBrainsBundle.message("build.tool.window.tree.build.label")))
viewPanel.add(build.panel)
build.panel.setNoBuilds()
viewPanel.add(buildBox.panel)
buildBox.panel.setNoBuilds()
project.zigCoroutineScope.launch {
while (!project.isDisposed && live.get()) {
@ -126,14 +126,14 @@ class BuildToolWindowContext(private val project: Project): Disposable {
ipc.mutex.withLock {
withEDTContext(ModalityState.any()) {
if (ipc.nodes.isEmpty()) {
build.root.removeAllChildren()
build.panel.setNoBuilds()
buildBox.root.removeAllChildren()
buildBox.panel.setNoBuilds()
return@withEDTContext
}
val allNodes = ArrayList(ipc.nodes)
val existingNodes = ArrayList<ZigIPCService.IPCTreeNode>()
val removedNodes = ArrayList<ZigIPCService.IPCTreeNode>()
build.root.children().iterator().forEach { child ->
buildBox.root.children().iterator().forEach { child ->
if (child !is ZigIPCService.IPCTreeNode) {
return@forEach
}
@ -145,18 +145,18 @@ class BuildToolWindowContext(private val project: Project): Disposable {
}
val newNodes = ArrayList<MutableTreeNode>(allNodes)
newNodes.removeAll(existingNodes)
removedNodes.forEach { build.root.remove(it) }
newNodes.forEach { build.root.add(it) }
removedNodes.forEach { buildBox.root.remove(it) }
newNodes.forEach { buildBox.root.add(it) }
if (removedNodes.isNotEmpty() || newNodes.isNotEmpty()) {
build.model.reload(build.root)
buildBox.model.reload(buildBox.root)
}
if (build.root.childCount == 0) {
build.panel.setNoBuilds()
if (buildBox.root.childCount == 0) {
buildBox.panel.setNoBuilds()
} else {
build.panel.setViewportBody(build.tree)
buildBox.panel.setViewportBody(buildBox.tree)
}
for (bn in allNodes) {
expandRecursively(build, bn)
expandRecursively(buildBox, bn)
}
}
}
@ -220,28 +220,28 @@ class BuildToolWindowContext(private val project: Project): Disposable {
inner class BuildReloadListener: ZigStepDiscoveryListener {
override suspend fun preReload() {
steps.panel.setRunningZigBuild()
stepsBox.panel.setRunningZigBuild()
}
override suspend fun postReload(stepInfo: List<Pair<String, String?>>) {
steps.root.removeAllChildren()
for ((task, description) in stepInfo) {
override suspend fun postReload(steps: List<Pair<String, String?>>) {
stepsBox.root.removeAllChildren()
for ((task, description) in steps) {
val icon = when(task) {
"install" -> AllIcons.Actions.Install
"uninstall" -> AllIcons.Actions.Uninstall
else -> AllIcons.RunConfigurations.TestState.Run
}
steps.root.add(DefaultMutableTreeNode(StepNodeDescriptor(project, task, icon, description)))
stepsBox.root.add(DefaultMutableTreeNode(StepNodeDescriptor(project, task, icon, description)))
}
withEDTContext(ModalityState.any()) {
steps.model.reload(steps.root)
steps.panel.setViewportBody(steps.tree)
stepsBox.model.reload(stepsBox.root)
stepsBox.panel.setViewportBody(stepsBox.tree)
}
}
override suspend fun errorReload(type: ZigStepDiscoveryListener.ErrorType, details: String?) {
withEDTContext(ModalityState.any()) {
steps.panel.setViewportError(ZigBrainsBundle.message(when(type) {
stepsBox.panel.setViewportError(ZigBrainsBundle.message(when(type) {
ZigStepDiscoveryListener.ErrorType.MissingToolchain -> "build.tool.window.status.error.missing-toolchain"
ZigStepDiscoveryListener.ErrorType.MissingZigExe -> "build.tool.window.status.error.missing-zig-exe"
ZigStepDiscoveryListener.ErrorType.MissingBuildZig -> "build.tool.window.status.error.missing-build-zig"
@ -252,7 +252,7 @@ class BuildToolWindowContext(private val project: Project): Disposable {
override suspend fun timeoutReload(seconds: Int) {
withEDTContext(ModalityState.any()) {
steps.panel.setViewportError(ZigBrainsBundle.message("build.tool.window.status.timeout", seconds), null)
stepsBox.panel.setViewportError(ZigBrainsBundle.message("build.tool.window.status.timeout", seconds), null)
}
}
}

View file

@ -23,13 +23,18 @@
package com.falsepattern.zigbrains.shared.cli
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.run.ZigProcessHandler
import com.falsepattern.zigbrains.shared.ipc.IPCUtil
import com.falsepattern.zigbrains.shared.ipc.ipc
import com.intellij.execution.ExecutionException
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.process.ProcessHandler
import com.intellij.execution.process.ProcessOutput
import com.intellij.execution.process.ProcessTerminatedListener
import com.intellij.openapi.options.ConfigurationException
import com.intellij.openapi.project.Project
import com.intellij.util.io.awaitExit
import com.intellij.util.system.OS
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withContext
@ -108,14 +113,6 @@ fun translateCommandline(toProcess: String): List<String> {
return result
}
fun coloredCliFlags(colored: Boolean, debug: Boolean): List<String> {
return if (debug) {
emptyList()
} else {
listOf("--color", if (colored) "on" else "off")
}
}
fun createCommandLineSafe(
workingDirectory: Path?,
exe: Path,
@ -133,14 +130,27 @@ fun createCommandLineSafe(
return Result.success(cli)
}
suspend fun GeneralCommandLine.call(timeoutMillis: Long = Long.MAX_VALUE, ipcProject: Project? = null): Result<ProcessOutput> {
val ipc = if (ipcProject != null) IPCUtil.wrapWithIPC(this) else null
@Throws(ExecutionException::class)
suspend fun GeneralCommandLine.startIPCAwareProcess(project: Project?, emulateTerminal: Boolean = false): ZigProcessHandler {
val ipc = if (project != null && !emulateTerminal) IPCUtil.wrapWithIPC(this) else null
val cli = ipc?.cli ?: this
val (process, exitCode) = withContext(Dispatchers.IO) {
val process = cli.createProcess()
if (ipc != null) {
ipcProject!!.ipc?.launchWatcher(ipc, process)
if (emulateTerminal && OS.CURRENT != OS.Windows && !cli.environment.contains("TERM")) {
cli.withEnvironment("TERM", "xterm-256color")
}
val handler = ZigProcessHandler(cli)
ProcessTerminatedListener.attach(handler)
if (ipc != null) {
project!!.ipc?.launchWatcher(ipc, handler.process)
}
return handler
}
suspend fun GeneralCommandLine.call(timeoutMillis: Long = Long.MAX_VALUE, ipcProject: Project? = null): Result<ProcessOutput> {
val (process, exitCode) = withContext(Dispatchers.IO) {
val handler = startIPCAwareProcess(ipcProject)
val process = handler.process
val exit = withTimeoutOrNull(timeoutMillis) {
process.awaitExit()
}

View file

@ -39,7 +39,7 @@ inline fun <T> runModalOrBlocking(taskOwnerFactory: () -> ModalTaskOwner, titleF
}
}
suspend inline fun <T> withEDTContext(state: ModalityState = ModalityState.defaultModalityState(), noinline block: suspend CoroutineScope.() -> T): T {
suspend inline fun <T> withEDTContext(state: ModalityState, noinline block: suspend CoroutineScope.() -> T): T {
return withContext(Dispatchers.EDT + state.asContextElement(), block = block)
}
@ -49,10 +49,10 @@ suspend inline fun <T> withCurrentEDTModalityContext(noinline block: suspend Cor
}
}
suspend inline fun <T> runInterruptibleEDT(state: ModalityState = ModalityState.defaultModalityState(), noinline targetAction: () -> T): T {
suspend inline fun <T> runInterruptibleEDT(state: ModalityState, noinline targetAction: () -> T): T {
return runInterruptible(Dispatchers.EDT + state.asContextElement(), block = targetAction)
}
fun CoroutineScope.launchWithEDT(state: ModalityState = ModalityState.defaultModalityState(), block: suspend CoroutineScope.() -> Unit): Job {
fun CoroutineScope.launchWithEDT(state: ModalityState, block: suspend CoroutineScope.() -> Unit): Job {
return launch(Dispatchers.EDT + state.asContextElement(), block = block)
}

View file

@ -74,7 +74,7 @@ object IPCUtil {
val cli = GeneralCommandLine(it)
val tmpFile = FileUtil.createTempFile("zigbrains-bash-detection", null, true).toPath()
try {
cli.addParameters("-c", "exec {var}>${tmpFile.pathString}; echo foo >&\$var; exec {var}>&-")
cli.addParameters("-c", "exec {var}>${tmpFile.pathString}; echo foo >&\$var; ZB_EXIT=\$?; exec {var}>&-; exit \$ZB_EXIT")
val process = cli.createProcess()
val exitCode = process.awaitExit()
if (exitCode != 0) {
@ -100,7 +100,7 @@ object IPCUtil {
val (fifoFile, fifo) = info!!.mkfifo.createTemp() ?: return null
//FIFO created, hack cli
val exePath = cli.exePath
val args = "exec {var}>${fifoFile.pathString}; ZIG_PROGRESS=\$var $exePath ${cli.parametersList.parametersString}; exec {var}>&-"
val args = "exec {var}>${fifoFile.pathString}; ZIG_PROGRESS=\$var $exePath ${cli.parametersList.parametersString}; ZB_EXIT=\$?; exec {var}>&-; exit \$ZB_EXIT"
cli.withExePath(info!!.bash)
cli.parametersList.clearAll()
cli.addParameters("-c", args)

View file

@ -55,18 +55,16 @@ class ZonBlock(
override fun getChildIndent(): Indent {
val node = this.node
return getIndentBasedOnParentType(node, null, node.elementType, PLACEHOLDER)
return getIndentBasedOnParentType(node.elementType, PLACEHOLDER)
}
override fun getIndent(): Indent {
val node = this.node
val parent = node.treeParent ?: return noneIndent
return getIndentBasedOnParentType(parent, node, parent.elementType, node.elementType)
return getIndentBasedOnParentType(parent.elementType, node.elementType)
}
}
private fun getIndentBasedOnParentType(
parent: ASTNode,
child: ASTNode?,
parentType: IElementType,
childType: IElementType
): Indent {

View file

@ -65,11 +65,9 @@ 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
exec.option.label.optimization=Optimization level
exec.option.label.optimization.force=Force even in debug runs
exec.option.label.emulate-terminal=Emulate terminal
exec.option.label.file-path=File Path
exec.option.label.compiler-args=Extra compiler command line arguments
exec.option.label.exe-args=Output program command line arguments

View file

@ -1,7 +1,7 @@
pluginName=ZigBrains
pluginRepositoryUrl=https://github.com/FalsePattern/ZigBrains
pluginVersion=23.1.2
pluginVersion=24.0.0
pluginSinceBuild=241
pluginUntilBuild=241.*

View file

@ -279,7 +279,7 @@ class ZLSSettingsPanel(private val project: Project) : ZigProjectConfigurationPr
}
private fun dispatchAutodetect(force: Boolean) {
project.zigCoroutineScope.launchWithEDT {
project.zigCoroutineScope.launchWithEDT(ModalityState.any()) {
withModalProgress(ModalTaskOwner.component(zlsPath), "Detecting ZLS...", TaskCancellation.cancellable()) {
autodetect(force)
}

View file

@ -6,7 +6,6 @@ settings.zls-version.label=Detected ZLS version
settings.zls-config-path.label=Config path
settings.zls-config-path.tooltip=Leave empty to use built-in config generated from the settings below
settings.zls-config-path.browse.title=Path to the Custom ZLS Config File (Optional)
settings.zls-path.use-direnv.label=Use direnv
settings.inlay-hints-group.label=Inlay Hints
settings.inlay-hints-enable.label=Enable
settings.inlay-hints-enable.tooltip=Toggle this to enable/disable all inlay hints
@ -50,7 +49,6 @@ settings.build_runner_path.label=Build runner path
settings.build_runner_path.tooltip=Specify a custom build runner to resolve build system information.
settings.global_cache_path.label=Global cache path
settings.global_cache_path.tooltip=Path to a directory that will be used as zig's cache. Will default to `${KnownFolders.Cache}/zls`.
configurable.name.zls.settings=ZLS Settings
notification.group.zigbrains-lsp=ZigBrains LSP Integration
notification.message.could-not-detect.content=Could not detect ZLS binary, please configure it
notification.message.zls-exe-path-invalid.content=ZLS executable path could not be parsed: {0}