From 0a5a765eafb191b736d3607f4a9b52a4a69f92eb Mon Sep 17 00:00:00 2001 From: FalsePattern Date: Thu, 27 Mar 2025 20:49:50 +0100 Subject: [PATCH] feat!: TTY support for zig processes --- CHANGELOG.md | 10 ++++ ...lionDebuggerDriverConfigurationProvider.kt | 4 +- .../debugger/ZigLocalDebugProcess.kt | 3 +- .../runner/base/PreLaunchProcessListener.kt | 12 +--- .../base/ZigDebugEmitBinaryInstaller.kt | 3 +- .../runner/base/ZigDebugRunnerBase.kt | 4 +- .../project/execution/ZigConsoleBuilder.kt | 38 ++++++++++++ .../project/execution/base/Configuration.kt | 6 -- .../project/execution/base/ZigExecConfig.kt | 9 +-- .../project/execution/base/ZigProfileState.kt | 37 +++++------- .../execution/build/ZigExecConfigBuild.kt | 7 +-- .../project/execution/run/ZigExecConfigRun.kt | 7 +-- .../execution/test/ZigExecConfigTest.kt | 7 +-- .../project/run/ZigProcessHandler.kt | 58 +------------------ .../zigbrains/project/run/ZigRegularRunner.kt | 3 +- .../zigbrains/shared/cli/CLIUtil.kt | 38 +++++++----- 16 files changed, 103 insertions(+), 143 deletions(-) create mode 100644 core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/ZigConsoleBuilder.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index 75409d2e..2831abb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,16 @@ Changelog structure reference: ## [Unreleased] +### 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 - Zig diff --git a/cidr/src/main/kotlin/com/falsepattern/zigbrains/clion/ZigClionDebuggerDriverConfigurationProvider.kt b/cidr/src/main/kotlin/com/falsepattern/zigbrains/clion/ZigClionDebuggerDriverConfigurationProvider.kt index 40e6563c..cf4ae4aa 100644 --- a/cidr/src/main/kotlin/com/falsepattern/zigbrains/clion/ZigClionDebuggerDriverConfigurationProvider.kt +++ b/cidr/src/main/kotlin/com/falsepattern/zigbrains/clion/ZigClionDebuggerDriverConfigurationProvider.kt @@ -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) } } } diff --git a/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigLocalDebugProcess.kt b/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigLocalDebugProcess.kt index fe9fcc31..cae89406 100644 --- a/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigLocalDebugProcess.kt +++ b/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/ZigLocalDebugProcess.kt @@ -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) \ No newline at end of file +class ZigLocalDebugProcess(parameters: RunParameters, session: XDebugSession, consoleBuilder: TextConsoleBuilder) : CidrLocalDebugProcess(parameters, session, consoleBuilder, { Filter.EMPTY_ARRAY }, true) \ No newline at end of file diff --git a/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/PreLaunchProcessListener.kt b/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/PreLaunchProcessListener.kt index 953e91e7..92b64c96 100644 --- a/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/PreLaunchProcessListener.kt +++ b/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/PreLaunchProcessListener.kt @@ -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 diff --git a/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugEmitBinaryInstaller.kt b/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugEmitBinaryInstaller.kt index e3169333..1f34ca3c 100644 --- a/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugEmitBinaryInstaller.kt +++ b/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugEmitBinaryInstaller.kt @@ -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>( ): 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.withWorkingDirectory(x) } cli.addParameters(exeArgs) cli.withCharset(Charsets.UTF_8) diff --git a/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugRunnerBase.kt b/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugRunnerBase.kt index 4bb92371..ccb53975 100644 --- a/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugRunnerBase.kt +++ b/cidr/src/main/kotlin/com/falsepattern/zigbrains/debugger/runner/base/ZigDebugRunnerBase.kt @@ -58,7 +58,7 @@ abstract class ZigDebugRunnerBase> : 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 @@ -98,7 +98,7 @@ abstract class ZigDebugRunnerBase> : ZigProgra 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() diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/ZigConsoleBuilder.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/ZigConsoleBuilder.kt new file mode 100644 index 00000000..e46a415f --- /dev/null +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/ZigConsoleBuilder.kt @@ -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 . + */ + +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() + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/Configuration.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/Configuration.kt index c7bb73e4..0c2ee17b 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/Configuration.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/Configuration.kt @@ -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, diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigExecConfig.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigExecConfig.kt index b03b16ad..ee148595 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigExecConfig.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigExecConfig.kt @@ -43,8 +43,6 @@ import org.jetbrains.annotations.Nls abstract class ZigExecConfig>(project: Project, factory: ConfigurationFactory, @Nls name: String): LocatableConfigurationBase>(project, factory, name) { var workingDirectory = WorkDirectoryConfigurable("workingDirectory").apply { path = project.guessProjectDir()?.toNioPathOrNull() } private set - var pty = CheckboxConfigurable("pty", ZigBrainsBundle.message("exec.option.label.emulate-terminal"), false) - private set abstract val suggestedName: @ActionText String @Throws(ExecutionException::class) @@ -73,17 +71,12 @@ abstract class ZigExecConfig>(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> = listOf(workingDirectory, pty) + open fun getConfigurables(): List> = listOf(workingDirectory) } \ No newline at end of file diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigProfileState.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigProfileState.kt index 0db42868..251ca441 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigProfileState.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/base/ZigProfileState.kt @@ -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> ( @@ -47,6 +53,10 @@ abstract class ZigProfileState> ( 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> ( @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,32 +75,11 @@ abstract class ZigProfileState> ( 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.withWorkingDirectory(it) } cli.withCharset(Charsets.UTF_8) cli.addParameters(configuration.buildCommandLineArgs(debug)) 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 -} +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigExecConfigBuild.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigExecConfigBuild.kt index cf30dfce..75104231 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigExecConfigBuild.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/build/ZigExecConfigBuild.kt @@ -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> { - 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 { diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigExecConfigRun.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigExecConfigRun.kt index b399105d..0a863dad 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigExecConfigRun.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/run/ZigExecConfigRun.kt @@ -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(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 { val result = ArrayList() 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> { - 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 { diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigExecConfigTest.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigExecConfigTest.kt index 4f37fee3..1ffd6370 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigExecConfigTest.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/execution/test/ZigExecConfigTest.kt @@ -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(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 { val result = ArrayList() 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> { - return super.getConfigurables() + listOf(filePath, optimization, colored, compilerArgs) + return super.getConfigurables() + listOf(filePath, optimization, compilerArgs) } override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState { diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigProcessHandler.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigProcessHandler.kt index 125e595e..8753be03 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigProcessHandler.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigProcessHandler.kt @@ -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() -} +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigRegularRunner.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigRegularRunner.kt index 7dce5c9c..166dc0e2 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigRegularRunner.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/project/run/ZigRegularRunner.kt @@ -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 @@ -37,7 +36,7 @@ import com.intellij.openapi.application.ModalityState class ZigRegularRunner: ZigProgramRunner>(DefaultRunExecutor.EXECUTOR_ID) { override suspend fun execute(state: ZigProfileState<*>, toolchain: AbstractZigToolchain, environment: ExecutionEnvironment): RunContentDescriptor? { val cli = state.getCommandLine(toolchain, false) - val exec = executeCommandLine(cli, environment) + val exec = state.execute(environment.executor, this) return withEDTContext(ModalityState.any()) { val runContentBuilder = RunContentBuilder(exec, environment) runContentBuilder.showRunContent(null) diff --git a/core/src/main/kotlin/com/falsepattern/zigbrains/shared/cli/CLIUtil.kt b/core/src/main/kotlin/com/falsepattern/zigbrains/shared/cli/CLIUtil.kt index 84201c37..e1c48c16 100644 --- a/core/src/main/kotlin/com/falsepattern/zigbrains/shared/cli/CLIUtil.kt +++ b/core/src/main/kotlin/com/falsepattern/zigbrains/shared/cli/CLIUtil.kt @@ -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 { return result } -fun coloredCliFlags(colored: Boolean, debug: Boolean): List { - 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 { - 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 + 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 { val (process, exitCode) = withContext(Dispatchers.IO) { - val process = cli.createProcess() - if (ipc != null) { - ipcProject!!.ipc?.launchWatcher(ipc, process) - } + val handler = startIPCAwareProcess(ipcProject) + val process = handler.process val exit = withTimeoutOrNull(timeoutMillis) { process.awaitExit() }