backport: 23.0.0
This commit is contained in:
parent
eeef01498c
commit
ae7657ab7c
21 changed files with 485 additions and 87 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -17,6 +17,18 @@ Changelog structure reference:
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [23.0.0]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Project
|
||||||
|
- Zig std.Progress visualization in the zig tool window (Linux/macOS only)
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Project
|
||||||
|
- Executable / Library new project templates temporarily removed until zig stabilizes
|
||||||
|
|
||||||
## [22.0.1]
|
## [22.0.1]
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -38,6 +38,7 @@ and might as well utilize the full semver string for extra information.
|
||||||
## Supporters
|
## Supporters
|
||||||
|
|
||||||
- ### [Techatrix](https://github.com/Techatrix)
|
- ### [Techatrix](https://github.com/Techatrix)
|
||||||
|
- ### [nuxusr](https://github.com/nuxusr)
|
||||||
- gree7
|
- gree7
|
||||||
- xceno
|
- xceno
|
||||||
- AnErrupTion
|
- AnErrupTion
|
||||||
|
|
|
@ -23,6 +23,8 @@
|
||||||
package com.falsepattern.zigbrains.debugger.runner.base
|
package com.falsepattern.zigbrains.debugger.runner.base
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.project.run.ZigProcessHandler
|
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.ExecutionException
|
||||||
import com.intellij.execution.configurations.GeneralCommandLine
|
import com.intellij.execution.configurations.GeneralCommandLine
|
||||||
import com.intellij.execution.process.ProcessEvent
|
import com.intellij.execution.process.ProcessEvent
|
||||||
|
@ -30,6 +32,7 @@ import com.intellij.execution.process.ProcessHandler
|
||||||
import com.intellij.execution.process.ProcessListener
|
import com.intellij.execution.process.ProcessListener
|
||||||
import com.intellij.execution.ui.ConsoleView
|
import com.intellij.execution.ui.ConsoleView
|
||||||
import com.intellij.execution.ui.ConsoleViewContentType
|
import com.intellij.execution.ui.ConsoleViewContentType
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.platform.util.progress.withProgressText
|
import com.intellij.platform.util.progress.withProgressText
|
||||||
import com.intellij.util.io.awaitExit
|
import com.intellij.util.io.awaitExit
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
@ -43,9 +46,14 @@ class PreLaunchProcessListener(val console: ConsoleView) : ProcessListener {
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@Throws(ExecutionException::class)
|
@Throws(ExecutionException::class)
|
||||||
suspend fun executeCommandLineWithHook(commandLine: GeneralCommandLine): Boolean {
|
suspend fun executeCommandLineWithHook(project: Project, commandLine: GeneralCommandLine): Boolean {
|
||||||
return withProgressText(commandLine.commandLineString) {
|
return withProgressText(commandLine.commandLineString) {
|
||||||
val processHandler = ZigProcessHandler(commandLine)
|
val ipc = IPCUtil.wrapWithIPC(commandLine)
|
||||||
|
val cli = ipc?.cli ?: commandLine
|
||||||
|
val processHandler = ZigProcessHandler(cli)
|
||||||
|
if (ipc != null) {
|
||||||
|
project.ipc?.launchWatcher(ipc, processHandler.process)
|
||||||
|
}
|
||||||
this@PreLaunchProcessListener.processHandler = processHandler
|
this@PreLaunchProcessListener.processHandler = processHandler
|
||||||
hook(processHandler)
|
hook(processHandler)
|
||||||
processHandler.startNotify()
|
processHandler.startNotify()
|
||||||
|
|
|
@ -54,7 +54,7 @@ abstract class ZigDebugParametersEmitBinaryBase<ProfileState: ZigProfileState<*>
|
||||||
val exe = tmpDir.resolve("executable")
|
val exe = tmpDir.resolve("executable")
|
||||||
commandLine.addParameters("-femit-bin=${exe.absolutePathString()}")
|
commandLine.addParameters("-femit-bin=${exe.absolutePathString()}")
|
||||||
|
|
||||||
if (listener.executeCommandLineWithHook(commandLine))
|
if (listener.executeCommandLineWithHook(profileState.environment.project, commandLine))
|
||||||
throw ExecutionException(ZigDebugBundle.message("debug.base.compile.failed.generic"))
|
throw ExecutionException(ZigDebugBundle.message("debug.base.compile.failed.generic"))
|
||||||
|
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
|
|
|
@ -82,6 +82,7 @@ abstract class ZigDebugRunnerBase<ProfileState : ZigProfileState<*>> : ZigProgra
|
||||||
} catch (e: ExecutionException) {
|
} catch (e: ExecutionException) {
|
||||||
console.print("\n", ConsoleViewContentType.ERROR_OUTPUT)
|
console.print("\n", ConsoleViewContentType.ERROR_OUTPUT)
|
||||||
e.message?.let { listener.console.print(it, ConsoleViewContentType.SYSTEM_OUTPUT) }
|
e.message?.let { listener.console.print(it, ConsoleViewContentType.SYSTEM_OUTPUT) }
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
if (listener.isBuildFailed) {
|
if (listener.isBuildFailed) {
|
||||||
val executionResult = DefaultExecutionResult(console, listener.processHandler)
|
val executionResult = DefaultExecutionResult(console, listener.processHandler)
|
||||||
|
|
|
@ -61,7 +61,7 @@ class ZigDebugParametersBuild(
|
||||||
withProgressText("Building zig project") {
|
withProgressText("Building zig project") {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val commandLine = profileState.getCommandLine(toolchain, true)
|
val commandLine = profileState.getCommandLine(toolchain, true)
|
||||||
if (listener.executeCommandLineWithHook(commandLine))
|
if (listener.executeCommandLineWithHook(profileState.environment.project, commandLine))
|
||||||
throw ExecutionException(ZigDebugBundle.message("debug.build.compile.failed.generic"))
|
throw ExecutionException(ZigDebugBundle.message("debug.build.compile.failed.generic"))
|
||||||
val cfg = profileState.configuration
|
val cfg = profileState.configuration
|
||||||
val workingDir = cfg.workingDirectory.path
|
val workingDir = cfg.workingDirectory.path
|
||||||
|
|
|
@ -26,7 +26,9 @@ import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
import com.falsepattern.zigbrains.project.run.ZigProcessHandler
|
import com.falsepattern.zigbrains.project.run.ZigProcessHandler
|
||||||
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
|
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
|
||||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||||
|
import com.falsepattern.zigbrains.shared.ipc.IPCUtil
|
||||||
import com.falsepattern.zigbrains.shared.coroutine.runModalOrBlocking
|
import com.falsepattern.zigbrains.shared.coroutine.runModalOrBlocking
|
||||||
|
import com.falsepattern.zigbrains.shared.ipc.ipc
|
||||||
import com.intellij.build.BuildTextConsoleView
|
import com.intellij.build.BuildTextConsoleView
|
||||||
import com.intellij.execution.DefaultExecutionResult
|
import com.intellij.execution.DefaultExecutionResult
|
||||||
import com.intellij.execution.ExecutionException
|
import com.intellij.execution.ExecutionException
|
||||||
|
@ -36,6 +38,7 @@ import com.intellij.execution.configurations.PtyCommandLine
|
||||||
import com.intellij.execution.process.ProcessHandler
|
import com.intellij.execution.process.ProcessHandler
|
||||||
import com.intellij.execution.process.ProcessTerminatedListener
|
import com.intellij.execution.process.ProcessTerminatedListener
|
||||||
import com.intellij.execution.runners.ExecutionEnvironment
|
import com.intellij.execution.runners.ExecutionEnvironment
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.platform.ide.progress.ModalTaskOwner
|
import com.intellij.platform.ide.progress.ModalTaskOwner
|
||||||
import kotlin.io.path.pathString
|
import kotlin.io.path.pathString
|
||||||
|
|
||||||
|
@ -54,7 +57,7 @@ abstract class ZigProfileState<T: ZigExecConfig<T>> (
|
||||||
@Throws(ExecutionException::class)
|
@Throws(ExecutionException::class)
|
||||||
suspend fun startProcessSuspend(): ProcessHandler {
|
suspend fun startProcessSuspend(): ProcessHandler {
|
||||||
val toolchain = environment.project.zigProjectSettings.state.toolchain ?: throw ExecutionException(ZigBrainsBundle.message("exception.zig-profile-state.start-process.no-toolchain"))
|
val toolchain = environment.project.zigProjectSettings.state.toolchain ?: throw ExecutionException(ZigBrainsBundle.message("exception.zig-profile-state.start-process.no-toolchain"))
|
||||||
return ZigProcessHandler(getCommandLine(toolchain, false))
|
return startProcess(getCommandLine(toolchain, false), environment.project)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(ExecutionException::class)
|
@Throws(ExecutionException::class)
|
||||||
|
@ -74,16 +77,20 @@ abstract class ZigProfileState<T: ZigExecConfig<T>> (
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(ExecutionException::class)
|
@Throws(ExecutionException::class)
|
||||||
fun executeCommandLine(commandLine: GeneralCommandLine, environment: ExecutionEnvironment): DefaultExecutionResult {
|
suspend fun executeCommandLine(commandLine: GeneralCommandLine, environment: ExecutionEnvironment): DefaultExecutionResult {
|
||||||
val handler = startProcess(commandLine)
|
val handler = startProcess(commandLine, environment.project)
|
||||||
val console = BuildTextConsoleView(environment.project, false, emptyList())
|
val console = BuildTextConsoleView(environment.project, false, emptyList())
|
||||||
console.attachToProcess(handler)
|
console.attachToProcess(handler)
|
||||||
return DefaultExecutionResult(console, handler)
|
return DefaultExecutionResult(console, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(ExecutionException::class)
|
@Throws(ExecutionException::class)
|
||||||
fun startProcess(commandLine: GeneralCommandLine): ProcessHandler {
|
suspend fun startProcess(commandLine: GeneralCommandLine, project: Project): ProcessHandler {
|
||||||
val handler = ZigProcessHandler(commandLine)
|
val ipc = IPCUtil.wrapWithIPC(commandLine)
|
||||||
|
val handler = ZigProcessHandler(ipc?.cli ?: commandLine)
|
||||||
ProcessTerminatedListener.attach(handler)
|
ProcessTerminatedListener.attach(handler)
|
||||||
|
if (ipc != null) {
|
||||||
|
project.ipc?.launchWatcher(ipc, handler.process)
|
||||||
|
}
|
||||||
return handler
|
return handler
|
||||||
}
|
}
|
|
@ -39,6 +39,10 @@ class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigEx
|
||||||
private set
|
private set
|
||||||
var colored = ColoredConfigurable("colored")
|
var colored = ColoredConfigurable("colored")
|
||||||
private set
|
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"))
|
||||||
|
private set
|
||||||
var exePath = FilePathConfigurable("exePath", ZigBrainsBundle.message("exec.option.label.build.exe-path-debug"))
|
var exePath = FilePathConfigurable("exePath", ZigBrainsBundle.message("exec.option.label.build.exe-path-debug"))
|
||||||
private set
|
private set
|
||||||
var exeArgs = ArgsConfigurable("exeArgs", ZigBrainsBundle.message("exec.option.label.build.exe-args-debug"))
|
var exeArgs = ArgsConfigurable("exeArgs", ZigBrainsBundle.message("exec.option.label.build.exe-args-debug"))
|
||||||
|
@ -48,23 +52,10 @@ class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigEx
|
||||||
override suspend fun buildCommandLineArgs(debug: Boolean): List<String> {
|
override suspend fun buildCommandLineArgs(debug: Boolean): List<String> {
|
||||||
val result = ArrayList<String>()
|
val result = ArrayList<String>()
|
||||||
result.add("build")
|
result.add("build")
|
||||||
val argsSplit = buildSteps.argsSplit()
|
val steps = if (debug) debugBuildSteps.argsSplit() else buildSteps.argsSplit()
|
||||||
val steps = if (debug) {
|
|
||||||
val truncatedSteps = ArrayList<String>()
|
|
||||||
for (step in argsSplit) {
|
|
||||||
if (step == "run")
|
|
||||||
continue
|
|
||||||
|
|
||||||
if (step == "test")
|
|
||||||
throw ExecutionException(ZigBrainsBundle.message("exception.zig-build.debug.test-not-supported"))
|
|
||||||
|
|
||||||
truncatedSteps.add(step)
|
|
||||||
}
|
|
||||||
truncatedSteps
|
|
||||||
} else argsSplit
|
|
||||||
result.addAll(steps)
|
result.addAll(steps)
|
||||||
result.addAll(coloredCliFlags(colored.value, debug))
|
result.addAll(coloredCliFlags(colored.value, debug))
|
||||||
result.addAll(extraArgs.argsSplit())
|
result.addAll(if (debug) debugExtraArgs.argsSplit() else extraArgs.argsSplit())
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +75,7 @@ class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigEx
|
||||||
override fun getConfigurables(): List<ZigConfigurable<*>> {
|
override fun getConfigurables(): List<ZigConfigurable<*>> {
|
||||||
val baseCfg = super.getConfigurables() + listOf(buildSteps, extraArgs, colored)
|
val baseCfg = super.getConfigurables() + listOf(buildSteps, extraArgs, colored)
|
||||||
return if (ZBFeatures.debug()) {
|
return if (ZBFeatures.debug()) {
|
||||||
baseCfg + listOf(exePath, exeArgs)
|
baseCfg + listOf(debugBuildSteps, debugExtraArgs, exePath, exeArgs)
|
||||||
} else {
|
} else {
|
||||||
baseCfg
|
baseCfg
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,7 @@ class ZigNewProjectPanel(private var handleGit: Boolean): Disposable {
|
||||||
|
|
||||||
|
|
||||||
private val defaultTemplates get() = listOf(
|
private val defaultTemplates get() = listOf(
|
||||||
ZigExecutableTemplate(),
|
// ZigExecutableTemplate(),
|
||||||
ZigLibraryTemplate(),
|
// ZigLibraryTemplate(),
|
||||||
ZigInitTemplate()
|
ZigInitTemplate()
|
||||||
)
|
)
|
|
@ -83,7 +83,8 @@ class ZigStepDiscoveryService(private val project: Project) {
|
||||||
val result = zig.callWithArgs(
|
val result = zig.callWithArgs(
|
||||||
project.guessProjectDir()?.toNioPathOrNull(),
|
project.guessProjectDir()?.toNioPathOrNull(),
|
||||||
"build", "-l",
|
"build", "-l",
|
||||||
timeoutMillis = currentTimeoutSec * 1000L
|
timeoutMillis = currentTimeoutSec * 1000L,
|
||||||
|
ipcProject = project
|
||||||
).getOrElse { throwable ->
|
).getOrElse { throwable ->
|
||||||
errorReload(ErrorType.MissingZigExe, throwable.message)
|
errorReload(ErrorType.MissingZigExe, throwable.message)
|
||||||
null
|
null
|
||||||
|
@ -106,7 +107,7 @@ class ZigStepDiscoveryService(private val project: Project) {
|
||||||
} else if (result.isTimeout) {
|
} else if (result.isTimeout) {
|
||||||
timeoutReload(currentTimeoutSec)
|
timeoutReload(currentTimeoutSec)
|
||||||
currentTimeoutSec *= 2
|
currentTimeoutSec *= 2
|
||||||
} else if (result.stderrLines.any { it.contains("error: no build.zig file found, in the current directory or any parent directories") }) {
|
} else if (result.stderrLines.any { it.contains("error: no build.zig file found") }) {
|
||||||
errorReload(ErrorType.MissingBuildZig, result.stderr)
|
errorReload(ErrorType.MissingBuildZig, result.stderr)
|
||||||
} else {
|
} else {
|
||||||
errorReload(ErrorType.GeneralError, result.stderr)
|
errorReload(ErrorType.GeneralError, result.stderr)
|
||||||
|
@ -158,6 +159,6 @@ val Project.zigStepDiscovery get() = service<ZigStepDiscoveryService>()
|
||||||
|
|
||||||
private val SPACES = Regex("\\s+")
|
private val SPACES = Regex("\\s+")
|
||||||
|
|
||||||
private const val DEFAULT_TIMEOUT_SEC = 10
|
private const val DEFAULT_TIMEOUT_SEC = 32
|
||||||
|
|
||||||
private val LOG = Logger.getInstance(ZigStepDiscoveryService::class.java)
|
private val LOG = Logger.getInstance(ZigStepDiscoveryService::class.java)
|
|
@ -28,7 +28,7 @@ import com.intellij.openapi.project.Project
|
||||||
import com.intellij.ui.SimpleTextAttributes
|
import com.intellij.ui.SimpleTextAttributes
|
||||||
import javax.swing.Icon
|
import javax.swing.Icon
|
||||||
|
|
||||||
open class BaseNodeDescriptor<T>(project: Project?, displayName: String, displayIcon: Icon, private var description: String? = null): PresentableNodeDescriptor<T>(project, null) {
|
open class BaseNodeDescriptor<T>(project: Project?, displayName: String, displayIcon: Icon? = null, private var description: String? = null): PresentableNodeDescriptor<T>(project, null) {
|
||||||
init {
|
init {
|
||||||
icon = displayIcon
|
icon = displayIcon
|
||||||
myName = displayName
|
myName = displayName
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
|
|
||||||
package com.falsepattern.zigbrains.project.steps.ui
|
package com.falsepattern.zigbrains.project.steps.ui
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.Icons
|
|
||||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
import com.falsepattern.zigbrains.project.execution.build.ZigConfigTypeBuild
|
import com.falsepattern.zigbrains.project.execution.build.ZigConfigTypeBuild
|
||||||
import com.falsepattern.zigbrains.project.execution.build.ZigExecConfigBuild
|
import com.falsepattern.zigbrains.project.execution.build.ZigExecConfigBuild
|
||||||
|
@ -30,6 +29,10 @@ import com.falsepattern.zigbrains.project.execution.firstConfigFactory
|
||||||
import com.falsepattern.zigbrains.project.steps.discovery.ZigStepDiscoveryListener
|
import com.falsepattern.zigbrains.project.steps.discovery.ZigStepDiscoveryListener
|
||||||
import com.falsepattern.zigbrains.project.steps.discovery.zigStepDiscovery
|
import com.falsepattern.zigbrains.project.steps.discovery.zigStepDiscovery
|
||||||
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||||
|
import com.falsepattern.zigbrains.shared.ipc.IPCUtil
|
||||||
|
import com.falsepattern.zigbrains.shared.ipc.ZigIPCService
|
||||||
|
import com.falsepattern.zigbrains.shared.ipc.ipc
|
||||||
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
import com.intellij.execution.ProgramRunnerUtil
|
import com.intellij.execution.ProgramRunnerUtil
|
||||||
import com.intellij.execution.RunManager
|
import com.intellij.execution.RunManager
|
||||||
import com.intellij.execution.RunnerAndConfigurationSettings
|
import com.intellij.execution.RunnerAndConfigurationSettings
|
||||||
|
@ -38,47 +41,58 @@ import com.intellij.icons.AllIcons
|
||||||
import com.intellij.openapi.Disposable
|
import com.intellij.openapi.Disposable
|
||||||
import com.intellij.openapi.actionSystem.ActionManager
|
import com.intellij.openapi.actionSystem.ActionManager
|
||||||
import com.intellij.openapi.actionSystem.DefaultActionGroup
|
import com.intellij.openapi.actionSystem.DefaultActionGroup
|
||||||
|
import com.intellij.openapi.application.ModalityState
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.ui.SimpleToolWindowPanel
|
import com.intellij.openapi.ui.SimpleToolWindowPanel
|
||||||
import com.intellij.openapi.util.Disposer
|
import com.intellij.openapi.util.Disposer
|
||||||
import com.intellij.openapi.util.Key
|
|
||||||
import com.intellij.openapi.wm.ToolWindow
|
import com.intellij.openapi.wm.ToolWindow
|
||||||
import com.intellij.openapi.wm.ToolWindowManager
|
|
||||||
import com.intellij.ui.AnimatedIcon
|
import com.intellij.ui.AnimatedIcon
|
||||||
import com.intellij.ui.components.JBLabel
|
import com.intellij.ui.components.JBLabel
|
||||||
import com.intellij.ui.components.JBScrollPane
|
import com.intellij.ui.components.JBScrollPane
|
||||||
import com.intellij.ui.components.JBTextArea
|
import com.intellij.ui.components.JBTextArea
|
||||||
|
import com.intellij.ui.components.panels.VerticalLayout
|
||||||
import com.intellij.ui.content.Content
|
import com.intellij.ui.content.Content
|
||||||
import com.intellij.ui.content.ContentFactory
|
import com.intellij.ui.content.ContentFactory
|
||||||
import com.intellij.ui.treeStructure.Tree
|
import com.intellij.ui.treeStructure.Tree
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
|
import java.awt.Component
|
||||||
import java.awt.GridBagConstraints
|
import java.awt.GridBagConstraints
|
||||||
import java.awt.GridBagLayout
|
import java.awt.GridBagLayout
|
||||||
import java.awt.event.MouseAdapter
|
import java.awt.event.MouseAdapter
|
||||||
import java.awt.event.MouseEvent
|
import java.awt.event.MouseEvent
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import javax.swing.BoxLayout
|
import javax.swing.BoxLayout
|
||||||
import javax.swing.JPanel
|
import javax.swing.JPanel
|
||||||
import javax.swing.SwingConstants
|
import javax.swing.SwingConstants
|
||||||
import javax.swing.tree.DefaultMutableTreeNode
|
import javax.swing.tree.DefaultMutableTreeNode
|
||||||
import javax.swing.tree.DefaultTreeModel
|
import javax.swing.tree.DefaultTreeModel
|
||||||
|
import javax.swing.tree.MutableTreeNode
|
||||||
import javax.swing.tree.TreePath
|
import javax.swing.tree.TreePath
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
class BuildToolWindowContext(private val project: Project): Disposable {
|
class BuildToolWindowContext(private val project: Project): Disposable {
|
||||||
val rootNode: DefaultMutableTreeNode = DefaultMutableTreeNode(BaseNodeDescriptor<Any>(project, project.name, AllIcons.Actions.ProjectDirectory))
|
inner class TreeBox() {
|
||||||
private val buildZig: DefaultMutableTreeNode = DefaultMutableTreeNode(BaseNodeDescriptor<Any>(project, ZigBrainsBundle.message("build.tool.window.tree.steps.label"), Icons.Zig))
|
val panel = JPanel(VerticalLayout(0))
|
||||||
init {
|
val root = DefaultMutableTreeNode(BaseNodeDescriptor<Any>(project, ""))
|
||||||
rootNode.add(buildZig)
|
val model = DefaultTreeModel(root)
|
||||||
|
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 var live = AtomicBoolean(true)
|
||||||
|
init {
|
||||||
|
viewPanel.add(JBLabel(ZigBrainsBundle.message("build.tool.window.tree.steps.label")))
|
||||||
|
viewPanel.add(steps.panel)
|
||||||
|
steps.panel.setNotScanned()
|
||||||
|
|
||||||
private fun setViewportTree(viewport: JBScrollPane) {
|
steps.tree.addMouseListener(object : MouseAdapter() {
|
||||||
val model = DefaultTreeModel(rootNode)
|
|
||||||
val tree = Tree(model)
|
|
||||||
tree.expandPath(TreePath(model.getPathToRoot(buildZig)))
|
|
||||||
viewport.setViewportView(tree)
|
|
||||||
tree.addMouseListener(object : MouseAdapter() {
|
|
||||||
override fun mouseClicked(e: MouseEvent) {
|
override fun mouseClicked(e: MouseEvent) {
|
||||||
if (e.clickCount == 2) {
|
if (e.clickCount == 2) {
|
||||||
val node = tree.lastSelectedPathComponent as? DefaultMutableTreeNode ?: return
|
val node = steps.tree.lastSelectedPathComponent as? DefaultMutableTreeNode ?: return
|
||||||
val step = node.userObject as? StepNodeDescriptor ?: return
|
val step = node.userObject as? StepNodeDescriptor ?: return
|
||||||
|
|
||||||
val stepName = step.element?.name ?: return
|
val stepName = step.element?.name ?: return
|
||||||
|
@ -97,6 +111,58 @@ class BuildToolWindowContext(private val project: Project): Disposable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (build != null) {
|
||||||
|
viewPanel.add(JBLabel(ZigBrainsBundle.message("build.tool.window.tree.build.label")))
|
||||||
|
viewPanel.add(build.panel)
|
||||||
|
build.panel.setNoBuilds()
|
||||||
|
|
||||||
|
project.zigCoroutineScope.launch {
|
||||||
|
while (!project.isDisposed && live.get()) {
|
||||||
|
val ipc = project.ipc ?: return@launch
|
||||||
|
withTimeoutOrNull(1000) {
|
||||||
|
ipc.changed.receive()
|
||||||
|
} ?: continue
|
||||||
|
ipc.mutex.withLock {
|
||||||
|
withEDTContext(ModalityState.any()) {
|
||||||
|
if (ipc.nodes.isEmpty()) {
|
||||||
|
build.root.removeAllChildren()
|
||||||
|
build.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 ->
|
||||||
|
if (child !is ZigIPCService.IPCTreeNode) {
|
||||||
|
return@forEach
|
||||||
|
}
|
||||||
|
if (child !in allNodes) {
|
||||||
|
removedNodes.add(child)
|
||||||
|
} else {
|
||||||
|
existingNodes.add(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val newNodes = ArrayList<MutableTreeNode>(allNodes)
|
||||||
|
newNodes.removeAll(existingNodes)
|
||||||
|
removedNodes.forEach { build.root.remove(it) }
|
||||||
|
newNodes.forEach { build.root.add(it) }
|
||||||
|
if (removedNodes.isNotEmpty() || newNodes.isNotEmpty()) {
|
||||||
|
build.model.reload(build.root)
|
||||||
|
}
|
||||||
|
if (build.root.childCount == 0) {
|
||||||
|
build.panel.setNoBuilds()
|
||||||
|
} else {
|
||||||
|
build.panel.setViewportBody(build.tree)
|
||||||
|
}
|
||||||
|
for (bn in allNodes) {
|
||||||
|
expandRecursively(build, bn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createContentPanel(): Content {
|
private fun createContentPanel(): Content {
|
||||||
|
@ -120,51 +186,62 @@ class BuildToolWindowContext(private val project: Project): Disposable {
|
||||||
c.weighty = 1.0
|
c.weighty = 1.0
|
||||||
c.fill = GridBagConstraints.BOTH
|
c.fill = GridBagConstraints.BOTH
|
||||||
val viewport = JBScrollPane()
|
val viewport = JBScrollPane()
|
||||||
viewport.setViewportNoContent()
|
viewport.setViewportView(viewPanel)
|
||||||
body.add(viewport, c)
|
body.add(viewport, c)
|
||||||
val content = ContentFactory.getInstance().createContent(contentPanel, "", false)
|
val content = ContentFactory.getInstance().createContent(contentPanel, "", false)
|
||||||
content.putUserData(VIEWPORT, viewport)
|
|
||||||
return content
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
|
live.set(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
suspend fun create(project: Project, window: ToolWindow) {
|
suspend fun create(project: Project, window: ToolWindow) {
|
||||||
withEDTContext {
|
withEDTContext(ModalityState.any()) {
|
||||||
val context = BuildToolWindowContext(project)
|
val context = BuildToolWindowContext(project)
|
||||||
Disposer.register(context, project.zigStepDiscovery.register(context.BuildReloadListener()))
|
Disposer.register(context, project.zigStepDiscovery.register(context.BuildReloadListener()))
|
||||||
Disposer.register(window.disposable, context)
|
Disposer.register(window.disposable, context)
|
||||||
window.contentManager.addContent(context.createContentPanel())
|
window.contentManager.addContent(context.createContentPanel())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun expandRecursively(box: TreeBox, node: ZigIPCService.IPCTreeNode) {
|
||||||
|
if (node.changed) {
|
||||||
|
box.model.reload(node)
|
||||||
|
node.changed = false
|
||||||
|
}
|
||||||
|
box.tree.expandPath(TreePath(box.model.getPathToRoot(node)))
|
||||||
|
node.children().asIterator().forEach { child ->
|
||||||
|
(child as? ZigIPCService.IPCTreeNode)?.let { expandRecursively(box, it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class BuildReloadListener: ZigStepDiscoveryListener {
|
inner class BuildReloadListener: ZigStepDiscoveryListener {
|
||||||
override suspend fun preReload() {
|
override suspend fun preReload() {
|
||||||
getViewport(project)?.setViewportLoading()
|
steps.panel.setRunningZigBuild()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun postReload(steps: List<Pair<String, String?>>) {
|
override suspend fun postReload(stepInfo: List<Pair<String, String?>>) {
|
||||||
buildZig.removeAllChildren()
|
steps.root.removeAllChildren()
|
||||||
for ((task, description) in steps) {
|
for ((task, description) in stepInfo) {
|
||||||
val icon = when(task) {
|
val icon = when(task) {
|
||||||
"install" -> AllIcons.Actions.Install
|
"install" -> AllIcons.Actions.Install
|
||||||
"uninstall" -> AllIcons.Actions.Uninstall
|
"uninstall" -> AllIcons.Actions.Uninstall
|
||||||
else -> AllIcons.RunConfigurations.TestState.Run
|
else -> AllIcons.RunConfigurations.TestState.Run
|
||||||
}
|
}
|
||||||
buildZig.add(DefaultMutableTreeNode(StepNodeDescriptor(project, task, icon, description)))
|
steps.root.add(DefaultMutableTreeNode(StepNodeDescriptor(project, task, icon, description)))
|
||||||
}
|
}
|
||||||
withEDTContext {
|
withEDTContext(ModalityState.any()) {
|
||||||
getViewport(project)?.let { setViewportTree(it) }
|
steps.model.reload(steps.root)
|
||||||
|
steps.panel.setViewportBody(steps.tree)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun errorReload(type: ZigStepDiscoveryListener.ErrorType, details: String?) {
|
override suspend fun errorReload(type: ZigStepDiscoveryListener.ErrorType, details: String?) {
|
||||||
withEDTContext {
|
withEDTContext(ModalityState.any()) {
|
||||||
getViewport(project)?.setViewportError(ZigBrainsBundle.message(when(type) {
|
steps.panel.setViewportError(ZigBrainsBundle.message(when(type) {
|
||||||
ZigStepDiscoveryListener.ErrorType.MissingToolchain -> "build.tool.window.status.error.missing-toolchain"
|
ZigStepDiscoveryListener.ErrorType.MissingToolchain -> "build.tool.window.status.error.missing-toolchain"
|
||||||
ZigStepDiscoveryListener.ErrorType.MissingZigExe -> "build.tool.window.status.error.missing-zig-exe"
|
ZigStepDiscoveryListener.ErrorType.MissingZigExe -> "build.tool.window.status.error.missing-zig-exe"
|
||||||
ZigStepDiscoveryListener.ErrorType.MissingBuildZig -> "build.tool.window.status.error.missing-build-zig"
|
ZigStepDiscoveryListener.ErrorType.MissingBuildZig -> "build.tool.window.status.error.missing-build-zig"
|
||||||
|
@ -174,22 +251,32 @@ class BuildToolWindowContext(private val project: Project): Disposable {
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun timeoutReload(seconds: Int) {
|
override suspend fun timeoutReload(seconds: Int) {
|
||||||
withEDTContext {
|
withEDTContext(ModalityState.any()) {
|
||||||
getViewport(project)?.setViewportError(ZigBrainsBundle.message("build.tool.window.status.timeout", seconds), null)
|
steps.panel.setViewportError(ZigBrainsBundle.message("build.tool.window.status.timeout", seconds), null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun JBScrollPane.setViewportLoading() {
|
private fun JPanel.setViewportBody(component: Component) {
|
||||||
setViewportView(JBLabel(ZigBrainsBundle.message("build.tool.window.status.loading"), AnimatedIcon.Default(), SwingConstants.CENTER))
|
removeAll()
|
||||||
|
add(component)
|
||||||
|
repaint()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun JBScrollPane.setViewportNoContent() {
|
private fun JPanel.setRunningZigBuild() {
|
||||||
setViewportView(JBLabel(ZigBrainsBundle.message("build.tool.window.status.not-scanned"), AllIcons.General.Information, SwingConstants.CENTER))
|
setViewportBody(JBLabel(ZigBrainsBundle.message("build.tool.window.status.loading"), AnimatedIcon.Default(), SwingConstants.CENTER))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun JBScrollPane.setViewportError(msg: String, details: String?) {
|
private fun JPanel.setNotScanned() {
|
||||||
|
setViewportBody(JBLabel(ZigBrainsBundle.message("build.tool.window.status.not-scanned"), AllIcons.General.Information, SwingConstants.CENTER))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JPanel.setNoBuilds() {
|
||||||
|
setViewportBody(JBLabel(ZigBrainsBundle.message("build.tool.window.status.no-builds"), AllIcons.General.Information, SwingConstants.CENTER))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JPanel.setViewportError(msg: String, details: String?) {
|
||||||
val result = JPanel()
|
val result = JPanel()
|
||||||
result.layout = BoxLayout(result, BoxLayout.Y_AXIS)
|
result.layout = BoxLayout(result, BoxLayout.Y_AXIS)
|
||||||
result.add(JBLabel(msg, AllIcons.General.Error, SwingConstants.CENTER))
|
result.add(JBLabel(msg, AllIcons.General.Error, SwingConstants.CENTER))
|
||||||
|
@ -200,14 +287,7 @@ private fun JBScrollPane.setViewportError(msg: String, details: String?) {
|
||||||
val scroll = JBScrollPane(code)
|
val scroll = JBScrollPane(code)
|
||||||
result.add(scroll)
|
result.add(scroll)
|
||||||
}
|
}
|
||||||
setViewportView(result)
|
setViewportBody(result)
|
||||||
}
|
|
||||||
|
|
||||||
private fun getViewport(project: Project): JBScrollPane? {
|
|
||||||
val toolWindow = ToolWindowManager.getInstance(project).getToolWindow("zigbrains.build") ?: return null
|
|
||||||
val cm = toolWindow.contentManager
|
|
||||||
val content = cm.getContent(0) ?: return null
|
|
||||||
return content.getUserData(VIEWPORT)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getExistingRunConfig(manager: RunManager, stepName: String): RunnerAndConfigurationSettings? {
|
private fun getExistingRunConfig(manager: RunManager, stepName: String): RunnerAndConfigurationSettings? {
|
||||||
|
@ -222,5 +302,3 @@ private fun getExistingRunConfig(manager: RunManager, stepName: String): RunnerA
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private val VIEWPORT = Key.create<JBScrollPane>("MODEL")
|
|
||||||
|
|
|
@ -23,12 +23,13 @@
|
||||||
package com.falsepattern.zigbrains.project.steps.ui
|
package com.falsepattern.zigbrains.project.steps.ui
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
|
import com.intellij.openapi.project.DumbAware
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.wm.ToolWindow
|
import com.intellij.openapi.wm.ToolWindow
|
||||||
import com.intellij.openapi.wm.ToolWindowFactory
|
import com.intellij.openapi.wm.ToolWindowFactory
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class BuildToolWindowFactory: ToolWindowFactory {
|
class BuildToolWindowFactory: ToolWindowFactory, DumbAware {
|
||||||
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
|
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
|
||||||
project.zigCoroutineScope.launch {
|
project.zigCoroutineScope.launch {
|
||||||
BuildToolWindowContext.create(project, toolWindow)
|
BuildToolWindowContext.create(project, toolWindow)
|
||||||
|
|
|
@ -27,14 +27,15 @@ import com.falsepattern.zigbrains.shared.cli.call
|
||||||
import com.falsepattern.zigbrains.shared.cli.createCommandLineSafe
|
import com.falsepattern.zigbrains.shared.cli.createCommandLineSafe
|
||||||
import com.intellij.execution.configurations.GeneralCommandLine
|
import com.intellij.execution.configurations.GeneralCommandLine
|
||||||
import com.intellij.execution.process.ProcessOutput
|
import com.intellij.execution.process.ProcessOutput
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
abstract class ZigTool(val toolchain: AbstractZigToolchain) {
|
abstract class ZigTool(val toolchain: AbstractZigToolchain) {
|
||||||
abstract val toolName: String
|
abstract val toolName: String
|
||||||
|
|
||||||
suspend fun callWithArgs(workingDirectory: Path?, vararg parameters: String, timeoutMillis: Long = Long.MAX_VALUE): Result<ProcessOutput> {
|
suspend fun callWithArgs(workingDirectory: Path?, vararg parameters: String, timeoutMillis: Long = Long.MAX_VALUE, ipcProject: Project? = null): Result<ProcessOutput> {
|
||||||
val cli = createBaseCommandLine(workingDirectory, *parameters).let { it.getOrElse { return Result.failure(it) } }
|
val cli = createBaseCommandLine(workingDirectory, *parameters).let { it.getOrElse { return Result.failure(it) } }
|
||||||
return cli.call(timeoutMillis)
|
return cli.call(timeoutMillis, ipcProject = ipcProject)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun createBaseCommandLine(
|
private suspend fun createBaseCommandLine(
|
||||||
|
|
|
@ -23,9 +23,12 @@
|
||||||
package com.falsepattern.zigbrains.shared.cli
|
package com.falsepattern.zigbrains.shared.cli
|
||||||
|
|
||||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||||
|
import com.falsepattern.zigbrains.shared.ipc.IPCUtil
|
||||||
|
import com.falsepattern.zigbrains.shared.ipc.ipc
|
||||||
import com.intellij.execution.configurations.GeneralCommandLine
|
import com.intellij.execution.configurations.GeneralCommandLine
|
||||||
import com.intellij.execution.process.ProcessOutput
|
import com.intellij.execution.process.ProcessOutput
|
||||||
import com.intellij.openapi.options.ConfigurationException
|
import com.intellij.openapi.options.ConfigurationException
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.util.io.awaitExit
|
import com.intellij.util.io.awaitExit
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.runInterruptible
|
import kotlinx.coroutines.runInterruptible
|
||||||
|
@ -130,9 +133,14 @@ fun createCommandLineSafe(
|
||||||
return Result.success(cli)
|
return Result.success(cli)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun GeneralCommandLine.call(timeoutMillis: Long = Long.MAX_VALUE): Result<ProcessOutput> {
|
suspend fun GeneralCommandLine.call(timeoutMillis: Long = Long.MAX_VALUE, ipcProject: Project? = null): Result<ProcessOutput> {
|
||||||
|
val ipc = if (ipcProject != null) IPCUtil.wrapWithIPC(this) else null
|
||||||
|
val cli = ipc?.cli ?: this
|
||||||
val (process, exitCode) = withContext(Dispatchers.IO) {
|
val (process, exitCode) = withContext(Dispatchers.IO) {
|
||||||
val process = createProcess()
|
val process = cli.createProcess()
|
||||||
|
if (ipc != null) {
|
||||||
|
ipcProject!!.ipc?.launchWatcher(ipc, process)
|
||||||
|
}
|
||||||
val exit = withTimeoutOrNull(timeoutMillis) {
|
val exit = withTimeoutOrNull(timeoutMillis) {
|
||||||
process.awaitExit()
|
process.awaitExit()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* 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.shared.ipc
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.direnv.emptyEnv
|
||||||
|
import com.intellij.execution.configurations.GeneralCommandLine
|
||||||
|
import com.intellij.openapi.util.SystemInfo
|
||||||
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
|
import com.intellij.util.io.awaitExit
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Path
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode
|
||||||
|
import kotlin.io.path.deleteIfExists
|
||||||
|
import kotlin.io.path.pathString
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Zig build progress node IPC glue code
|
||||||
|
*/
|
||||||
|
object IPCUtil {
|
||||||
|
val haveIPC = checkHaveIPC()
|
||||||
|
|
||||||
|
private fun checkHaveIPC(): Boolean {
|
||||||
|
if (SystemInfo.isWindows) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
val mkfifo = emptyEnv.findExecutableOnPATH("mkfifo")
|
||||||
|
val bash = emptyEnv.findExecutableOnPATH("bash")
|
||||||
|
return mkfifo != null && bash != null
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun mkfifo(path: Path): AutoCloseable? {
|
||||||
|
val cli = GeneralCommandLine("mkfifo", path.pathString)
|
||||||
|
val process = cli.createProcess()
|
||||||
|
val exitCode = process.awaitExit()
|
||||||
|
return if (exitCode == 0) AutoCloseable {
|
||||||
|
path.deleteIfExists()
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
|
||||||
|
data class IPC(val cli: GeneralCommandLine, val fifoPath: Path, val fifoClose: AutoCloseable)
|
||||||
|
|
||||||
|
suspend fun wrapWithIPC(cli: GeneralCommandLine): IPC? {
|
||||||
|
if (!haveIPC)
|
||||||
|
return null
|
||||||
|
val fifoFile = FileUtil.createTempFile("zigbrains-ipc-pipe", null, true).toPath()
|
||||||
|
fifoFile.deleteIfExists()
|
||||||
|
val fifo = mkfifo(fifoFile)
|
||||||
|
if (fifo == null) {
|
||||||
|
fifoFile.deleteIfExists()
|
||||||
|
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}>&-"
|
||||||
|
cli.withExePath("bash")
|
||||||
|
cli.parametersList.clearAll()
|
||||||
|
cli.addParameters("-c", args)
|
||||||
|
return IPC(cli, fifoFile, fifo)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* 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.shared.ipc
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.project.steps.ui.BaseNodeDescriptor
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.util.asSafely
|
||||||
|
import java.io.DataInput
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode
|
||||||
|
|
||||||
|
data class Payload(val completed: UInt, val estimatedTotal: UInt, val name: String, var children: ArrayList<Payload> = ArrayList()) {
|
||||||
|
companion object {
|
||||||
|
fun DataInput.readPayload(): Payload {
|
||||||
|
val completed = readInt().toUInt()
|
||||||
|
val estimatedTotal = readInt().toUInt()
|
||||||
|
val name = ByteArray(40) {
|
||||||
|
readByte()
|
||||||
|
}
|
||||||
|
val length = name.indexOf(0).let { if (it == -1) 40 else it }
|
||||||
|
val nameText = String(name, 0, length)
|
||||||
|
return Payload(completed, estimatedTotal, nameText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addWithChildren(project: Project, parent: ZigIPCService.IPCTreeNode, index: Int) {
|
||||||
|
val text = StringBuilder()
|
||||||
|
if (estimatedTotal != 0u) {
|
||||||
|
text.append('[').append(completed).append('/').append(estimatedTotal).append("] ")
|
||||||
|
} else if (completed != 0u) {
|
||||||
|
text.append('[').append(completed).append("] ")
|
||||||
|
}
|
||||||
|
text.append(name)
|
||||||
|
val descriptor = BaseNodeDescriptor<Any>(project, text.toString())
|
||||||
|
val self = if (index >= parent.childCount) {
|
||||||
|
ZigIPCService.IPCTreeNode(descriptor).also { parent.add(it) }
|
||||||
|
} else {
|
||||||
|
parent.getChildAt(index).asSafely<ZigIPCService.IPCTreeNode>()?.also {
|
||||||
|
(it.userObject as BaseNodeDescriptor<*>).applyFrom(descriptor)
|
||||||
|
} ?: ZigIPCService.IPCTreeNode(descriptor).also { parent.add(it) }
|
||||||
|
}
|
||||||
|
for ((i, child) in children.withIndex()) {
|
||||||
|
child.addWithChildren(project, self, i)
|
||||||
|
}
|
||||||
|
while (self.childCount > children.size) {
|
||||||
|
self.remove(self.childCount - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
/*
|
||||||
|
* 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.shared.ipc
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.Icons
|
||||||
|
import com.falsepattern.zigbrains.project.steps.ui.BaseNodeDescriptor
|
||||||
|
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||||
|
import com.falsepattern.zigbrains.shared.ipc.Payload.Companion.readPayload
|
||||||
|
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||||
|
import com.google.common.io.LittleEndianDataInputStream
|
||||||
|
import com.intellij.icons.AllIcons
|
||||||
|
import com.intellij.openapi.application.ModalityState
|
||||||
|
import com.intellij.openapi.components.Service
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.BufferedInputStream
|
||||||
|
import java.io.DataInput
|
||||||
|
import java.io.EOFException
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode
|
||||||
|
import javax.swing.tree.MutableTreeNode
|
||||||
|
import kotlin.io.path.deleteIfExists
|
||||||
|
import kotlin.io.path.inputStream
|
||||||
|
|
||||||
|
@Service(Service.Level.PROJECT)
|
||||||
|
class ZigIPCService(val project: Project) {
|
||||||
|
class IPCTreeNode(userObject: Any?): DefaultMutableTreeNode(userObject) {
|
||||||
|
var changed: Boolean = true
|
||||||
|
|
||||||
|
override fun add(newChild: MutableTreeNode?) {
|
||||||
|
super.add(newChild)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(aChild: MutableTreeNode?) {
|
||||||
|
super.remove(aChild)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun remove(childIndex: Int) {
|
||||||
|
super.remove(childIndex)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val nodes = ArrayList<IPCTreeNode>()
|
||||||
|
val changed = Channel<Unit>(1)
|
||||||
|
val mutex = Mutex()
|
||||||
|
|
||||||
|
private fun DataInput.readRoots(): List<Payload> {
|
||||||
|
val len = readByte().toUByte().toInt()
|
||||||
|
val payloads = Array<Payload>(len) {
|
||||||
|
readPayload()
|
||||||
|
}
|
||||||
|
val parents = ByteArray(len) {
|
||||||
|
readByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
val roots = ArrayList<Payload>()
|
||||||
|
for (i in 0..len - 1) {
|
||||||
|
val parent = parents[i].toUByte()
|
||||||
|
val payload = payloads[i]
|
||||||
|
if (parent.toUInt() == 255u) {
|
||||||
|
roots.add(payloads[i])
|
||||||
|
} else {
|
||||||
|
payloads[parent.toInt()].children.add(payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return roots
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun watch(ipc: IPCUtil.IPC, process: Process) {
|
||||||
|
val currentNode = IPCTreeNode(BaseNodeDescriptor<Any>(project, "pid: ${process.pid()}", AllIcons.Actions.InlayGear))
|
||||||
|
mutex.withLock {
|
||||||
|
nodes.add(currentNode)
|
||||||
|
}
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
try {
|
||||||
|
LittleEndianDataInputStream(BufferedInputStream(ipc.fifoPath.inputStream())).use { fifo ->
|
||||||
|
while (!project.isDisposed && process.isAlive) {
|
||||||
|
val roots = fifo.readRoots()
|
||||||
|
mutex.withLock {
|
||||||
|
for ((id, root) in roots.withIndex()) {
|
||||||
|
root.addWithChildren(project, currentNode, id)
|
||||||
|
}
|
||||||
|
while (currentNode.childCount > roots.size) {
|
||||||
|
currentNode.remove(currentNode.childCount - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed.trySend(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (_: EOFException) {
|
||||||
|
} finally {
|
||||||
|
mutex.withLock {
|
||||||
|
nodes.remove(currentNode)
|
||||||
|
}
|
||||||
|
changed.trySend(Unit)
|
||||||
|
ipc.fifoPath.deleteIfExists()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun launchWatcher(ipc: IPCUtil.IPC, process: Process) {
|
||||||
|
project.zigCoroutineScope.launch {
|
||||||
|
watch(ipc, process)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val Project.ipc get() = if (IPCUtil.haveIPC) service<ZigIPCService>() else null
|
|
@ -85,8 +85,10 @@ exec.option.label.compiler-args=Extra compiler command line arguments
|
||||||
exec.option.label.exe-args=Output program command line arguments
|
exec.option.label.exe-args=Output program command line arguments
|
||||||
exec.option.label.build.steps=Build steps
|
exec.option.label.build.steps=Build steps
|
||||||
exec.option.label.build.args=Extra command line arguments
|
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.steps-debug=Debug Build steps
|
||||||
exec.option.label.build.exe-path-debug=Output executable created by the build (debug only, autodetect if empty)
|
exec.option.label.build.args-debug=Debug Extra command line arguments
|
||||||
|
exec.option.label.build.exe-args-debug=Debug output program command line arguments
|
||||||
|
exec.option.label.build.exe-path-debug=Debug output executable created by the build
|
||||||
exception.zig.empty-file-path=Empty file path
|
exception.zig.empty-file-path=Empty file path
|
||||||
exception.translate-command-line.unbalanced-quotes=Unbalanced quotes in {0}
|
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-profile-state.start-process.no-toolchain=Failed to get zig toolchain from project
|
||||||
|
@ -108,12 +110,15 @@ settings.project.label.toolchain=Toolchain location
|
||||||
settings.project.label.toolchain-version=Detected zig version
|
settings.project.label.toolchain-version=Detected zig version
|
||||||
settings.project.label.override-std=Override standard library
|
settings.project.label.override-std=Override standard library
|
||||||
settings.project.label.std-location=Standard library location
|
settings.project.label.std-location=Standard library location
|
||||||
|
toolwindow.stripe.zigbrains.build=Zig
|
||||||
build.tool.window.tree.steps.label=Steps
|
build.tool.window.tree.steps.label=Steps
|
||||||
|
build.tool.window.tree.build.label=Active builds
|
||||||
build.tool.window.status.not-scanned=Build steps not yet scanned. Click the refresh button.
|
build.tool.window.status.not-scanned=Build steps not yet scanned. Click the refresh button.
|
||||||
build.tool.window.status.loading=Running zig build -l
|
build.tool.window.status.loading=Running zig build -l
|
||||||
build.tool.window.status.error.missing-build-zig=No build.zig file found
|
build.tool.window.status.error.missing-build-zig=No build.zig file found
|
||||||
build.tool.window.status.error.missing-toolchain=No zig toolchain configured
|
build.tool.window.status.error.missing-toolchain=No zig toolchain configured
|
||||||
build.tool.window.status.error.missing-zig-exe=Zig executable missing
|
build.tool.window.status.error.missing-zig-exe=Zig executable missing
|
||||||
build.tool.window.status.error.general=Error while running zig build -l
|
build.tool.window.status.error.general=Error while running zig build -l
|
||||||
|
build.tool.window.status.no-builds=No builds currently in progress
|
||||||
build.tool.window.status.timeout=zig build -l timed out after {0} seconds.
|
build.tool.window.status.timeout=zig build -l timed out after {0} seconds.
|
||||||
zig=Zig
|
zig=Zig
|
|
@ -1,7 +1,7 @@
|
||||||
pluginName=ZigBrains
|
pluginName=ZigBrains
|
||||||
pluginRepositoryUrl=https://github.com/FalsePattern/ZigBrains
|
pluginRepositoryUrl=https://github.com/FalsePattern/ZigBrains
|
||||||
|
|
||||||
pluginVersion=22.0.1
|
pluginVersion=23.0.0
|
||||||
|
|
||||||
pluginSinceBuild=242
|
pluginSinceBuild=242
|
||||||
pluginUntilBuild=242.*
|
pluginUntilBuild=242.*
|
||||||
|
@ -14,7 +14,7 @@ javaVersion=21
|
||||||
runIdeTarget=clion
|
runIdeTarget=clion
|
||||||
|
|
||||||
lsp4jVersion=0.21.1
|
lsp4jVersion=0.21.1
|
||||||
lsp4ijVersion=0.9.0
|
lsp4ijVersion=0.11.0
|
||||||
lsp4ijNightly=false
|
lsp4ijNightly=false
|
||||||
|
|
||||||
kotlin.stdlib.default.dependency=false
|
kotlin.stdlib.default.dependency=false
|
||||||
|
|
|
@ -339,7 +339,7 @@ class ZLSSettingsPanel(private val project: Project) : ZigProjectConfigurationPr
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val version = result.stdout
|
val version = result.stdout.trim()
|
||||||
withEDTContext(ModalityState.any()) {
|
withEDTContext(ModalityState.any()) {
|
||||||
zlsVersion.text = version
|
zlsVersion.text = version
|
||||||
zlsVersion.foreground = JBColor.foreground()
|
zlsVersion.foreground = JBColor.foreground()
|
||||||
|
|
Loading…
Add table
Reference in a new issue