Compare commits
94 commits
Author | SHA1 | Date | |
---|---|---|---|
0032dbf0f3 | |||
e7f0bb793a | |||
532292068f | |||
dd2104065c | |||
8389c66814 | |||
803b25d7fb | |||
b3ce6d62a7 | |||
f28cf77347 | |||
c524bbf899 | |||
a9c8a3a0d9 | |||
570eeac755 | |||
5efc28ae18 | |||
91747319aa | |||
8a7ca04168 | |||
5e4f2d5dfb | |||
4722613b4c | |||
53f4d1d330 | |||
270ac9e113 | |||
a4db054b59 | |||
bd47cb201f | |||
520167414a | |||
2d0ad8be44 | |||
9913ceb67e | |||
d5359e5816 | |||
8336d2bcc5 | |||
281ce0ed4e | |||
dcede7eb43 | |||
137977f691 | |||
ab20a57e9e | |||
1725b189a4 | |||
68b60e2c77 | |||
f7ea73ae45 | |||
3ceb61f2dd | |||
c7e33ea8de | |||
12e5ffdea1 | |||
54cd514249 | |||
a8f97172d6 | |||
b485c1e48c | |||
ee5a2463b9 | |||
9676b70821 | |||
9023026478 | |||
8bb4e8bef1 | |||
9541bb9752 | |||
2c500d40a5 | |||
e737058cb5 | |||
f53b0e3283 | |||
3de2f92bf8 | |||
dec12a8041 | |||
ac90dab503 | |||
9830b7ebc9 | |||
efa2f127a9 | |||
78c4751c53 | |||
1f79f484e5 | |||
6c14ad7113 | |||
ab5948a51b | |||
a950c932f5 | |||
46069b9a22 | |||
0a5a765eaf | |||
da38433eb3 | |||
7c1aa36d82 | |||
ceea101170 | |||
6dacf97583 | |||
b497f04e40 | |||
13266d112c | |||
74f1ceb880 | |||
af9ebee500 | |||
cc062b533e | |||
612841724c | |||
0cbce88d10 | |||
1ce31d786d | |||
8880f22840 | |||
94fe15409a | |||
868c37c567 | |||
e33c96d658 | |||
f02f50c1a7 | |||
9faa421ad9 | |||
009db1cb0f | |||
5c682f6bfa | |||
cc7d1393d6 | |||
7ee7e2f3d1 | |||
f138a2a4ba | |||
7c14ca2944 | |||
7c0fb4412d | |||
cb4ecb9ff6 | |||
7da3af7cd0 | |||
09ec2d79cc | |||
2f8bc57fe1 | |||
b2d4355488 | |||
1382aed48c | |||
404a554654 | |||
206cc1e437 | |||
c6f3a8202b | |||
4f9c324af1 | |||
a9b55ec830 |
155 changed files with 6278 additions and 1823 deletions
137
CHANGELOG.md
137
CHANGELOG.md
|
@ -17,6 +17,143 @@ Changelog structure reference:
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [25.2.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Debugger
|
||||
- Notify the user if zig run / zig test debugging starts, but a build.zig is present
|
||||
|
||||
### Changed
|
||||
|
||||
- Project
|
||||
- Line marker task suggestions for main/test now defer to Zig Build if build.zig file is detected.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Debugger
|
||||
- Compilation failures did not open the terminal properly and suppressed the error message
|
||||
|
||||
## [25.1.0]
|
||||
|
||||
### Added
|
||||
|
||||
- IDEA 2025.1 support
|
||||
|
||||
- LSP
|
||||
- Configurable inlay hints file size limit to reduce IDE lag
|
||||
|
||||
## [25.0.2]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Project
|
||||
- ZLS settings not scrollable in the language server list
|
||||
|
||||
## [25.0.1]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Project
|
||||
- Zig.iml file created in every project
|
||||
|
||||
### Changed
|
||||
|
||||
- Project
|
||||
- BREAKING MAJOR UPDATE: Fully reworked toolchain and language server management
|
||||
The configuration menu is now very similar to the intellij java toolchain management,
|
||||
with proper toolchain selection, detection, downloading, etc. This change will require
|
||||
you to re-configure your toolchains!
|
||||
- Zig external library root is now no longer shown if zig is not configured
|
||||
|
||||
## [24.0.1]
|
||||
|
||||
### 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
|
||||
|
||||
- LSP
|
||||
- IDE warning when renaming symbols
|
||||
|
||||
## [23.1.1]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Project
|
||||
- New project creation creates a blank ZLS config
|
||||
|
||||
## [23.1.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Project
|
||||
- Support running file main/tests with hotkey (default: ctrl+shift+f10)
|
||||
|
||||
### Changed
|
||||
|
||||
- Direnv
|
||||
- Centralized all direnv toggling into a single project-level option
|
||||
|
||||
## [23.0.2]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Zig
|
||||
- Documentation comment after regular comment was being highlighted as regular comment
|
||||
|
||||
## [23.0.1]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Project
|
||||
- mkfifo/bash for zig progress visualization is now detected more reliably (fixes error on macOS)
|
||||
- Deadlock when launching zig build tasks
|
||||
|
||||
## [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]
|
||||
|
||||
### Fixed
|
||||
|
||||
- LSP
|
||||
- Changing ZLS configs would not restart ZLS
|
||||
|
||||
- Project
|
||||
- Occasional "AWT events are not allowed inside write action" error coming from LSP
|
||||
- IllegalStateException coming from the standard library handler
|
||||
|
||||
## [22.0.0]
|
||||
|
||||
### Added
|
||||
|
|
8
LICENSE
8
LICENSE
|
@ -25,6 +25,11 @@ which are the property of the Zig Software Foundation.
|
|||
(https://github.com/ziglang/logo)
|
||||
These art assets are licensed under Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0).
|
||||
--------------------------------
|
||||
The art assets inside src/art/zls, and all copies of them, are derived from the Zig Language Server,
|
||||
which are the property of the zigtools organization.
|
||||
(https://github.com/zigtools/zls)
|
||||
These art assets are licensed under MIT license.
|
||||
--------------------------------
|
||||
Parts of the codebase are based on the intellij-zig plugin,
|
||||
developed by HTGAzureX1212 (https://github.com/HTGAzureX1212), licensed under the Apache 2.0 license.
|
||||
--------------------------------
|
||||
|
@ -37,4 +42,5 @@ All of the licenses listed here are available in the following files, bundled wi
|
|||
- licenses/CC_BY_SA_4.0.LICENSE
|
||||
- licenses/GPL3.LICENSE
|
||||
- licenses/INTELLIJ-RUST.LICENSE
|
||||
- licenses/LGPL3.LICENSE
|
||||
- licenses/LGPL3.LICENSE
|
||||
- licenses/ZLS.LICENSE
|
|
@ -15,6 +15,7 @@ through the built-in plugin browser:
|
|||
1. Go to `Settings -> Plugins`
|
||||
2. To the right of the `Installed` button at the top, click on the `...` dropdown menu, then select `Manage Plugin Repositories...`
|
||||
3. Click the add button, and then enter the ZigBrains updater URL, based on your IDE version:
|
||||
- `2025.1.*` or newer: https://falsepattern.com/zigbrains/updatePlugins-251.xml
|
||||
- `2024.3.*`: https://falsepattern.com/zigbrains/updatePlugins-243.xml
|
||||
- `2024.2.*`: https://falsepattern.com/zigbrains/updatePlugins-242.xml
|
||||
- `2024.1.*`: https://falsepattern.com/zigbrains/updatePlugins-241.xml
|
||||
|
@ -38,6 +39,7 @@ and might as well utilize the full semver string for extra information.
|
|||
## Supporters
|
||||
|
||||
- ### [Techatrix](https://github.com/Techatrix)
|
||||
- ### [nuxusr](https://github.com/nuxusr)
|
||||
- gree7
|
||||
- xceno
|
||||
- AnErrupTion
|
||||
|
@ -70,11 +72,7 @@ excellent example on how to write debugger support that doesn't depend on CLion.
|
|||
<!-- Plugin description -->
|
||||
Adds support for the Zig Language, utilizing the ZLS language server for advanced coding assistance.
|
||||
|
||||
## Quick setup guide for Zig and ZLS
|
||||
|
||||
1. Download the latest version of Zig from https://ziglang.org/download
|
||||
2. Download and compile the ZLS language server, available at https://github.com/zigtools/zls
|
||||
3. Go to `Settings` -> `Languages & Frameworks` -> `Zig`, and point the `Toolchain Location` and `ZLS path` to the correct places
|
||||
Before you can properly use the plugin, you need to select or download the Zig toolchain and language server in `Settings` -> `Languages & Frameworks` -> `Zig`.
|
||||
|
||||
## Debugging
|
||||
|
||||
|
@ -88,6 +86,7 @@ Debugging Zig code is supported in any native debugging capable IDE. The followi
|
|||
- RustRover (including the non-commercial free version too)
|
||||
- GoLand
|
||||
- PyCharm Professional
|
||||
- Android Studio
|
||||
|
||||
Additionally, in CLion, the plugin uses the C++ Toolchains for sourcing the debugger (this can be toggled off in the settings).
|
||||
|
||||
|
|
|
@ -6,15 +6,15 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
|
|||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
|
||||
|
||||
plugins {
|
||||
kotlin("jvm") version "2.0.21" apply false
|
||||
kotlin("plugin.serialization") version "2.0.21" apply false
|
||||
id("org.jetbrains.intellij.platform") version "2.2.1"
|
||||
kotlin("jvm") version "2.1.10" apply false
|
||||
kotlin("plugin.serialization") version "2.1.10" apply false
|
||||
id("org.jetbrains.intellij.platform") version "2.5.0"
|
||||
id("org.jetbrains.changelog") version "2.2.1"
|
||||
id("org.jetbrains.grammarkit") version "2022.3.2.2" apply false
|
||||
idea
|
||||
`maven-publish`
|
||||
}
|
||||
val publishVersions = listOf("241", "242", "243")
|
||||
val publishVersions = listOf("241", "242", "243", "251")
|
||||
val pluginVersionFull get() = "$pluginVersion-$pluginSinceBuild"
|
||||
val pluginVersion: String by project
|
||||
val pluginSinceBuild: String by project
|
||||
|
@ -60,7 +60,6 @@ tasks {
|
|||
allprojects {
|
||||
idea {
|
||||
module {
|
||||
isDownloadJavadoc = false
|
||||
isDownloadSources = true
|
||||
}
|
||||
}
|
||||
|
@ -206,6 +205,7 @@ publishVersions.forEach {
|
|||
archiveFile = distFile(it)
|
||||
token = providers.environmentVariable("IJ_PUBLISH_TOKEN")
|
||||
channels = if (pluginVersion.contains("-")) listOf("nightly") else listOf("default")
|
||||
setDependsOn(dependsOn.filter { if (it is TaskProvider<*>) it.name != "signPlugin" && it.name != "buildPlugin" else true })
|
||||
}
|
||||
tasks.named("publish").configure {
|
||||
dependsOn("jbpublish-$it")
|
||||
|
|
2
build.sh
2
build.sh
|
@ -23,7 +23,7 @@
|
|||
|
||||
set -e
|
||||
|
||||
declare -a branches=("master" "242" "241")
|
||||
declare -a branches=("master" "243" "242" "241")
|
||||
|
||||
DEFAULT_BRANCH="${branches[0]}"
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@ sourceSets["main"].resources.srcDir(genOutputDir)
|
|||
|
||||
tasks {
|
||||
register<Download>("downloadProps") {
|
||||
onlyIfModified(true)
|
||||
useETag(true)
|
||||
src("https://falsepattern.com/zigbrains/msvc.properties")
|
||||
dest(genOutputDir.map { it.file("msvc.properties") })
|
||||
}
|
||||
|
@ -27,7 +29,9 @@ dependencies {
|
|||
create(IntelliJPlatformType.CLion, clionVersion, useInstaller = useInstaller)
|
||||
bundledPlugins("com.intellij.clion", "com.intellij.cidr.base", "com.intellij.nativeDebug")
|
||||
}
|
||||
implementation(project(":core"))
|
||||
implementation(project(":core")) {
|
||||
isTransitive = false
|
||||
}
|
||||
implementation("org.eclipse.lsp4j:org.eclipse.lsp4j.debug:$lsp4jVersion") {
|
||||
exclude("org.eclipse.lsp4j", "org.eclipse.lsp4j")
|
||||
exclude("org.eclipse.lsp4j", "org.eclipse.lsp4j.jsonrpc")
|
||||
|
|
|
@ -24,7 +24,6 @@ package com.falsepattern.zigbrains.clion
|
|||
|
||||
import com.falsepattern.zigbrains.debugger.ZigDebuggerDriverConfigurationProvider
|
||||
import com.falsepattern.zigbrains.debugger.settings.ZigDebuggerSettings
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
|
@ -58,8 +57,9 @@ 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,
|
||||
CPPDebugger.Kind.CUSTOM_LLDB -> CLionLLDBDriverConfiguration(project, toolchain, isEmulateTerminal = emulateTerminal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import com.falsepattern.zigbrains.debugger.toolchain.*
|
|||
import com.falsepattern.zigbrains.debugger.win.MSVCDriverConfiguration
|
||||
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||
import com.falsepattern.zigbrains.zig.ZigLanguage
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.DoNotAskOption
|
||||
import com.intellij.openapi.ui.MessageDialogBuilder
|
||||
|
@ -85,7 +86,7 @@ private suspend fun availabilityCheck(project: Project, kind: DebuggerKind): Boo
|
|||
}
|
||||
|
||||
if (downloadDebugger) {
|
||||
val result = withEDTContext {
|
||||
val result = withEDTContext(ModalityState.any()) {
|
||||
service.downloadDebugger(project, kind)
|
||||
}
|
||||
if (result is ZigDebuggerToolchainService.DownloadResult.Ok) {
|
||||
|
@ -104,7 +105,7 @@ private suspend fun showDialog(project: Project, message: String, action: String
|
|||
}
|
||||
}
|
||||
|
||||
return withEDTContext {
|
||||
return withEDTContext(ModalityState.any()) {
|
||||
MessageDialogBuilder
|
||||
.okCancel(ZigDebugBundle.message("debugger.run.unavailable"), message)
|
||||
.yesText(action)
|
||||
|
|
|
@ -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)
|
|
@ -26,11 +26,7 @@ import com.falsepattern.zigbrains.project.run.ZigProcessHandler
|
|||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.execution.configurations.PtyCommandLine
|
||||
import com.intellij.execution.process.BaseProcessHandler
|
||||
import com.intellij.execution.process.ProcessAdapter
|
||||
import com.intellij.execution.process.ProcessEvent
|
||||
import com.intellij.execution.process.ProcessListener
|
||||
import com.intellij.execution.process.ProcessOutputType
|
||||
import com.intellij.execution.process.*
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.KeyWithDefaultValue
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
|
@ -45,9 +41,12 @@ import com.jetbrains.cidr.execution.debugger.memory.AddressRange
|
|||
import com.jetbrains.cidr.system.HostMachine
|
||||
import com.jetbrains.cidr.system.LocalHost
|
||||
import io.ktor.util.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.future.asCompletableFuture
|
||||
import kotlinx.coroutines.future.asDeferred
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.eclipse.lsp4j.debug.*
|
||||
import org.eclipse.lsp4j.debug.services.IDebugProtocolClient
|
||||
import org.eclipse.lsp4j.debug.services.IDebugProtocolServer
|
||||
|
@ -58,8 +57,7 @@ import java.io.ByteArrayOutputStream
|
|||
import java.io.File
|
||||
import java.io.OutputStream
|
||||
import java.io.PipedOutputStream
|
||||
import java.lang.Exception
|
||||
import java.util.TreeMap
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.math.min
|
||||
|
|
|
@ -24,14 +24,14 @@ package com.falsepattern.zigbrains.debugger.execution.binary
|
|||
|
||||
import com.falsepattern.zigbrains.debugger.ZigDebugBundle
|
||||
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.execution.runners.ExecutionEnvironment
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
class ZigProfileStateBinary(environment: ExecutionEnvironment, configuration: ZigExecConfigBinary) : ZigProfileState<ZigExecConfigBinary>(environment, configuration) {
|
||||
override suspend fun getCommandLine(toolchain: AbstractZigToolchain, debug: Boolean): GeneralCommandLine {
|
||||
override suspend fun getCommandLine(toolchain: ZigToolchain, debug: Boolean): GeneralCommandLine {
|
||||
val cli = GeneralCommandLine()
|
||||
val cfg = configuration
|
||||
cfg.workingDirectory.path?.let { cli.withWorkingDirectory(it) }
|
||||
|
|
|
@ -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.intellij.execution.ExecutionException
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.execution.process.ProcessEvent
|
||||
|
@ -30,6 +31,7 @@ import com.intellij.execution.process.ProcessHandler
|
|||
import com.intellij.execution.process.ProcessListener
|
||||
import com.intellij.execution.ui.ConsoleView
|
||||
import com.intellij.execution.ui.ConsoleViewContentType
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.util.progress.withProgressText
|
||||
import com.intellij.util.io.awaitExit
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
@ -39,17 +41,17 @@ import kotlinx.coroutines.withContext
|
|||
class PreLaunchProcessListener(val console: ConsoleView) : ProcessListener {
|
||||
var isBuildFailed: Boolean = false
|
||||
private set
|
||||
lateinit var processHandler: ProcessHandler
|
||||
lateinit var processHandler: ZigProcessHandler.IPCAware
|
||||
private set
|
||||
|
||||
@Throws(ExecutionException::class)
|
||||
suspend fun executeCommandLineWithHook(commandLine: GeneralCommandLine): Boolean {
|
||||
suspend fun executeCommandLineWithHook(project: Project, commandLine: GeneralCommandLine): Boolean {
|
||||
return withProgressText(commandLine.commandLineString) {
|
||||
val processHandler = ZigProcessHandler(commandLine)
|
||||
val processHandler = commandLine.startIPCAwareProcess(project)
|
||||
this@PreLaunchProcessListener.processHandler = processHandler
|
||||
hook(processHandler)
|
||||
processHandler.startNotify()
|
||||
withContext(Dispatchers.Default) {
|
||||
withContext(Dispatchers.IO) {
|
||||
processHandler.process.awaitExit()
|
||||
}
|
||||
runInterruptible {
|
||||
|
@ -66,10 +68,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
|
||||
|
|
|
@ -23,9 +23,10 @@
|
|||
package com.falsepattern.zigbrains.debugger.runner.base
|
||||
|
||||
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
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
|
||||
|
@ -33,13 +34,13 @@ import java.io.File
|
|||
|
||||
class ZigDebugEmitBinaryInstaller<ProfileState: ZigProfileState<*>>(
|
||||
private val profileState: ProfileState,
|
||||
private val toolchain: AbstractZigToolchain,
|
||||
private val toolchain: ZigToolchain,
|
||||
private val executableFile: File,
|
||||
private val exeArgs: List<String>
|
||||
): 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)
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
package com.falsepattern.zigbrains.debugger.runner.base
|
||||
|
||||
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.intellij.util.system.CpuArch
|
||||
import com.jetbrains.cidr.ArchitectureType
|
||||
import com.jetbrains.cidr.execution.RunParameters
|
||||
|
@ -31,7 +31,7 @@ import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
|
|||
|
||||
abstract class ZigDebugParametersBase<ProfileState: ZigProfileState<*>>(
|
||||
private val driverConfiguration: DebuggerDriverConfiguration,
|
||||
protected val toolchain: AbstractZigToolchain,
|
||||
protected val toolchain: ZigToolchain,
|
||||
protected val profileState: ProfileState
|
||||
): RunParameters() {
|
||||
override fun getDebuggerDriverConfiguration(): DebuggerDriverConfiguration {
|
||||
|
|
|
@ -24,9 +24,10 @@ package com.falsepattern.zigbrains.debugger.runner.base
|
|||
|
||||
import com.falsepattern.zigbrains.debugger.ZigDebugBundle
|
||||
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.platform.util.progress.withProgressText
|
||||
import com.intellij.util.containers.orNull
|
||||
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
|
||||
|
@ -36,10 +37,11 @@ import java.io.File
|
|||
import java.nio.file.Files
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.io.path.isExecutable
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
abstract class ZigDebugParametersEmitBinaryBase<ProfileState: ZigProfileState<*>>(
|
||||
driverConfiguration: DebuggerDriverConfiguration,
|
||||
toolchain: AbstractZigToolchain,
|
||||
toolchain: ZigToolchain,
|
||||
profileState: ProfileState,
|
||||
) : ZigDebugParametersBase<ProfileState>(driverConfiguration, toolchain, profileState), PreLaunchAware {
|
||||
@Volatile
|
||||
|
@ -49,13 +51,14 @@ abstract class ZigDebugParametersEmitBinaryBase<ProfileState: ZigProfileState<*>
|
|||
@Throws(ExecutionException::class)
|
||||
private suspend fun compileExe(listener: PreLaunchProcessListener): File {
|
||||
val commandLine = profileState.getCommandLine(toolchain, true)
|
||||
val cliString = commandLine.getCommandLineString(commandLine.exePath.toNioPathOrNull()?.fileName?.pathString)
|
||||
val tmpDir = FileUtil.createTempDirectory("zigbrains_debug", "", true).toPath()
|
||||
|
||||
val exe = tmpDir.resolve("executable")
|
||||
commandLine.addParameters("-femit-bin=${exe.absolutePathString()}")
|
||||
|
||||
if (listener.executeCommandLineWithHook(commandLine))
|
||||
throw ExecutionException(ZigDebugBundle.message("debug.base.compile.failed.generic"))
|
||||
if (listener.executeCommandLineWithHook(profileState.environment.project, commandLine))
|
||||
throw ExecutionException(ZigDebugBundle.message("debug.base.compile.failed.generic", cliString))
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
Files.list(tmpDir).use { stream ->
|
||||
|
|
|
@ -24,9 +24,10 @@ package com.falsepattern.zigbrains.debugger.runner.base
|
|||
|
||||
import com.falsepattern.zigbrains.debugbridge.ZigDebuggerDriverConfigurationProviderBase
|
||||
import com.falsepattern.zigbrains.debugger.ZigLocalDebugProcess
|
||||
import com.falsepattern.zigbrains.debugger.runner.build.ZigDebugRunnerBuild
|
||||
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
|
||||
import com.falsepattern.zigbrains.project.run.ZigProgramRunner
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.shared.coroutine.runInterruptibleEDT
|
||||
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||
import com.intellij.execution.DefaultExecutionResult
|
||||
|
@ -40,6 +41,8 @@ import com.intellij.execution.runners.RunContentBuilder
|
|||
import com.intellij.execution.ui.ConsoleView
|
||||
import com.intellij.execution.ui.ConsoleViewContentType
|
||||
import com.intellij.execution.ui.RunContentDescriptor
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.project.guessProjectDir
|
||||
import com.intellij.platform.util.progress.reportProgress
|
||||
import com.intellij.xdebugger.XDebugProcess
|
||||
import com.intellij.xdebugger.XDebugProcessStarter
|
||||
|
@ -51,13 +54,13 @@ abstract class ZigDebugRunnerBase<ProfileState : ZigProfileState<*>> : ZigProgra
|
|||
@Throws(ExecutionException::class)
|
||||
override suspend fun execute(
|
||||
state: ProfileState,
|
||||
toolchain: AbstractZigToolchain,
|
||||
toolchain: ZigToolchain,
|
||||
environment: ExecutionEnvironment
|
||||
): RunContentDescriptor? {
|
||||
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
|
||||
|
@ -66,7 +69,7 @@ abstract class ZigDebugRunnerBase<ProfileState : ZigProfileState<*>> : ZigProgra
|
|||
@Throws(ExecutionException::class)
|
||||
private suspend fun executeWithDriver(
|
||||
state: ProfileState,
|
||||
toolchain: AbstractZigToolchain,
|
||||
toolchain: ZigToolchain,
|
||||
environment: ExecutionEnvironment,
|
||||
debuggerDriver: DebuggerDriverConfiguration
|
||||
): RunContentDescriptor? {
|
||||
|
@ -81,22 +84,25 @@ abstract class ZigDebugRunnerBase<ProfileState : ZigProfileState<*>> : ZigProgra
|
|||
}
|
||||
} catch (e: ExecutionException) {
|
||||
console.print("\n", ConsoleViewContentType.ERROR_OUTPUT)
|
||||
e.message?.let { listener.console.print(it, ConsoleViewContentType.SYSTEM_OUTPUT) }
|
||||
e.message?.let { listener.console.print(it, ConsoleViewContentType.ERROR_OUTPUT) }
|
||||
if (this !is ZigDebugRunnerBuild && environment.project.guessProjectDir()?.children?.any { it.name == "build.zig" } == true) {
|
||||
console.print("\n Warning: build.zig file detected in project.\n Did you want to use a Zig Build task instead?", ConsoleViewContentType.ERROR_OUTPUT)
|
||||
}
|
||||
}
|
||||
if (listener.isBuildFailed) {
|
||||
val executionResult = DefaultExecutionResult(console, listener.processHandler)
|
||||
return@reportProgress withEDTContext {
|
||||
val executionResult = DefaultExecutionResult(console, listener.processHandler.unwrap())
|
||||
return@reportProgress withEDTContext(ModalityState.any()) {
|
||||
val runContentBuilder = RunContentBuilder(executionResult, environment)
|
||||
runContentBuilder.showRunContent(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
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()
|
||||
|
@ -111,7 +117,7 @@ abstract class ZigDebugRunnerBase<ProfileState : ZigProfileState<*>> : ZigProgra
|
|||
protected abstract fun getDebugParameters(
|
||||
state: ProfileState,
|
||||
debuggerDriver: DebuggerDriverConfiguration,
|
||||
toolchain: AbstractZigToolchain
|
||||
toolchain: ZigToolchain
|
||||
): ZigDebugParametersBase<ProfileState>
|
||||
|
||||
private class SharedConsoleBuilder(private val console: ConsoleView) : TextConsoleBuilder() {
|
||||
|
|
|
@ -26,13 +26,13 @@ import com.falsepattern.zigbrains.debugger.ZigDebugBundle
|
|||
import com.falsepattern.zigbrains.debugger.execution.binary.ZigProfileStateBinary
|
||||
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugEmitBinaryInstaller
|
||||
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.jetbrains.cidr.execution.Installer
|
||||
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
|
||||
|
||||
|
||||
class ZigDebugParametersBinary @Throws(ExecutionException::class) constructor(driverConfiguration: DebuggerDriverConfiguration, toolchain: AbstractZigToolchain, profileState: ZigProfileStateBinary) :
|
||||
class ZigDebugParametersBinary @Throws(ExecutionException::class) constructor(driverConfiguration: DebuggerDriverConfiguration, toolchain: ZigToolchain, profileState: ZigProfileStateBinary) :
|
||||
ZigDebugParametersBase<ZigProfileStateBinary>(driverConfiguration, toolchain, profileState) {
|
||||
private val executableFile = profileState.configuration.exePath.path?.toFile() ?: throw ExecutionException(ZigDebugBundle.message("exception.missing-exe-path"))
|
||||
override fun getInstaller(): Installer {
|
||||
|
|
|
@ -27,8 +27,8 @@ import com.falsepattern.zigbrains.debugger.execution.binary.ZigProfileStateBinar
|
|||
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase
|
||||
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugRunnerBase
|
||||
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
|
||||
import com.intellij.execution.configurations.RunProfile
|
||||
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
|
||||
|
||||
|
@ -36,7 +36,7 @@ class ZigDebugRunnerBinary: ZigDebugRunnerBase<ZigProfileStateBinary>() {
|
|||
override fun getDebugParameters(
|
||||
state: ZigProfileStateBinary,
|
||||
debuggerDriver: DebuggerDriverConfiguration,
|
||||
toolchain: AbstractZigToolchain
|
||||
toolchain: ZigToolchain
|
||||
): ZigDebugParametersBase<ZigProfileStateBinary> {
|
||||
return ZigDebugParametersBinary(debuggerDriver, LocalZigToolchain.ensureLocal(toolchain), state)
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ import com.falsepattern.zigbrains.debugger.runner.base.PreLaunchProcessListener
|
|||
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugEmitBinaryInstaller
|
||||
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersBase
|
||||
import com.falsepattern.zigbrains.project.execution.build.ZigProfileStateBuild
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.platform.util.progress.withProgressText
|
||||
|
@ -46,7 +46,7 @@ import kotlin.io.path.isRegularFile
|
|||
|
||||
class ZigDebugParametersBuild(
|
||||
driverConfiguration: DebuggerDriverConfiguration,
|
||||
toolchain: AbstractZigToolchain,
|
||||
toolchain: ZigToolchain,
|
||||
profileState: ZigProfileStateBuild
|
||||
) : ZigDebugParametersBase<ZigProfileStateBuild>(driverConfiguration, toolchain, profileState), PreLaunchAware {
|
||||
@Volatile
|
||||
|
@ -61,8 +61,8 @@ class ZigDebugParametersBuild(
|
|||
withProgressText("Building zig project") {
|
||||
withContext(Dispatchers.IO) {
|
||||
val commandLine = profileState.getCommandLine(toolchain, true)
|
||||
if (listener.executeCommandLineWithHook(commandLine))
|
||||
throw ExecutionException(ZigDebugBundle.message("debug.build.compile.failed.generic"))
|
||||
if (listener.executeCommandLineWithHook(profileState.environment.project, commandLine))
|
||||
return@withContext
|
||||
val cfg = profileState.configuration
|
||||
val workingDir = cfg.workingDirectory.path
|
||||
val exe = profileState.configuration.exePath.path ?: run {
|
||||
|
|
|
@ -27,8 +27,8 @@ import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugRunnerBase
|
|||
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
|
||||
import com.falsepattern.zigbrains.project.execution.build.ZigExecConfigBuild
|
||||
import com.falsepattern.zigbrains.project.execution.build.ZigProfileStateBuild
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
|
||||
import com.intellij.execution.configurations.RunProfile
|
||||
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
|
||||
|
||||
|
@ -36,7 +36,7 @@ class ZigDebugRunnerBuild: ZigDebugRunnerBase<ZigProfileStateBuild>() {
|
|||
override fun getDebugParameters(
|
||||
state: ZigProfileStateBuild,
|
||||
debuggerDriver: DebuggerDriverConfiguration,
|
||||
toolchain: AbstractZigToolchain
|
||||
toolchain: ZigToolchain
|
||||
): ZigDebugParametersBase<ZigProfileStateBuild> {
|
||||
return ZigDebugParametersBuild(debuggerDriver, LocalZigToolchain.ensureLocal(toolchain), state)
|
||||
}
|
||||
|
|
|
@ -25,11 +25,11 @@ package com.falsepattern.zigbrains.debugger.runner.run
|
|||
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugEmitBinaryInstaller
|
||||
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersEmitBinaryBase
|
||||
import com.falsepattern.zigbrains.project.execution.run.ZigProfileStateRun
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.jetbrains.cidr.execution.Installer
|
||||
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
|
||||
|
||||
class ZigDebugParametersRun(driverConfiguration: DebuggerDriverConfiguration, toolchain: AbstractZigToolchain, profileState: ZigProfileStateRun) :
|
||||
class ZigDebugParametersRun(driverConfiguration: DebuggerDriverConfiguration, toolchain: ZigToolchain, profileState: ZigProfileStateRun) :
|
||||
ZigDebugParametersEmitBinaryBase<ZigProfileStateRun>(driverConfiguration, toolchain, profileState) {
|
||||
override fun getInstaller(): Installer {
|
||||
return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, profileState.configuration.exeArgs.argsSplit())
|
||||
|
|
|
@ -27,8 +27,8 @@ import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugRunnerBase
|
|||
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
|
||||
import com.falsepattern.zigbrains.project.execution.run.ZigExecConfigRun
|
||||
import com.falsepattern.zigbrains.project.execution.run.ZigProfileStateRun
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
|
||||
import com.intellij.execution.configurations.RunProfile
|
||||
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
|
||||
|
||||
|
@ -36,7 +36,7 @@ class ZigDebugRunnerRun: ZigDebugRunnerBase<ZigProfileStateRun>() {
|
|||
override fun getDebugParameters(
|
||||
state: ZigProfileStateRun,
|
||||
debuggerDriver: DebuggerDriverConfiguration,
|
||||
toolchain: AbstractZigToolchain
|
||||
toolchain: ZigToolchain
|
||||
): ZigDebugParametersBase<ZigProfileStateRun> {
|
||||
return ZigDebugParametersRun(debuggerDriver, LocalZigToolchain.ensureLocal(toolchain), state)
|
||||
}
|
||||
|
|
|
@ -25,11 +25,11 @@ package com.falsepattern.zigbrains.debugger.runner.test
|
|||
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugEmitBinaryInstaller
|
||||
import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugParametersEmitBinaryBase
|
||||
import com.falsepattern.zigbrains.project.execution.test.ZigProfileStateTest
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.jetbrains.cidr.execution.Installer
|
||||
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
|
||||
|
||||
class ZigDebugParametersTest(driverConfiguration: DebuggerDriverConfiguration, toolchain: AbstractZigToolchain, profileState: ZigProfileStateTest) :
|
||||
class ZigDebugParametersTest(driverConfiguration: DebuggerDriverConfiguration, toolchain: ZigToolchain, profileState: ZigProfileStateTest) :
|
||||
ZigDebugParametersEmitBinaryBase<ZigProfileStateTest>(driverConfiguration, toolchain, profileState) {
|
||||
override fun getInstaller(): Installer {
|
||||
return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, listOf())
|
||||
|
|
|
@ -27,8 +27,8 @@ import com.falsepattern.zigbrains.debugger.runner.base.ZigDebugRunnerBase
|
|||
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
|
||||
import com.falsepattern.zigbrains.project.execution.test.ZigExecConfigTest
|
||||
import com.falsepattern.zigbrains.project.execution.test.ZigProfileStateTest
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
|
||||
import com.intellij.execution.configurations.RunProfile
|
||||
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
|
||||
|
||||
|
@ -36,7 +36,7 @@ class ZigDebugRunnerTest: ZigDebugRunnerBase<ZigProfileStateTest>() {
|
|||
override fun getDebugParameters(
|
||||
state: ZigProfileStateTest,
|
||||
debuggerDriver: DebuggerDriverConfiguration,
|
||||
toolchain: AbstractZigToolchain
|
||||
toolchain: ZigToolchain
|
||||
): ZigDebugParametersBase<ZigProfileStateTest> {
|
||||
return ZigDebugParametersTest(debuggerDriver, LocalZigToolchain.ensureLocal(toolchain), state)
|
||||
}
|
||||
|
|
|
@ -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.defaultModalityState()) {
|
||||
withModalProgress(ModalTaskOwner.component(debuggerKindComboBox), "Downloading debugger", TaskCancellation.cancellable()) {
|
||||
downloadDebugger()
|
||||
}
|
||||
|
@ -96,7 +97,7 @@ class ZigDebuggerToolchainConfigurableUi : ZigDebuggerUiComponent {
|
|||
}
|
||||
.applyToComponent {
|
||||
whenItemSelected(null) {
|
||||
zigCoroutineScope.launchWithEDT {
|
||||
zigCoroutineScope.launchWithEDT(ModalityState.defaultModalityState()) {
|
||||
this@ZigDebuggerToolchainConfigurableUi.update()
|
||||
}
|
||||
}
|
||||
|
@ -111,7 +112,7 @@ class ZigDebuggerToolchainConfigurableUi : ZigDebuggerUiComponent {
|
|||
cell(useClion)
|
||||
}
|
||||
}
|
||||
zigCoroutineScope.launchWithEDT {
|
||||
zigCoroutineScope.launchWithEDT(ModalityState.defaultModalityState()) {
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,10 +27,8 @@ import com.falsepattern.zigbrains.debugger.settings.MSVCDownloadPermission
|
|||
import com.falsepattern.zigbrains.debugger.settings.ZigDebuggerSettings
|
||||
import com.falsepattern.zigbrains.debugger.toolchain.ZigDebuggerToolchainService.Companion.downloadPath
|
||||
import com.falsepattern.zigbrains.shared.coroutine.withCurrentEDTModalityContext
|
||||
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||
import com.intellij.notification.Notification
|
||||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.progress.coroutineToIndicator
|
||||
import com.intellij.openapi.ui.DialogBuilder
|
||||
import com.intellij.platform.util.progress.withProgressText
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
package com.falsepattern.zigbrains.debugger.toolchain
|
||||
|
||||
import com.falsepattern.zigbrains.debugger.ZigDebugBundle
|
||||
import com.falsepattern.zigbrains.shared.Unarchiver
|
||||
import com.intellij.notification.Notification
|
||||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.openapi.application.PathManager
|
||||
|
@ -34,22 +35,20 @@ import com.intellij.openapi.project.Project
|
|||
import com.intellij.openapi.ui.DialogBuilder
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.platform.util.progress.reportSequentialProgress
|
||||
import com.intellij.ui.BrowserHyperlinkListener
|
||||
import com.intellij.ui.HyperlinkLabel
|
||||
import com.intellij.ui.components.JBPanel
|
||||
import com.intellij.util.application
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.intellij.util.download.DownloadableFileService
|
||||
import com.intellij.util.io.Decompressor
|
||||
import com.intellij.util.system.CpuArch
|
||||
import com.intellij.util.system.OS
|
||||
import com.jetbrains.cidr.execution.debugger.CidrDebuggerPathManager
|
||||
import com.jetbrains.cidr.execution.debugger.backend.bin.UrlProvider
|
||||
import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBDriverConfiguration
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.URL
|
||||
import java.nio.file.Path
|
||||
|
@ -169,7 +168,9 @@ class ZigDebuggerToolchainService {
|
|||
}
|
||||
|
||||
try {
|
||||
downloadAndUnArchive(baseDir, downloadableBinaries)
|
||||
withContext(Dispatchers.IO) {
|
||||
downloadAndUnArchive(baseDir, downloadableBinaries)
|
||||
}
|
||||
return DownloadResult.Ok(baseDir)
|
||||
} catch (e: IOException) {
|
||||
//TODO logging
|
||||
|
@ -208,34 +209,40 @@ class ZigDebuggerToolchainService {
|
|||
@Throws(IOException::class)
|
||||
@RequiresEdt
|
||||
private suspend fun downloadAndUnArchive(baseDir: Path, binariesToDownload: List<DownloadableDebuggerBinary>) {
|
||||
val service = DownloadableFileService.getInstance()
|
||||
reportSequentialProgress { reporter ->
|
||||
val service = DownloadableFileService.getInstance()
|
||||
|
||||
val downloadDir = baseDir.toFile()
|
||||
downloadDir.deleteRecursively()
|
||||
val downloadDir = baseDir.toFile()
|
||||
downloadDir.deleteRecursively()
|
||||
|
||||
val descriptions = binariesToDownload.map {
|
||||
service.createFileDescription(it.url, fileName(it.url))
|
||||
}
|
||||
|
||||
val downloader = service.createDownloader(descriptions, "Debugger downloading")
|
||||
val downloadDirectory = downloadPath().toFile()
|
||||
val downloadResults = withContext(Dispatchers.IO) {
|
||||
coroutineToIndicator {
|
||||
downloader.download(downloadDirectory)
|
||||
val descriptions = binariesToDownload.map {
|
||||
service.createFileDescription(it.url, fileName(it.url))
|
||||
}
|
||||
}
|
||||
val versions = Properties()
|
||||
for (result in downloadResults) {
|
||||
val downloadUrl = result.second.downloadUrl
|
||||
val binaryToDownload = binariesToDownload.first { it.url == downloadUrl }
|
||||
val propertyName = binaryToDownload.propertyName
|
||||
val archiveFile = result.first
|
||||
Unarchiver.unarchive(archiveFile.toPath(), baseDir, binaryToDownload.prefix)
|
||||
archiveFile.delete()
|
||||
versions[propertyName] = binaryToDownload.version
|
||||
}
|
||||
|
||||
saveVersionsFile(baseDir, versions)
|
||||
val downloader = service.createDownloader(descriptions, "Debugger downloading")
|
||||
val downloadDirectory = downloadPath().toFile()
|
||||
val downloadResults = reporter.sizedStep(100) {
|
||||
coroutineToIndicator {
|
||||
downloader.download(downloadDirectory)
|
||||
}
|
||||
}
|
||||
val versions = Properties()
|
||||
for (result in downloadResults) {
|
||||
val downloadUrl = result.second.downloadUrl
|
||||
val binaryToDownload = binariesToDownload.first { it.url == downloadUrl }
|
||||
val propertyName = binaryToDownload.propertyName
|
||||
val archiveFile = result.first
|
||||
reporter.indeterminateStep {
|
||||
coroutineToIndicator {
|
||||
Unarchiver.unarchive(archiveFile.toPath(), baseDir, binaryToDownload.prefix)
|
||||
}
|
||||
}
|
||||
archiveFile.delete()
|
||||
versions[propertyName] = binaryToDownload.version
|
||||
}
|
||||
|
||||
saveVersionsFile(baseDir, versions)
|
||||
}
|
||||
}
|
||||
|
||||
private fun lldbUrls(): Pair<URL, URL>? {
|
||||
|
@ -330,38 +337,6 @@ class ZigDebuggerToolchainService {
|
|||
}
|
||||
}
|
||||
|
||||
private enum class Unarchiver {
|
||||
ZIP {
|
||||
override val extension = "zip"
|
||||
override fun createDecompressor(file: Path) = Decompressor.Zip(file)
|
||||
},
|
||||
TAR {
|
||||
override val extension = "tar.gz"
|
||||
override fun createDecompressor(file: Path) = Decompressor.Tar(file)
|
||||
},
|
||||
VSIX {
|
||||
override val extension = "vsix"
|
||||
override fun createDecompressor(file: Path) = Decompressor.Zip(file)
|
||||
};
|
||||
|
||||
protected abstract val extension: String
|
||||
protected abstract fun createDecompressor(file: Path): Decompressor
|
||||
|
||||
companion object {
|
||||
@Throws(IOException::class)
|
||||
suspend fun unarchive(archivePath: Path, dst: Path, prefix: String? = null) {
|
||||
runInterruptible {
|
||||
val unarchiver = entries.find { archivePath.name.endsWith(it.extension) } ?: error("Unexpected archive type: $archivePath")
|
||||
val dec = unarchiver.createDecompressor(archivePath)
|
||||
if (prefix != null) {
|
||||
dec.removePrefixPath(prefix)
|
||||
}
|
||||
dec.extract(dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class DownloadResult {
|
||||
class Ok(val baseDir: Path): DownloadResult()
|
||||
data object NoUrls: DownloadResult()
|
||||
|
|
|
@ -39,9 +39,8 @@ import org.eclipse.lsp4j.jsonrpc.debug.messages.DebugResponseMessage
|
|||
import org.eclipse.lsp4j.jsonrpc.messages.Message
|
||||
import org.eclipse.lsp4j.jsonrpc.services.JsonRequest
|
||||
import java.io.InputStream
|
||||
import java.lang.RuntimeException
|
||||
import java.security.MessageDigest
|
||||
import java.util.Base64
|
||||
import java.util.*
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.zip.Inflater
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ debugger.run.unavailable.reason.download.button=Download
|
|||
debugger.run.unavailable.reason.update=Debugger is outdated
|
||||
debugger.run.unavailable.reason.update.button=Update
|
||||
debug.build.compile.failed.boilerplate={0}\nPlease edit this intellij build configuration and specify the path of the executable created by "zig build" directly!
|
||||
debug.base.compile.failed.generic=Failed to compile executable
|
||||
debug.base.compile.failed.generic=Failed to compile executable with command: {0}
|
||||
debug.base.compile.failed.no-exe=Failed to find compiled binary
|
||||
debug.build.compile.failed.multiple-exe=Multiple compiled binaries found
|
||||
debug.build.compile.failed.no-workdir=Cannot find working directory to run debugged executable
|
||||
|
|
|
@ -9,12 +9,15 @@ plugins {
|
|||
|
||||
val ideaCommunityVersion: String by project
|
||||
val useInstaller = property("useInstaller").toString().toBoolean()
|
||||
val serializationVersion: String by project
|
||||
|
||||
dependencies {
|
||||
intellijPlatform {
|
||||
create(IntelliJPlatformType.IntellijIdeaCommunity, ideaCommunityVersion, useInstaller = useInstaller)
|
||||
}
|
||||
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3")
|
||||
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:$serializationVersion") {
|
||||
isTransitive = false
|
||||
}
|
||||
}
|
||||
|
||||
//region grammars
|
||||
|
|
|
@ -36,8 +36,10 @@ import static com.falsepattern.zigbrains.zig.psi.ZigTypes.*;
|
|||
%type IElementType
|
||||
%unicode
|
||||
|
||||
CRLF=\R
|
||||
WHITE_SPACE=[\s]+
|
||||
WHITE_SPACE=\s+
|
||||
|
||||
// visual studio parity
|
||||
LF=\r\n?|[\n\u0085\u2028\u2029]
|
||||
|
||||
bin=[01]
|
||||
bin_="_"? {bin}
|
||||
|
@ -54,13 +56,13 @@ dec_int={dec} {dec_}*
|
|||
hex_int={hex} {hex_}*
|
||||
|
||||
char_char= \\ .
|
||||
| [^\'\n]
|
||||
| [^\'\r\n\u0085\u2028\u2029]
|
||||
|
||||
string_char= \\ .
|
||||
| [^\"\n]
|
||||
| [^\"\r\n\u0085\u2028\u2029]
|
||||
|
||||
all_nl_wrap=[^\n]* [ \n]*
|
||||
all_no_nl=[^\n]+
|
||||
nl_wrap={LF} (\s|{LF})*
|
||||
all_no_nl=[^\r\n\u0085\u2028\u2029]+
|
||||
|
||||
|
||||
FLOAT= "0x" {hex_int} "." {hex_int} ([pP] [-+]? {dec_int})?
|
||||
|
@ -84,30 +86,35 @@ BUILTINIDENTIFIER="@"[A-Za-z_][A-Za-z0-9_]*
|
|||
%state UNT_SQUOT
|
||||
%state UNT_DQUOT
|
||||
|
||||
%state CDOC_CMT
|
||||
%state DOC_CMT
|
||||
%state LINE_CMT
|
||||
%state CMT_LINE
|
||||
%state CMT_DOC
|
||||
%state CMT_CDOC
|
||||
%%
|
||||
|
||||
//Comments
|
||||
|
||||
<YYINITIAL> "//!" { yybegin(CDOC_CMT); }
|
||||
<CDOC_CMT> {all_nl_wrap} "//!" { }
|
||||
<CDOC_CMT> {all_no_nl} { }
|
||||
<CDOC_CMT> \n { yybegin(YYINITIAL); return CONTAINER_DOC_COMMENT; }
|
||||
<CDOC_CMT> <<EOF>> { yybegin(YYINITIAL); return CONTAINER_DOC_COMMENT; }
|
||||
<YYINITIAL> "//!" { yybegin(CMT_CDOC); }
|
||||
<YYINITIAL> "////" { yybegin(CMT_LINE); }
|
||||
<YYINITIAL> "///" { yybegin(CMT_DOC); }
|
||||
<YYINITIAL> "//" { yybegin(CMT_LINE); }
|
||||
|
||||
<YYINITIAL> "///" { yybegin(DOC_CMT); }
|
||||
<DOC_CMT> {all_nl_wrap} "///" { }
|
||||
<DOC_CMT> {all_no_nl} { }
|
||||
<DOC_CMT> \n { yybegin(YYINITIAL); return DOC_COMMENT; }
|
||||
<DOC_CMT> <<EOF>> { yybegin(YYINITIAL); return DOC_COMMENT; }
|
||||
<CMT_LINE> {all_no_nl} { }
|
||||
<CMT_LINE> {nl_wrap} "////" { }
|
||||
<CMT_LINE> {nl_wrap} "///" { yypushback(yylength()); yybegin(YYINITIAL); return LINE_COMMENT; }
|
||||
<CMT_LINE> {nl_wrap} "//" { }
|
||||
<CMT_LINE> {LF} { yybegin(YYINITIAL); return LINE_COMMENT; }
|
||||
<CMT_LINE> <<EOF>> { yybegin(YYINITIAL); return LINE_COMMENT; }
|
||||
|
||||
<YYINITIAL> "//" { yybegin(LINE_CMT); }
|
||||
<LINE_CMT> {all_nl_wrap} "//" { }
|
||||
<LINE_CMT> {all_no_nl} { }
|
||||
<LINE_CMT> \n { yybegin(YYINITIAL); return LINE_COMMENT; }
|
||||
<LINE_CMT> <<EOF>> { yybegin(YYINITIAL); return LINE_COMMENT; }
|
||||
<CMT_DOC> {all_no_nl} { }
|
||||
<CMT_DOC> {nl_wrap} "////" { yypushback(yylength()); yybegin(YYINITIAL); return DOC_COMMENT; }
|
||||
<CMT_DOC> {nl_wrap} "///" { }
|
||||
<CMT_DOC> {LF} { yybegin(YYINITIAL); return DOC_COMMENT; }
|
||||
<CMT_DOC> <<EOF>> { yybegin(YYINITIAL); return DOC_COMMENT; }
|
||||
|
||||
<CMT_CDOC> {all_no_nl} { }
|
||||
<CMT_CDOC> {nl_wrap} "//!" { }
|
||||
<CMT_CDOC> {LF} { yybegin(YYINITIAL); return CONTAINER_DOC_COMMENT; }
|
||||
<CMT_CDOC> <<EOF>> { yybegin(YYINITIAL); return CONTAINER_DOC_COMMENT; }
|
||||
|
||||
//Symbols
|
||||
<YYINITIAL> "&" { return AMPERSAND; }
|
||||
|
@ -227,24 +234,31 @@ BUILTINIDENTIFIER="@"[A-Za-z_][A-Za-z0-9_]*
|
|||
<YYINITIAL> "volatile" { return KEYWORD_VOLATILE; }
|
||||
<YYINITIAL> "while" { return KEYWORD_WHILE; }
|
||||
|
||||
//Strings
|
||||
|
||||
<YYINITIAL> "'" { yybegin(CHAR_LIT); }
|
||||
<CHAR_LIT> {char_char}*"'" { yybegin(YYINITIAL); return CHAR_LITERAL; }
|
||||
<CHAR_LIT> <<EOF>> { yybegin(YYINITIAL); return BAD_SQUOT; }
|
||||
<CHAR_LIT> [^] { yypushback(1); yybegin(UNT_SQUOT); }
|
||||
|
||||
<YYINITIAL> {FLOAT} { return FLOAT; }
|
||||
<YYINITIAL> {INTEGER} { return INTEGER; }
|
||||
|
||||
<YYINITIAL> "\"" { yybegin(STR_LIT); }
|
||||
<STR_LIT> {string_char}*"\"" { yybegin(YYINITIAL); return STRING_LITERAL_SINGLE; }
|
||||
<STR_LIT> <<EOF>> { yybegin(YYINITIAL); return BAD_DQUOT; }
|
||||
<STR_LIT> [^] { yypushback(1); yybegin(UNT_DQUOT); }
|
||||
|
||||
<YYINITIAL> "\\\\" { yybegin(STR_MULT_LINE); }
|
||||
<STR_MULT_LINE> {all_nl_wrap} "\\\\" { }
|
||||
<STR_MULT_LINE> {all_no_nl} { }
|
||||
<STR_MULT_LINE> \n { yybegin(YYINITIAL); return STRING_LITERAL_MULTI; }
|
||||
<STR_MULT_LINE> {all_no_nl} { }
|
||||
<STR_MULT_LINE> {nl_wrap} "\\\\" { }
|
||||
<STR_MULT_LINE> {LF} { yybegin(YYINITIAL); return STRING_LITERAL_MULTI; }
|
||||
<STR_MULT_LINE> <<EOF>> { yybegin(YYINITIAL); return STRING_LITERAL_MULTI; }
|
||||
|
||||
//Numbers
|
||||
|
||||
<YYINITIAL> {FLOAT} { return FLOAT; }
|
||||
<YYINITIAL> {INTEGER} { return INTEGER; }
|
||||
|
||||
//Identifiers
|
||||
|
||||
<YYINITIAL> {IDENTIFIER_PLAIN} { return IDENTIFIER; }
|
||||
<YYINITIAL> "@\"" { yybegin(ID_QUOT); }
|
||||
<ID_QUOT> {string_char}*"\"" { yybegin(YYINITIAL); return IDENTIFIER; }
|
||||
|
@ -253,13 +267,17 @@ BUILTINIDENTIFIER="@"[A-Za-z_][A-Za-z0-9_]*
|
|||
|
||||
<YYINITIAL> {BUILTINIDENTIFIER} { return BUILTINIDENTIFIER; }
|
||||
|
||||
//Error handling
|
||||
|
||||
<UNT_SQUOT> <<EOF>> { yybegin(YYINITIAL); return BAD_SQUOT; }
|
||||
<UNT_SQUOT> {CRLF} { yybegin(YYINITIAL); return BAD_SQUOT; }
|
||||
<UNT_SQUOT> {LF} { yybegin(YYINITIAL); return BAD_SQUOT; }
|
||||
<UNT_SQUOT> {all_no_nl} { }
|
||||
<UNT_DQUOT> <<EOF>> { yybegin(YYINITIAL); return BAD_DQUOT; }
|
||||
<UNT_DQUOT> {CRLF} { yybegin(YYINITIAL); return BAD_DQUOT; }
|
||||
<UNT_DQUOT> {LF} { yybegin(YYINITIAL); return BAD_DQUOT; }
|
||||
<UNT_DQUOT> {all_no_nl} { }
|
||||
|
||||
//Misc
|
||||
|
||||
<YYINITIAL> {WHITE_SPACE} { return WHITE_SPACE; }
|
||||
|
||||
[^] { return BAD_CHARACTER; }
|
||||
|
|
|
@ -36,8 +36,10 @@ import static com.falsepattern.zigbrains.zon.psi.ZonTypes.*;
|
|||
%type IElementType
|
||||
%unicode
|
||||
|
||||
CRLF=\R
|
||||
WHITE_SPACE=[\s]+
|
||||
WHITE_SPACE=\s+
|
||||
|
||||
// visual studio parity
|
||||
LF=\r\n?|[\n\u0085\u2028\u2029]
|
||||
|
||||
bin=[01]
|
||||
bin_="_"? {bin}
|
||||
|
@ -54,13 +56,13 @@ dec_int={dec} {dec_}*
|
|||
hex_int={hex} {hex_}*
|
||||
|
||||
char_char= \\ .
|
||||
| [^\'\n]
|
||||
| [^\'\r\n\u0085\u2028\u2029]
|
||||
|
||||
string_char= \\ .
|
||||
| [^\"\n]
|
||||
| [^\"\r\n\u0085\u2028\u2029]
|
||||
|
||||
all_nl_wrap=[^\n]* [ \n]*
|
||||
all_no_nl=[^\n]+
|
||||
nl_wrap={LF} (\s|{LF})*
|
||||
all_no_nl=[^\r\n\u0085\u2028\u2029]+
|
||||
|
||||
|
||||
FLOAT= "0x" {hex_int} "." {hex_int} ([pP] [-+]? {dec_int})?
|
||||
|
@ -83,16 +85,16 @@ IDENTIFIER_PLAIN=[A-Za-z_][A-Za-z0-9_]*
|
|||
%state UNT_SQUOT
|
||||
%state UNT_DQUOT
|
||||
|
||||
%state LINE_CMT
|
||||
%state CMT_LINE
|
||||
%%
|
||||
|
||||
//Comments
|
||||
|
||||
<YYINITIAL> "//" { yybegin(LINE_CMT); }
|
||||
<LINE_CMT> {all_nl_wrap} "//" { }
|
||||
<LINE_CMT> {all_no_nl} { }
|
||||
<LINE_CMT> \n { yybegin(YYINITIAL); return LINE_COMMENT; }
|
||||
<LINE_CMT> <<EOF>> { yybegin(YYINITIAL); return LINE_COMMENT; }
|
||||
<YYINITIAL> "//" { yybegin(CMT_LINE); }
|
||||
<CMT_LINE> {all_no_nl} { }
|
||||
<CMT_LINE> {nl_wrap} "//" { }
|
||||
<CMT_LINE> \R { yybegin(YYINITIAL); return LINE_COMMENT; }
|
||||
<CMT_LINE> <<EOF>> { yybegin(YYINITIAL); return LINE_COMMENT; }
|
||||
|
||||
//Symbols
|
||||
|
||||
|
@ -123,9 +125,9 @@ IDENTIFIER_PLAIN=[A-Za-z_][A-Za-z0-9_]*
|
|||
<STR_LIT> [^] { yypushback(1); yybegin(UNT_DQUOT); }
|
||||
|
||||
<YYINITIAL> "\\\\" { yybegin(STR_MULT_LINE); }
|
||||
<STR_MULT_LINE> {all_nl_wrap} "\\\\" { }
|
||||
<STR_MULT_LINE> {all_no_nl} { }
|
||||
<STR_MULT_LINE> \n { yybegin(YYINITIAL); return STRING_LITERAL_MULTI; }
|
||||
<STR_MULT_LINE> {nl_wrap} "\\\\" { }
|
||||
<STR_MULT_LINE> {LF} { yybegin(YYINITIAL); return STRING_LITERAL_MULTI; }
|
||||
<STR_MULT_LINE> <<EOF>> { yybegin(YYINITIAL); return STRING_LITERAL_MULTI; }
|
||||
|
||||
//Numbers
|
||||
|
@ -144,10 +146,10 @@ IDENTIFIER_PLAIN=[A-Za-z_][A-Za-z0-9_]*
|
|||
//Error handling
|
||||
|
||||
<UNT_SQUOT> <<EOF>> { yybegin(YYINITIAL); return BAD_SQUOT; }
|
||||
<UNT_SQUOT> {CRLF} { yybegin(YYINITIAL); return BAD_SQUOT; }
|
||||
<UNT_SQUOT> {LF} { yybegin(YYINITIAL); return BAD_SQUOT; }
|
||||
<UNT_SQUOT> {all_no_nl} { }
|
||||
<UNT_DQUOT> <<EOF>> { yybegin(YYINITIAL); return BAD_DQUOT; }
|
||||
<UNT_DQUOT> {CRLF} { yybegin(YYINITIAL); return BAD_DQUOT; }
|
||||
<UNT_DQUOT> {LF} { yybegin(YYINITIAL); return BAD_DQUOT; }
|
||||
<UNT_DQUOT> {all_no_nl} { }
|
||||
|
||||
//Misc
|
||||
|
|
|
@ -22,12 +22,6 @@
|
|||
|
||||
package com.falsepattern.zigbrains
|
||||
|
||||
import com.falsepattern.zigbrains.direnv.DirenvCmd
|
||||
import com.falsepattern.zigbrains.direnv.emptyEnv
|
||||
import com.falsepattern.zigbrains.direnv.getDirenv
|
||||
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
|
||||
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider
|
||||
import com.intellij.ide.BrowserUtil
|
||||
import com.intellij.ide.plugins.PluginManager
|
||||
import com.intellij.notification.Notification
|
||||
|
@ -39,10 +33,8 @@ import com.intellij.openapi.options.Configurable
|
|||
import com.intellij.openapi.options.ShowSettingsUtil
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.startup.ProjectActivity
|
||||
import com.intellij.openapi.util.UserDataHolderBase
|
||||
import java.lang.reflect.Constructor
|
||||
import java.lang.reflect.Method
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
class ZBStartup: ProjectActivity {
|
||||
var firstInit = true
|
||||
|
@ -75,19 +67,6 @@ class ZBStartup: ProjectActivity {
|
|||
notif.notify(null)
|
||||
}
|
||||
}
|
||||
//Autodetection
|
||||
val zigProjectState = project.zigProjectSettings.state
|
||||
if (zigProjectState.toolchainPath.isNullOrBlank()) {
|
||||
val data = UserDataHolderBase()
|
||||
data.putUserData(LocalZigToolchain.DIRENV_KEY,
|
||||
DirenvCmd.direnvInstalled() && !project.isDefault && zigProjectState.direnv
|
||||
)
|
||||
val tc = ZigToolchainProvider.suggestToolchain(project, data) ?: return
|
||||
if (tc is LocalZigToolchain) {
|
||||
zigProjectState.toolchainPath = tc.location.pathString
|
||||
project.zigProjectSettings.state = zigProjectState
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,23 +29,53 @@ import com.intellij.ide.impl.isTrusted
|
|||
import com.intellij.notification.Notification
|
||||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.notification.Notifications
|
||||
import com.intellij.openapi.components.*
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.guessProjectDir
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.UserDataHolder
|
||||
import com.intellij.openapi.vfs.toNioPathOrNull
|
||||
import com.intellij.platform.util.progress.withProgressText
|
||||
import com.intellij.util.io.awaitExit
|
||||
import com.intellij.util.xmlb.annotations.Attribute
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
|
||||
object DirenvCmd {
|
||||
suspend fun importDirenv(project: Project): Env {
|
||||
if (!direnvInstalled() || !project.isTrusted())
|
||||
return emptyEnv
|
||||
val workDir = project.guessProjectDir()?.toNioPath() ?: return emptyEnv
|
||||
@Service(Service.Level.PROJECT)
|
||||
@State(
|
||||
name = "Direnv",
|
||||
storages = [Storage("zigbrains.xml")]
|
||||
)
|
||||
class DirenvService(val project: Project): SerializablePersistentStateComponent<DirenvService.State>(State()), IDirenvService {
|
||||
private val mutex = Mutex()
|
||||
|
||||
val runOutput = run(project, workDir, "export", "json")
|
||||
override val isInstalled: Boolean by lazy {
|
||||
// Using the builtin stuff here instead of Env because it should only scan for direnv on the process path
|
||||
PathEnvironmentVariableUtil.findExecutableInPathOnAnyOS("direnv") != null
|
||||
}
|
||||
|
||||
var isEnabledRaw: DirenvState
|
||||
get() = state.enabled
|
||||
set(value) {
|
||||
updateState {
|
||||
it.copy(enabled = value)
|
||||
}
|
||||
}
|
||||
|
||||
override val isEnabled: DirenvState
|
||||
get() = isEnabledRaw
|
||||
|
||||
override suspend fun import(): Env {
|
||||
if (!isInstalled || !project.isTrusted() || project.isDefault)
|
||||
return Env.empty
|
||||
val workDir = project.guessProjectDir()?.toNioPath() ?: return Env.empty
|
||||
|
||||
val runOutput = run(workDir, "export", "json")
|
||||
if (runOutput.error) {
|
||||
if (runOutput.output.contains("is blocked")) {
|
||||
Notifications.Bus.notify(Notification(
|
||||
|
@ -54,7 +84,7 @@ object DirenvCmd {
|
|||
ZigBrainsBundle.message("notification.content.direnv-blocked"),
|
||||
NotificationType.ERROR
|
||||
))
|
||||
return emptyEnv
|
||||
return Env.empty
|
||||
} else {
|
||||
Notifications.Bus.notify(Notification(
|
||||
GROUP_DISPLAY_ID,
|
||||
|
@ -62,22 +92,22 @@ object DirenvCmd {
|
|||
ZigBrainsBundle.message("notification.content.direnv-error", runOutput.output),
|
||||
NotificationType.ERROR
|
||||
))
|
||||
return emptyEnv
|
||||
return Env.empty
|
||||
}
|
||||
}
|
||||
return if (runOutput.output.isBlank()) {
|
||||
emptyEnv
|
||||
Env.empty
|
||||
} else {
|
||||
Env(Json.decodeFromString<Map<String, String>>(runOutput.output))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun run(project: Project, workDir: Path, vararg args: String): DirenvOutput {
|
||||
private suspend fun run(workDir: Path, vararg args: String): DirenvOutput {
|
||||
val cli = GeneralCommandLine("direnv", *args).withWorkingDirectory(workDir)
|
||||
|
||||
val (process, exitCode) = withProgressText("Running ${cli.commandLineString}") {
|
||||
withContext(Dispatchers.IO) {
|
||||
project.direnvService.mutex.withLock {
|
||||
mutex.withLock {
|
||||
val process = cli.createProcess()
|
||||
val exitCode = process.awaitExit()
|
||||
process to exitCode
|
||||
|
@ -94,17 +124,39 @@ object DirenvCmd {
|
|||
return DirenvOutput(stdOut, false)
|
||||
}
|
||||
|
||||
private const val GROUP_DISPLAY_ID = "zigbrains-direnv"
|
||||
|
||||
private val _direnvInstalled by lazy {
|
||||
// Using the builtin stuff here instead of Env because it should only scan for direnv on the process path
|
||||
PathEnvironmentVariableUtil.findExecutableInPathOnAnyOS("direnv") != null
|
||||
fun hasDotEnv(): Boolean {
|
||||
if (!isInstalled)
|
||||
return false
|
||||
val projectDir = project.guessProjectDir()?.toNioPathOrNull() ?: return false
|
||||
return envFiles.any { projectDir.resolve(it).isRegularFile() }
|
||||
}
|
||||
|
||||
data class State(
|
||||
@JvmField
|
||||
@Attribute
|
||||
var enabled: DirenvState = DirenvState.Auto
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val GROUP_DISPLAY_ID = "zigbrains-direnv"
|
||||
fun getInstance(project: Project): IDirenvService = project.service<DirenvService>()
|
||||
|
||||
private val STATE_KEY = Key.create<DirenvState>("DIRENV_STATE")
|
||||
|
||||
fun getStateFor(data: UserDataHolder?, project: Project?): DirenvState {
|
||||
return data?.getUserData(STATE_KEY) ?: project?.let { getInstance(project).isEnabled } ?: DirenvState.Disabled
|
||||
}
|
||||
|
||||
fun setStateFor(data: UserDataHolder, state: DirenvState) {
|
||||
data.putUserData(STATE_KEY, state)
|
||||
}
|
||||
}
|
||||
fun direnvInstalled() = _direnvInstalled
|
||||
}
|
||||
|
||||
suspend fun Project?.getDirenv(): Env {
|
||||
if (this == null)
|
||||
return emptyEnv
|
||||
return DirenvCmd.importDirenv(this)
|
||||
}
|
||||
sealed interface IDirenvService {
|
||||
val isInstalled: Boolean
|
||||
val isEnabled: DirenvState
|
||||
suspend fun import(): Env
|
||||
}
|
||||
|
||||
private val envFiles = listOf(".envrc", ".env")
|
|
@ -22,14 +22,19 @@
|
|||
|
||||
package com.falsepattern.zigbrains.direnv
|
||||
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
|
||||
@Service(Service.Level.PROJECT)
|
||||
class DirenvProjectService {
|
||||
val mutex = Mutex()
|
||||
}
|
||||
enum class DirenvState {
|
||||
Auto,
|
||||
Enabled,
|
||||
Disabled;
|
||||
|
||||
val Project.direnvService get() = service<DirenvProjectService>()
|
||||
fun isEnabled(project: Project?): Boolean {
|
||||
return when(this) {
|
||||
Enabled -> true
|
||||
Disabled -> false
|
||||
Auto -> project?.service<DirenvService>()?.hasDotEnv() == true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,20 +25,26 @@ package com.falsepattern.zigbrains.direnv
|
|||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.util.EnvironmentUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
import kotlin.io.path.absolute
|
||||
import kotlin.io.path.isDirectory
|
||||
import kotlin.io.path.isExecutable
|
||||
import kotlin.io.path.isRegularFile
|
||||
|
||||
@JvmRecord
|
||||
data class Env(val env: Map<String, String>) {
|
||||
private val path get() = getVariable("PATH")?.split(File.pathSeparatorChar)
|
||||
|
||||
private fun getVariable(name: @NonNls String) =
|
||||
env.getOrElse(name) { EnvironmentUtil.getValue(name) }
|
||||
|
||||
fun findExecutableOnPATH(exe: @NonNls String): Path? {
|
||||
fun findAllExecutablesOnPATH(exe: @NonNls String) = flow {
|
||||
val exeName = if (SystemInfo.isWindows) "$exe.exe" else exe
|
||||
val paths = path ?: return null
|
||||
val paths = path ?: return@flow
|
||||
for (dir in paths) {
|
||||
val path = dir.toNioPathOrNull()?.absolute() ?: continue
|
||||
if (!path.toFile().exists() || !path.isDirectory())
|
||||
|
@ -46,10 +52,11 @@ data class Env(val env: Map<String, String>) {
|
|||
val exePath = path.resolve(exeName).absolute()
|
||||
if (!exePath.isRegularFile() || !exePath.isExecutable())
|
||||
continue
|
||||
return exePath
|
||||
emit(exePath)
|
||||
}
|
||||
return null
|
||||
}.flowOn(Dispatchers.IO)
|
||||
|
||||
companion object {
|
||||
val empty = Env(emptyMap())
|
||||
}
|
||||
}
|
||||
|
||||
val emptyEnv = Env(emptyMap())
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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.direnv.ui
|
||||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.direnv.DirenvService
|
||||
import com.falsepattern.zigbrains.direnv.DirenvState
|
||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider.Companion.PROJECT_KEY
|
||||
import com.falsepattern.zigbrains.shared.SubConfigurable
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import java.awt.event.ItemEvent
|
||||
|
||||
abstract class DirenvEditor<T>(private val sharedState: ZigProjectConfigurationProvider.IUserDataBridge?): SubConfigurable<T> {
|
||||
private var cb: ComboBox<DirenvState>? = null
|
||||
override fun attach(panel: Panel): Unit = with(panel) {
|
||||
row(ZigBrainsBundle.message("settings.direnv.enable.label")) {
|
||||
comboBox(DirenvState.entries).component.let {
|
||||
cb = it
|
||||
if (sharedState != null) {
|
||||
it.addItemListener { e ->
|
||||
if (e.stateChange != ItemEvent.SELECTED)
|
||||
return@addItemListener
|
||||
val item = e.item
|
||||
if (item !is DirenvState)
|
||||
return@addItemListener
|
||||
DirenvService.setStateFor(sharedState, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isModified(context: T): Boolean {
|
||||
return isEnabled(context) != cb?.selectedItem as DirenvState
|
||||
}
|
||||
|
||||
override fun apply(context: T) {
|
||||
setEnabled(context, cb?.selectedItem as DirenvState)
|
||||
}
|
||||
|
||||
override fun reset(context: T?) {
|
||||
if (context == null) {
|
||||
cb?.selectedItem = DirenvState.Auto
|
||||
return
|
||||
}
|
||||
cb?.selectedItem = isEnabled(context)
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
}
|
||||
|
||||
abstract fun isEnabled(context: T): DirenvState
|
||||
abstract fun setEnabled(context: T, value: DirenvState)
|
||||
|
||||
class ForProject(sharedState: ZigProjectConfigurationProvider.IUserDataBridge) : DirenvEditor<Project>(sharedState) {
|
||||
override fun isEnabled(context: Project): DirenvState {
|
||||
return DirenvService.getInstance(context).isEnabled
|
||||
}
|
||||
|
||||
override fun setEnabled(context: Project, value: DirenvState) {
|
||||
context.service<DirenvService>().isEnabledRaw = value
|
||||
}
|
||||
}
|
||||
|
||||
class Provider: ZigProjectConfigurationProvider {
|
||||
override fun create(sharedState: ZigProjectConfigurationProvider.IUserDataBridge): SubConfigurable<Project>? {
|
||||
if (sharedState.getUserData(PROJECT_KEY)?.isDefault != false) {
|
||||
return null
|
||||
}
|
||||
DirenvService.setStateFor(sharedState, DirenvState.Auto)
|
||||
return ForProject(sharedState)
|
||||
}
|
||||
|
||||
override val index: Int
|
||||
get() = 100
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* 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.actions
|
||||
|
||||
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfig
|
||||
import com.falsepattern.zigbrains.project.execution.run.ZigConfigProducerRun
|
||||
import com.falsepattern.zigbrains.project.execution.run.ZigExecConfigRun
|
||||
import com.falsepattern.zigbrains.project.execution.test.ZigConfigProducerTest
|
||||
import com.falsepattern.zigbrains.project.execution.test.ZigExecConfigTest
|
||||
import com.intellij.execution.ExecutionManager
|
||||
import com.intellij.execution.actions.ConfigurationContext
|
||||
import com.intellij.execution.actions.RunConfigurationProducer
|
||||
import com.intellij.execution.executors.DefaultRunExecutor
|
||||
import com.intellij.execution.runners.ExecutionEnvironmentBuilder
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
|
||||
class ZigRunFileAction: DumbAwareAction() {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val file = e.getData(CommonDataKeys.PSI_FILE) ?: return
|
||||
val config = getConfig(e) ?: return
|
||||
val project = file.project
|
||||
val builder = ExecutionEnvironmentBuilder.createOrNull(DefaultRunExecutor.getRunExecutorInstance(), config) ?: return
|
||||
ExecutionManager.getInstance(project).restartRunProfile(builder.build())
|
||||
}
|
||||
|
||||
private fun getConfig(e: AnActionEvent): ZigExecConfig<*>? {
|
||||
val context = ConfigurationContext.getFromContext(e.dataContext, e.place)
|
||||
return getRunConfiguration(context) ?: getTestConfiguration(context)
|
||||
}
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
e.presentation.isEnabledAndVisible = getConfig(e) != null
|
||||
}
|
||||
|
||||
private fun getRunConfiguration(context: ConfigurationContext): ZigExecConfigRun? {
|
||||
try {
|
||||
val configProducer = RunConfigurationProducer.getInstance(ZigConfigProducerRun::class.java)
|
||||
val settings = configProducer.findExistingConfiguration(context)
|
||||
if (settings != null) {
|
||||
return settings.configuration as? ZigExecConfigRun
|
||||
}
|
||||
val fromContext = configProducer.createConfigurationFromContext(context)
|
||||
if (fromContext != null) {
|
||||
return fromContext.configuration as? ZigExecConfigRun
|
||||
}
|
||||
} catch (_: NullPointerException) {}
|
||||
return null
|
||||
}
|
||||
private fun getTestConfiguration(context: ConfigurationContext): ZigExecConfigTest? {
|
||||
try {
|
||||
val configProducer = RunConfigurationProducer.getInstance(ZigConfigProducerTest::class.java)
|
||||
val settings = configProducer.findExistingConfiguration(context)
|
||||
if (settings != null) {
|
||||
return settings.configuration as? ZigExecConfigTest
|
||||
}
|
||||
val fromContext = configProducer.createConfigurationFromContext(context)
|
||||
if (fromContext != null) {
|
||||
return fromContext.configuration as? ZigExecConfigTest
|
||||
}
|
||||
} catch (_: NullPointerException) {}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getActionUpdateThread(): ActionUpdateThread {
|
||||
return ActionUpdateThread.BGT
|
||||
}
|
||||
}
|
|
@ -20,24 +20,18 @@
|
|||
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.falsepattern.zigbrains.project.settings
|
||||
package com.falsepattern.zigbrains.project.execution
|
||||
|
||||
import com.falsepattern.zigbrains.shared.SubConfigurable
|
||||
import com.intellij.execution.filters.TextConsoleBuilderImpl
|
||||
import com.intellij.execution.ui.ConsoleView
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.terminal.TerminalExecutionConsole
|
||||
|
||||
class ZigCoreProjectConfigurationProvider: ZigProjectConfigurationProvider {
|
||||
override fun handleMainConfigChanged(project: Project) {
|
||||
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()
|
||||
}
|
||||
|
||||
override fun createConfigurable(project: Project): SubConfigurable {
|
||||
return ZigProjectConfigurable(project)
|
||||
}
|
||||
|
||||
override fun createNewProjectSettingsPanel(): ZigProjectConfigurationProvider.SettingsPanel {
|
||||
return ZigProjectSettingsPanel(ProjectManager.getInstance().defaultProject)
|
||||
}
|
||||
|
||||
override val priority: Int
|
||||
get() = 0
|
||||
}
|
|
@ -24,20 +24,17 @@ package com.falsepattern.zigbrains.project.execution.base
|
|||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.project.execution.base.ZigConfigurable.ZigConfigModule
|
||||
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
|
||||
import com.falsepattern.zigbrains.shared.cli.translateCommandline
|
||||
import com.falsepattern.zigbrains.shared.element.*
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||
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 +158,10 @@ class WorkDirectoryConfigurable(@Transient override val serializedName: String)
|
|||
}
|
||||
|
||||
class WorkDirectoryConfigModule(private val serializedName: String) : PathConfigModule<WorkDirectoryConfigurable>() {
|
||||
private val field = TextFieldWithBrowseButton(
|
||||
TextBrowseFolderListener(
|
||||
FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.working-directory"))
|
||||
),
|
||||
this
|
||||
)
|
||||
private val field = textFieldWithBrowseButton(
|
||||
null,
|
||||
FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.working-directory"))
|
||||
).also { Disposer.register(this, it) }
|
||||
|
||||
override var stringValue by field::text
|
||||
|
||||
|
@ -201,9 +196,9 @@ class FilePathConfigurable(
|
|||
}
|
||||
|
||||
class FilePathConfigModule(private val serializedName: String, @Nls private val label: String) : PathConfigModule<FilePathConfigurable>() {
|
||||
private val field = TextFieldWithBrowseButton(
|
||||
TextBrowseFolderListener(FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()),
|
||||
this
|
||||
private val field = textFieldWithBrowseButton(
|
||||
null,
|
||||
FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor(),
|
||||
)
|
||||
|
||||
override var stringValue by field::text
|
||||
|
@ -274,18 +269,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 DirenvConfigurable(serializedName: String, project: Project): CheckboxConfigurable(serializedName, ZigBrainsBundle.message("exec.option.label.direnv"), project.zigProjectSettings.state.direnv) {
|
||||
override fun clone(): DirenvConfigurable {
|
||||
return super.clone() as DirenvConfigurable
|
||||
}
|
||||
}
|
||||
|
||||
class OptimizationConfigurable(
|
||||
@Transient private val serializedName: String,
|
||||
var level: OptimizationLevel = OptimizationLevel.Debug,
|
||||
|
|
|
@ -41,7 +41,7 @@ abstract class ZigConfigProducer<T: ZigExecConfig<T>>: LazyRunConfigurationProdu
|
|||
val psiFile = element.containingFile as? ZigFile ?: return false
|
||||
val theFile = psiFile.virtualFile ?: return false
|
||||
val filePath = theFile.toNioPathOrNull() ?: return false
|
||||
return setupConfigurationFromContext(configuration, element, filePath, theFile)
|
||||
return setupConfigurationFromContext(configuration, element, psiFile, filePath, theFile)
|
||||
}
|
||||
|
||||
override fun isConfigurationFromContext(configuration: T, context: ConfigurationContext): Boolean {
|
||||
|
@ -49,7 +49,7 @@ abstract class ZigConfigProducer<T: ZigExecConfig<T>>: LazyRunConfigurationProdu
|
|||
val psiFile = element.containingFile as? ZigFile ?: return false
|
||||
val theFile = psiFile.virtualFile ?: return false
|
||||
val filePath = theFile.toNioPathOrNull() ?: return false
|
||||
return isConfigurationFromContext(configuration, element, filePath, theFile)
|
||||
return isConfigurationFromContext(configuration, element, psiFile, filePath, theFile)
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -78,7 +78,7 @@ abstract class ZigConfigProducer<T: ZigExecConfig<T>>: LazyRunConfigurationProdu
|
|||
}
|
||||
*/
|
||||
|
||||
protected abstract fun setupConfigurationFromContext(configuration: T, element: PsiElement, filePath: Path, theFile: VirtualFile): Boolean
|
||||
protected abstract fun isConfigurationFromContext(configuration: T, element: PsiElement, filePath: Path, theFile: VirtualFile): Boolean
|
||||
protected abstract fun setupConfigurationFromContext(configuration: T, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean
|
||||
protected abstract fun isConfigurationFromContext(configuration: T, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean
|
||||
abstract override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean
|
||||
}
|
|
@ -22,8 +22,7 @@
|
|||
|
||||
package com.falsepattern.zigbrains.project.execution.base
|
||||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.direnv.DirenvCmd
|
||||
import com.falsepattern.zigbrains.direnv.DirenvService
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.execution.Executor
|
||||
import com.intellij.execution.configurations.ConfigurationFactory
|
||||
|
@ -42,10 +41,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
|
||||
var direnv = DirenvConfigurable("direnv", project)
|
||||
private set
|
||||
|
||||
abstract val suggestedName: @ActionText String
|
||||
@Throws(ExecutionException::class)
|
||||
|
@ -68,24 +63,19 @@ abstract class ZigExecConfig<T: ZigExecConfig<T>>(project: Project, factory: Con
|
|||
|
||||
|
||||
suspend fun patchCommandLine(commandLine: GeneralCommandLine): GeneralCommandLine {
|
||||
if (direnv.value) {
|
||||
commandLine.withEnvironment(DirenvCmd.importDirenv(project).env)
|
||||
val direnv = DirenvService.getInstance(project)
|
||||
if (direnv.isEnabled.isEnabled(project)) {
|
||||
commandLine.withEnvironment(direnv.import().env)
|
||||
}
|
||||
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()
|
||||
myClone.direnv = direnv.clone()
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return myClone as T
|
||||
}
|
||||
|
||||
open fun getConfigurables(): List<ZigConfigurable<*>> = listOf(workingDirectory, pty, direnv)
|
||||
open fun getConfigurables(): List<ZigConfigurable<*>> = listOf(workingDirectory)
|
||||
}
|
|
@ -23,18 +23,16 @@
|
|||
package com.falsepattern.zigbrains.project.execution.base
|
||||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
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.project.execution.ZigConsoleBuilder
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.shared.cli.startIPCAwareProcess
|
||||
import com.falsepattern.zigbrains.shared.coroutine.runModalOrBlocking
|
||||
import com.intellij.build.BuildTextConsoleView
|
||||
import com.intellij.execution.DefaultExecutionResult
|
||||
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.process.ProcessHandler
|
||||
import com.intellij.execution.process.ProcessTerminatedListener
|
||||
import com.intellij.execution.runners.ExecutionEnvironment
|
||||
import com.intellij.platform.ide.progress.ModalTaskOwner
|
||||
import kotlin.io.path.pathString
|
||||
|
@ -44,6 +42,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"}) {
|
||||
|
@ -53,37 +55,20 @@ 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 ZigProcessHandler(getCommandLine(toolchain, false))
|
||||
val toolchain = ZigToolchainService.getInstance(environment.project).toolchain ?: throw ExecutionException(ZigBrainsBundle.message("exception.zig-profile-state.start-process.no-toolchain"))
|
||||
return getCommandLine(toolchain, false).startIPCAwareProcess(environment.project, emulateTerminal = true)
|
||||
}
|
||||
|
||||
@Throws(ExecutionException::class)
|
||||
open suspend fun getCommandLine(toolchain: AbstractZigToolchain, debug: Boolean): GeneralCommandLine {
|
||||
open suspend fun getCommandLine(toolchain: ZigToolchain, debug: Boolean): GeneralCommandLine {
|
||||
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)
|
||||
fun executeCommandLine(commandLine: GeneralCommandLine, environment: ExecutionEnvironment): DefaultExecutionResult {
|
||||
val handler = startProcess(commandLine)
|
||||
val console = BuildTextConsoleView(environment.project, false, emptyList())
|
||||
console.attachToProcess(handler)
|
||||
return DefaultExecutionResult(console, handler)
|
||||
}
|
||||
|
||||
@Throws(ExecutionException::class)
|
||||
fun startProcess(commandLine: GeneralCommandLine): ProcessHandler {
|
||||
val handler = ZigProcessHandler(commandLine)
|
||||
ProcessTerminatedListener.attach(handler)
|
||||
return handler
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.base
|
||||
|
||||
import com.falsepattern.zigbrains.zig.psi.ZigContainerMembers
|
||||
import com.falsepattern.zigbrains.zig.psi.ZigFile
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.isFile
|
||||
import com.intellij.psi.util.childrenOfType
|
||||
|
||||
fun ZigFile.hasMainFunction(): Boolean {
|
||||
val members = childrenOfType<ZigContainerMembers>().firstOrNull() ?: return false
|
||||
return members.containerDeclarationList.any { it.decl?.fnProto?.identifier?.textMatches("main") == true }
|
||||
}
|
||||
|
||||
fun ZigFile.hasTests(): Boolean {
|
||||
val members = childrenOfType<ZigContainerMembers>().firstOrNull() ?: return false
|
||||
return members.containerDeclarationList.any { it.testDecl != null }
|
||||
}
|
||||
|
||||
fun VirtualFile.findBuildZig(): VirtualFile? {
|
||||
var parent = this.parent
|
||||
while (parent != null) {
|
||||
parent.children.forEach {
|
||||
if (it.isFile && it.name == "build.zig") {
|
||||
return it
|
||||
}
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun VirtualFile.isBuildZig(): Boolean {
|
||||
return name == "build.zig"
|
||||
}
|
|
@ -24,11 +24,16 @@ package com.falsepattern.zigbrains.project.execution.build
|
|||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.project.execution.base.ZigConfigProducer
|
||||
import com.falsepattern.zigbrains.project.execution.base.findBuildZig
|
||||
import com.falsepattern.zigbrains.project.execution.base.isBuildZig
|
||||
import com.falsepattern.zigbrains.project.execution.firstConfigFactory
|
||||
import com.falsepattern.zigbrains.zig.psi.ZigFile
|
||||
import com.falsepattern.zigbrains.zig.psi.ZigTypes
|
||||
import com.intellij.execution.actions.ConfigurationFromContext
|
||||
import com.intellij.execution.configurations.ConfigurationFactory
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.elementType
|
||||
import java.nio.file.Path
|
||||
|
||||
class ZigConfigProducerBuild: ZigConfigProducer<ZigExecConfigBuild>() {
|
||||
|
@ -36,21 +41,43 @@ class ZigConfigProducerBuild: ZigConfigProducer<ZigExecConfigBuild>() {
|
|||
return firstConfigFactory<ZigConfigTypeBuild>()
|
||||
}
|
||||
|
||||
override fun setupConfigurationFromContext(configuration: ZigExecConfigBuild, element: PsiElement, filePath: Path, theFile: VirtualFile): Boolean {
|
||||
if (LINE_MARKER.elementMatches(element)) {
|
||||
configuration.name = ZigBrainsBundle.message("configuration.build.marker-name")
|
||||
override fun setupConfigurationFromContext(configuration: ZigExecConfigBuild, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean {
|
||||
if (theFile.isBuildZig()) {
|
||||
configuration.name = ZigBrainsBundle.message("configuration.build.marker-run")
|
||||
configuration.buildSteps.args = "run"
|
||||
configuration.debugBuildSteps.args = "install"
|
||||
return true
|
||||
}
|
||||
val buildZig = theFile.findBuildZig() ?: return false
|
||||
configuration.workingDirectory.path = buildZig.parent.toNioPath()
|
||||
if (element.elementType == ZigTypes.KEYWORD_TEST) {
|
||||
configuration.name = ZigBrainsBundle.message("configuration.build.marker-test")
|
||||
configuration.buildSteps.args = "test"
|
||||
configuration.debugBuildSteps.args = "install_test"
|
||||
return true
|
||||
} else {
|
||||
configuration.name = ZigBrainsBundle.message("configuration.build.marker-run")
|
||||
configuration.buildSteps.args = "run"
|
||||
configuration.debugBuildSteps.args = "install"
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun isConfigurationFromContext(configuration: ZigExecConfigBuild, element: PsiElement, filePath: Path, theFile: VirtualFile): Boolean {
|
||||
return filePath.parent == (configuration.workingDirectory.path ?: return false)
|
||||
override fun isConfigurationFromContext(configuration: ZigExecConfigBuild, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean {
|
||||
val dir = configuration.workingDirectory.path ?: return false
|
||||
if (theFile.isBuildZig()) {
|
||||
return filePath.parent == dir
|
||||
} else {
|
||||
if (element.elementType == ZigTypes.KEYWORD_TEST) {
|
||||
if (configuration.buildSteps.args != "test")
|
||||
return false
|
||||
}
|
||||
val buildZig = theFile.findBuildZig() ?: return false
|
||||
return buildZig.parent.toNioPath() == dir
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean {
|
||||
return self.configurationType is ZigConfigTypeBuild
|
||||
}
|
||||
}
|
||||
|
||||
private val LINE_MARKER = ZigLineMarkerBuild()
|
|
@ -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,7 +36,9 @@ 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")
|
||||
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"))
|
||||
private set
|
||||
|
@ -48,23 +49,9 @@ class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigEx
|
|||
override suspend fun buildCommandLineArgs(debug: Boolean): List<String> {
|
||||
val result = ArrayList<String>()
|
||||
result.add("build")
|
||||
val argsSplit = 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
|
||||
val steps = if (debug) debugBuildSteps.argsSplit() else buildSteps.argsSplit()
|
||||
result.addAll(steps)
|
||||
result.addAll(coloredCliFlags(colored.value, debug))
|
||||
result.addAll(extraArgs.argsSplit())
|
||||
result.addAll(if (debug) debugExtraArgs.argsSplit() else extraArgs.argsSplit())
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -75,16 +62,15 @@ 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(exePath, exeArgs)
|
||||
baseCfg + listOf(debugBuildSteps, debugExtraArgs, exePath, exeArgs)
|
||||
} else {
|
||||
baseCfg
|
||||
}
|
||||
|
|
|
@ -23,7 +23,10 @@
|
|||
package com.falsepattern.zigbrains.project.execution.run
|
||||
|
||||
import com.falsepattern.zigbrains.project.execution.base.ZigConfigProducer
|
||||
import com.falsepattern.zigbrains.project.execution.base.findBuildZig
|
||||
import com.falsepattern.zigbrains.project.execution.base.hasMainFunction
|
||||
import com.falsepattern.zigbrains.project.execution.firstConfigFactory
|
||||
import com.falsepattern.zigbrains.zig.psi.ZigFile
|
||||
import com.intellij.execution.actions.ConfigurationFromContext
|
||||
import com.intellij.execution.configurations.ConfigurationFactory
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
|
@ -35,16 +38,19 @@ class ZigConfigProducerRun: ZigConfigProducer<ZigExecConfigRun>() {
|
|||
return firstConfigFactory<ZigConfigTypeRun>()
|
||||
}
|
||||
|
||||
override fun setupConfigurationFromContext(configuration: ZigExecConfigRun, element: PsiElement, filePath: Path, theFile: VirtualFile): Boolean {
|
||||
if (LINE_MARKER.elementMatches(element)) {
|
||||
configuration.filePath.path = filePath
|
||||
configuration.name = theFile.presentableName
|
||||
return true
|
||||
override fun setupConfigurationFromContext(configuration: ZigExecConfigRun, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean {
|
||||
if (!psiFile.hasMainFunction()) {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
if (theFile.findBuildZig() != null) {
|
||||
return false
|
||||
}
|
||||
configuration.filePath.path = filePath
|
||||
configuration.name = theFile.presentableName
|
||||
return true
|
||||
}
|
||||
|
||||
override fun isConfigurationFromContext(configuration: ZigExecConfigRun, element: PsiElement, filePath: Path, theFile: VirtualFile): Boolean {
|
||||
override fun isConfigurationFromContext(configuration: ZigExecConfigRun, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean {
|
||||
return filePath == configuration.filePath.path
|
||||
}
|
||||
|
||||
|
@ -52,5 +58,3 @@ class ZigConfigProducerRun: ZigConfigProducer<ZigExecConfigRun>() {
|
|||
return self.configurationType is ZigConfigTypeRun
|
||||
}
|
||||
}
|
||||
|
||||
private val LINE_MARKER = ZigLineMarkerRun()
|
|
@ -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> {
|
||||
|
|
|
@ -24,7 +24,10 @@ package com.falsepattern.zigbrains.project.execution.test
|
|||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.project.execution.base.ZigConfigProducer
|
||||
import com.falsepattern.zigbrains.project.execution.base.findBuildZig
|
||||
import com.falsepattern.zigbrains.project.execution.base.hasTests
|
||||
import com.falsepattern.zigbrains.project.execution.firstConfigFactory
|
||||
import com.falsepattern.zigbrains.zig.psi.ZigFile
|
||||
import com.intellij.execution.actions.ConfigurationFromContext
|
||||
import com.intellij.execution.configurations.ConfigurationFactory
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
|
@ -36,22 +39,23 @@ class ZigConfigProducerTest: ZigConfigProducer<ZigExecConfigTest>() {
|
|||
return firstConfigFactory<ZigConfigTypeTest>()
|
||||
}
|
||||
|
||||
override fun setupConfigurationFromContext(configuration: ZigExecConfigTest, element: PsiElement, filePath: Path, theFile: VirtualFile): Boolean {
|
||||
if (LINE_MARKER.elementMatches(element)) {
|
||||
configuration.filePath.path = filePath
|
||||
configuration.name = ZigBrainsBundle.message("configuration.test.marker-name", theFile.presentableName)
|
||||
return true
|
||||
override fun setupConfigurationFromContext(configuration: ZigExecConfigTest, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean {
|
||||
if (!psiFile.hasTests()) {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
if (theFile.findBuildZig() != null) {
|
||||
return false
|
||||
}
|
||||
configuration.filePath.path = filePath
|
||||
configuration.name = ZigBrainsBundle.message("configuration.test.marker-name", theFile.presentableName)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun isConfigurationFromContext(configuration: ZigExecConfigTest, element: PsiElement, filePath: Path, theFile: VirtualFile): Boolean {
|
||||
override fun isConfigurationFromContext(configuration: ZigExecConfigTest, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean {
|
||||
return filePath == configuration.filePath.path
|
||||
}
|
||||
|
||||
override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean {
|
||||
return self.configurationType is ZigConfigTypeTest
|
||||
}
|
||||
}
|
||||
|
||||
private val LINE_MARKER = ZigLineMarkerTest()
|
||||
}
|
|
@ -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> {
|
||||
|
|
|
@ -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
|
||||
|
@ -49,7 +50,7 @@ class ZigModuleBuilder: ModuleBuilder() {
|
|||
|
||||
override fun getCustomOptionsStep(context: WizardContext?, parentDisposable: Disposable?): ModuleWizardStep? {
|
||||
val step = ZigModuleWizardStep(parentDisposable)
|
||||
parentDisposable?.let { Disposer.register(it, step.peer) }
|
||||
parentDisposable?.let { Disposer.register(it) { step.peer.dispose() } }
|
||||
return step
|
||||
}
|
||||
|
||||
|
@ -58,20 +59,20 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
inner class ZigModuleWizardStep(parent: Disposable?): ModuleWizardStep() {
|
||||
internal val peer = ZigProjectGeneratorPeer(true).also { Disposer.register(parent ?: return@also, it) }
|
||||
internal val peer = ZigProjectGeneratorPeer(true).also { Disposer.register(parent ?: return@also) {it.dispose()} }
|
||||
|
||||
override fun getComponent(): JComponent {
|
||||
return peer.myComponent.withBorder()
|
||||
}
|
||||
|
||||
override fun disposeUIResources() {
|
||||
Disposer.dispose(peer)
|
||||
Disposer.dispose(peer.newProjectPanel)
|
||||
}
|
||||
|
||||
override fun updateDataModel() {
|
||||
|
|
|
@ -23,9 +23,7 @@
|
|||
package com.falsepattern.zigbrains.project.newproject
|
||||
|
||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
||||
import com.falsepattern.zigbrains.project.template.ZigExecutableTemplate
|
||||
import com.falsepattern.zigbrains.project.template.ZigInitTemplate
|
||||
import com.falsepattern.zigbrains.project.template.ZigLibraryTemplate
|
||||
import com.falsepattern.zigbrains.project.template.ZigProjectTemplate
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.ActionToolbarPosition
|
||||
|
@ -43,7 +41,7 @@ import javax.swing.ListSelectionModel
|
|||
|
||||
class ZigNewProjectPanel(private var handleGit: Boolean): Disposable {
|
||||
private val git = JBCheckBox()
|
||||
private val panels = ZigProjectConfigurationProvider.createNewProjectSettingsPanels().onEach { Disposer.register(this, it) }
|
||||
val panels = ZigProjectConfigurationProvider.createPanels(null).onEach { Disposer.register(this, it) }
|
||||
private val templateList = JBList(JBList.createDefaultListModel(defaultTemplates)).apply {
|
||||
selectionMode = ListSelectionModel.SINGLE_SELECTION
|
||||
selectedIndex = 0
|
||||
|
@ -66,7 +64,7 @@ class ZigNewProjectPanel(private var handleGit: Boolean): Disposable {
|
|||
|
||||
fun getData(): ZigProjectConfigurationData {
|
||||
val selectedTemplate = templateList.selectedValue
|
||||
return ZigProjectConfigurationData(handleGit && git.isSelected, panels.map { it.data }, selectedTemplate)
|
||||
return ZigProjectConfigurationData(handleGit && git.isSelected, panels, selectedTemplate)
|
||||
}
|
||||
|
||||
fun attach(p: Panel): Unit = with(p) {
|
||||
|
@ -75,6 +73,7 @@ class ZigNewProjectPanel(private var handleGit: Boolean): Disposable {
|
|||
cell(git)
|
||||
}
|
||||
}
|
||||
panels.filter { it.newProjectBeforeInitSelector }.forEach { it.attach(p) }
|
||||
group("Zig Project Template") {
|
||||
row {
|
||||
resizableRow()
|
||||
|
@ -83,7 +82,7 @@ class ZigNewProjectPanel(private var handleGit: Boolean): Disposable {
|
|||
.align(AlignY.FILL)
|
||||
}
|
||||
}
|
||||
panels.forEach { it.attach(p) }
|
||||
panels.filter { !it.newProjectBeforeInitSelector }.forEach { it.attach(p) }
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
|
@ -92,7 +91,7 @@ class ZigNewProjectPanel(private var handleGit: Boolean): Disposable {
|
|||
|
||||
|
||||
private val defaultTemplates get() = listOf(
|
||||
ZigExecutableTemplate(),
|
||||
ZigLibraryTemplate(),
|
||||
// ZigExecutableTemplate(),
|
||||
// ZigLibraryTemplate(),
|
||||
ZigInitTemplate()
|
||||
)
|
|
@ -22,9 +22,10 @@
|
|||
|
||||
package com.falsepattern.zigbrains.project.newproject
|
||||
|
||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
||||
import com.falsepattern.zigbrains.project.template.ZigInitTemplate
|
||||
import com.falsepattern.zigbrains.project.template.ZigProjectTemplate
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
|
||||
import com.falsepattern.zigbrains.shared.SubConfigurable
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.intellij.notification.Notification
|
||||
import com.intellij.notification.NotificationType
|
||||
|
@ -42,7 +43,7 @@ import kotlinx.coroutines.launch
|
|||
@JvmRecord
|
||||
data class ZigProjectConfigurationData(
|
||||
val git: Boolean,
|
||||
val conf: List<ZigProjectConfigurationProvider.Settings>,
|
||||
val conf: List<SubConfigurable<Project>>,
|
||||
val selectedTemplate: ZigProjectTemplate
|
||||
) {
|
||||
@RequiresBackgroundThread
|
||||
|
@ -54,9 +55,7 @@ data class ZigProjectConfigurationData(
|
|||
|
||||
if (!reporter.indeterminateStep("Initializing project") {
|
||||
if (template is ZigInitTemplate) {
|
||||
val toolchain = conf
|
||||
.mapNotNull { it as? ZigProjectConfigurationProvider.ToolchainProvider }
|
||||
.firstNotNullOfOrNull { it.toolchain } ?: run {
|
||||
val toolchain = ZigToolchainService.getInstance(project).toolchain ?: run {
|
||||
Notification(
|
||||
"zigbrains",
|
||||
"Tried to generate project with zig init, but zig toolchain is invalid",
|
||||
|
@ -119,10 +118,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 +130,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
|
||||
}
|
||||
|
|
|
@ -23,17 +23,15 @@
|
|||
package com.falsepattern.zigbrains.project.newproject
|
||||
|
||||
import com.intellij.ide.util.projectWizard.SettingsStep
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton
|
||||
import com.intellij.openapi.ui.ValidationInfo
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.platform.ProjectGeneratorPeer
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import javax.swing.JComponent
|
||||
|
||||
class ZigProjectGeneratorPeer(var handleGit: Boolean): ProjectGeneratorPeer<ZigProjectConfigurationData>, Disposable {
|
||||
private val newProjectPanel by lazy {
|
||||
ZigNewProjectPanel(handleGit).also { Disposer.register(this, it) }
|
||||
class ZigProjectGeneratorPeer(var handleGit: Boolean): ProjectGeneratorPeer<ZigProjectConfigurationData> {
|
||||
val newProjectPanel by lazy {
|
||||
ZigNewProjectPanel(handleGit)
|
||||
}
|
||||
val myComponent: JComponent by lazy {
|
||||
panel {
|
||||
|
@ -61,6 +59,7 @@ class ZigProjectGeneratorPeer(var handleGit: Boolean): ProjectGeneratorPeer<ZigP
|
|||
return false
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
fun dispose() {
|
||||
newProjectPanel.dispose()
|
||||
}
|
||||
}
|
|
@ -24,73 +24,29 @@ package com.falsepattern.zigbrains.project.run
|
|||
|
||||
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.openapi.util.Key
|
||||
import com.intellij.execution.process.KillableProcessHandler
|
||||
import com.pty4j.PtyProcess
|
||||
import java.nio.charset.Charset
|
||||
|
||||
class ZigProcessHandler : KillableColoredProcessHandler, ColoredTextAcceptor {
|
||||
open class ZigProcessHandler : KillableProcessHandler {
|
||||
constructor(commandLine: GeneralCommandLine) : super(commandLine) {
|
||||
setHasPty(commandLine is PtyCommandLine)
|
||||
setShouldDestroyProcessRecursively(!hasPty())
|
||||
}
|
||||
|
||||
constructor (process: Process, commandLine: String, charset: Charset) : super(process, commandLine, charset) {
|
||||
protected constructor (process: Process, commandLine: String, charset: Charset) : super(process, commandLine, charset) {
|
||||
setHasPty(process is PtyProcess)
|
||||
setShouldDestroyProcessRecursively(!hasPty())
|
||||
}
|
||||
|
||||
override fun coloredTextAvailable(text: String, attributes: Key<*>) {
|
||||
super.coloredTextAvailable(text.translateVT100Escapes(), attributes)
|
||||
class IPCAware : ZigProcessHandler {
|
||||
val originalCommandLine: String
|
||||
constructor(originalCommandLine: String, commandLine: GeneralCommandLine) : super(commandLine) {
|
||||
this.originalCommandLine = originalCommandLine
|
||||
}
|
||||
|
||||
fun unwrap(): ZigProcessHandler {
|
||||
return ZigProcessHandler(this.process, this.originalCommandLine, this.charset ?: Charsets.UTF_8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -23,8 +23,8 @@
|
|||
package com.falsepattern.zigbrains.project.run
|
||||
|
||||
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
|
||||
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.execution.configurations.RunProfileState
|
||||
|
@ -61,7 +61,7 @@ abstract class ZigProgramRunner<ProfileState : ZigProfileState<*>>(protected val
|
|||
|
||||
val state = castProfileState(baseState) ?: return null
|
||||
|
||||
val toolchain = environment.project.zigProjectSettings.state.toolchain ?: run {
|
||||
val toolchain = ZigToolchainService.getInstance(environment.project).toolchain ?: run {
|
||||
Notification(
|
||||
"zigbrains",
|
||||
"Zig project toolchain not set, cannot execute program! Please configure it in [Settings | Languages & Frameworks | Zig]",
|
||||
|
@ -81,5 +81,5 @@ abstract class ZigProgramRunner<ProfileState : ZigProfileState<*>>(protected val
|
|||
protected abstract fun castProfileState(state: ZigProfileState<*>): ProfileState?
|
||||
|
||||
@Throws(ExecutionException::class)
|
||||
abstract suspend fun execute(state: ProfileState, toolchain: AbstractZigToolchain, environment: ExecutionEnvironment): RunContentDescriptor?
|
||||
abstract suspend fun execute(state: ProfileState, toolchain: ZigToolchain, environment: ExecutionEnvironment): RunContentDescriptor?
|
||||
}
|
|
@ -24,20 +24,22 @@ 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.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||
import com.intellij.execution.configurations.RunProfile
|
||||
import com.intellij.execution.executors.DefaultRunExecutor
|
||||
import com.intellij.execution.runners.ExecutionEnvironment
|
||||
import com.intellij.execution.runners.RunContentBuilder
|
||||
import com.intellij.execution.ui.RunContentDescriptor
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.progress.blockingContext
|
||||
|
||||
class ZigRegularRunner: ZigProgramRunner<ZigProfileState<*>>(DefaultRunExecutor.EXECUTOR_ID) {
|
||||
override suspend fun execute(state: ZigProfileState<*>, toolchain: AbstractZigToolchain, environment: ExecutionEnvironment): RunContentDescriptor? {
|
||||
val cli = state.getCommandLine(toolchain, false)
|
||||
val exec = executeCommandLine(cli, environment)
|
||||
return withEDTContext {
|
||||
override suspend fun execute(state: ZigProfileState<*>, toolchain: ZigToolchain, environment: ExecutionEnvironment): RunContentDescriptor? {
|
||||
val exec = blockingContext {
|
||||
state.execute(environment.executor, this)
|
||||
}
|
||||
return withEDTContext(ModalityState.any()) {
|
||||
val runContentBuilder = RunContentBuilder(exec, environment)
|
||||
runContentBuilder.showRunContent(null)
|
||||
}
|
||||
|
|
|
@ -22,11 +22,14 @@
|
|||
|
||||
package com.falsepattern.zigbrains.project.settings
|
||||
|
||||
import com.falsepattern.zigbrains.shared.MultiConfigurable
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.shared.SubConfigurable
|
||||
import com.intellij.openapi.project.Project
|
||||
|
||||
class ZigConfigurable(project: Project): MultiConfigurable(ZigProjectConfigurationProvider.createConfigurables(project)) {
|
||||
override fun getDisplayName(): String {
|
||||
return "Zig"
|
||||
class ZigConfigurable(override val context: Project) : SubConfigurable.Adapter<Project>() {
|
||||
override fun instantiate(): List<SubConfigurable<Project>> {
|
||||
return ZigProjectConfigurationProvider.createPanels(context)
|
||||
}
|
||||
|
||||
override fun getDisplayName() = ZigBrainsBundle.message("settings.project.display-name")
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* 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.settings
|
||||
|
||||
import com.falsepattern.zigbrains.shared.SubConfigurable
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
|
||||
class ZigProjectConfigurable(private val project: Project): SubConfigurable {
|
||||
private var settingsPanel: ZigProjectSettingsPanel? = null
|
||||
override fun createComponent(panel: Panel) {
|
||||
settingsPanel?.let { Disposer.dispose(it) }
|
||||
settingsPanel = ZigProjectSettingsPanel(project).apply { attach(panel) }.also { Disposer.register(this, it) }
|
||||
}
|
||||
|
||||
override fun isModified(): Boolean {
|
||||
return project.zigProjectSettings.isModified(settingsPanel?.data ?: return false)
|
||||
}
|
||||
|
||||
override fun apply() {
|
||||
val service = project.zigProjectSettings
|
||||
val data = settingsPanel?.data ?: return
|
||||
val modified = service.isModified(data)
|
||||
service.state = data
|
||||
if (modified) {
|
||||
ZigProjectConfigurationProvider.mainConfigChanged(project)
|
||||
}
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
settingsPanel?.data = project.zigProjectSettings.state
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
settingsPanel = null
|
||||
}
|
||||
}
|
|
@ -22,38 +22,56 @@
|
|||
|
||||
package com.falsepattern.zigbrains.project.settings
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.shared.SubConfigurable
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.UserDataHolder
|
||||
import com.intellij.openapi.util.UserDataHolderBase
|
||||
|
||||
interface ZigProjectConfigurationProvider {
|
||||
fun handleMainConfigChanged(project: Project)
|
||||
fun createConfigurable(project: Project): SubConfigurable
|
||||
fun createNewProjectSettingsPanel(): SettingsPanel?
|
||||
val priority: Int
|
||||
fun create(sharedState: IUserDataBridge): SubConfigurable<Project>?
|
||||
val index: Int
|
||||
companion object {
|
||||
private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigProjectConfigurationProvider>("com.falsepattern.zigbrains.projectConfigProvider")
|
||||
fun mainConfigChanged(project: Project) {
|
||||
EXTENSION_POINT_NAME.extensionList.forEach { it.handleMainConfigChanged(project) }
|
||||
}
|
||||
fun createConfigurables(project: Project): List<SubConfigurable> {
|
||||
return EXTENSION_POINT_NAME.extensionList.sortedBy { it.priority }.map { it.createConfigurable(project) }
|
||||
}
|
||||
fun createNewProjectSettingsPanels(): List<SettingsPanel> {
|
||||
return EXTENSION_POINT_NAME.extensionList.sortedBy { it.priority }.mapNotNull { it.createNewProjectSettingsPanel() }
|
||||
val PROJECT_KEY: Key<Project> = Key.create("Project")
|
||||
fun createPanels(project: Project?): List<SubConfigurable<Project>> {
|
||||
val sharedState = UserDataBridge()
|
||||
sharedState.putUserData(PROJECT_KEY, project)
|
||||
return EXTENSION_POINT_NAME.extensionList.sortedBy { it.index }.mapNotNull { it.create(sharedState) }
|
||||
}
|
||||
}
|
||||
interface SettingsPanel: Disposable {
|
||||
val data: Settings
|
||||
fun attach(p: Panel)
|
||||
|
||||
interface IUserDataBridge: UserDataHolder {
|
||||
fun addUserDataChangeListener(listener: UserDataListener)
|
||||
fun removeUserDataChangeListener(listener: UserDataListener)
|
||||
}
|
||||
interface Settings {
|
||||
fun apply(project: Project)
|
||||
|
||||
interface UserDataListener {
|
||||
fun onUserDataChanged(key: Key<*>)
|
||||
}
|
||||
interface ToolchainProvider {
|
||||
val toolchain: AbstractZigToolchain?
|
||||
|
||||
class UserDataBridge: UserDataHolderBase(), IUserDataBridge {
|
||||
private val listeners = ArrayList<UserDataListener>()
|
||||
override fun <T : Any?> putUserData(key: Key<T>, value: T?) {
|
||||
super.putUserData(key, value)
|
||||
synchronized(listeners) {
|
||||
listeners.forEach { listener ->
|
||||
listener.onUserDataChanged(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun addUserDataChangeListener(listener: UserDataListener) {
|
||||
synchronized(listeners) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeUserDataChangeListener(listener: UserDataListener) {
|
||||
synchronized(listeners) {
|
||||
listeners.remove(listener)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* 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.settings
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.util.xmlb.annotations.Transient
|
||||
import kotlin.io.path.isDirectory
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
data class ZigProjectSettings(
|
||||
var direnv: Boolean = false,
|
||||
var overrideStdPath: Boolean = false,
|
||||
var explicitPathToStd: String? = null,
|
||||
var toolchainPath: String? = null
|
||||
): ZigProjectConfigurationProvider.Settings, ZigProjectConfigurationProvider.ToolchainProvider {
|
||||
override fun apply(project: Project) {
|
||||
project.zigProjectSettings.loadState(this)
|
||||
}
|
||||
|
||||
@get:Transient
|
||||
@set:Transient
|
||||
override var toolchain: LocalZigToolchain?
|
||||
get() {
|
||||
val nioPath = toolchainPath?.toNioPathOrNull() ?: return null
|
||||
if (!nioPath.isDirectory()) {
|
||||
return null
|
||||
}
|
||||
return LocalZigToolchain(nioPath)
|
||||
}
|
||||
set(value) {
|
||||
toolchainPath = value?.location?.pathString
|
||||
}
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
/*
|
||||
* 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.settings
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.stdlib.ZigSyntheticLibrary
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.intellij.openapi.components.*
|
||||
import com.intellij.openapi.project.Project
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Service(Service.Level.PROJECT)
|
||||
@State(
|
||||
name = "ZigProjectSettings",
|
||||
storages = [Storage("zigbrains.xml")]
|
||||
)
|
||||
class ZigProjectSettingsService(val project: Project): PersistentStateComponent<ZigProjectSettings> {
|
||||
@Volatile
|
||||
private var state = ZigProjectSettings()
|
||||
|
||||
override fun getState(): ZigProjectSettings {
|
||||
return state.copy()
|
||||
}
|
||||
|
||||
fun setState(value: ZigProjectSettings) {
|
||||
this.state = value
|
||||
zigCoroutineScope.launch {
|
||||
ZigSyntheticLibrary.reload(project, value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadState(state: ZigProjectSettings) {
|
||||
setState(state)
|
||||
}
|
||||
|
||||
fun isModified(otherData: ZigProjectSettings): Boolean {
|
||||
return state != otherData
|
||||
}
|
||||
}
|
||||
|
||||
val Project.zigProjectSettings get() = service<ZigProjectSettingsService>()
|
|
@ -20,7 +20,7 @@
|
|||
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.falsepattern.zigbrains.project.toolchain.stdlib
|
||||
package com.falsepattern.zigbrains.project.stdlib
|
||||
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.AdditionalLibraryRootsProvider
|
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
* 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.stdlib
|
||||
|
||||
import com.falsepattern.zigbrains.Icons
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
|
||||
import com.intellij.navigation.ItemPresentation
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.guessProjectDir
|
||||
import com.intellij.openapi.roots.SyntheticLibrary
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.refreshAndFindVirtualDirectory
|
||||
import com.intellij.platform.backend.workspace.WorkspaceModel
|
||||
import com.intellij.platform.backend.workspace.toVirtualFileUrl
|
||||
import com.intellij.platform.workspace.jps.entities.*
|
||||
import com.intellij.project.isDirectoryBased
|
||||
import com.intellij.project.stateStore
|
||||
import com.intellij.workspaceModel.ide.legacyBridge.LegacyBridgeJpsEntitySourceFactory
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.*
|
||||
import javax.swing.Icon
|
||||
|
||||
class ZigSyntheticLibrary(val project: Project) : SyntheticLibrary(), ItemPresentation {
|
||||
private var toolchain: ZigToolchain? = ZigToolchainService.getInstance(project).toolchain
|
||||
private val roots by lazy {
|
||||
runBlocking {getRoot(toolchain, project)}?.let { setOf(it) } ?: emptySet()
|
||||
}
|
||||
|
||||
private val name by lazy {
|
||||
getName(toolchain, project)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is ZigSyntheticLibrary)
|
||||
return false
|
||||
|
||||
return toolchain == other.toolchain
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return Objects.hash(roots)
|
||||
}
|
||||
|
||||
override fun getPresentableText(): String {
|
||||
return name
|
||||
}
|
||||
|
||||
override fun getIcon(unused: Boolean): Icon {
|
||||
return Icons.Zig
|
||||
}
|
||||
|
||||
override fun getSourceRoots(): Collection<VirtualFile> {
|
||||
return roots
|
||||
}
|
||||
|
||||
override fun isShowInExternalLibrariesNode(): Boolean {
|
||||
return !roots.isEmpty()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ZIG_LIBRARY_ID = "Zig SDK"
|
||||
private const val ZIG_MODULE_ID = "ZigBrains"
|
||||
private val libraryTableId = LibraryTableId.ProjectLibraryTableId
|
||||
private val libraryId = LibraryId(ZIG_LIBRARY_ID, libraryTableId)
|
||||
private val moduleId = ModuleId(ZIG_MODULE_ID)
|
||||
suspend fun reload(project: Project, toolchain: ZigToolchain?) {
|
||||
val root = getRoot(toolchain, project)
|
||||
if (root != null) {
|
||||
add(project, root)
|
||||
} else {
|
||||
remove(project)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun remove(project: Project) {
|
||||
val workspaceModel = WorkspaceModel.getInstance(project)
|
||||
workspaceModel.update("Update Zig std") { builder ->
|
||||
builder.resolve(moduleId)?.let { moduleEntity ->
|
||||
builder.removeEntity(moduleEntity)
|
||||
}
|
||||
builder.resolve(libraryId)?.let { libraryEntity ->
|
||||
builder.removeEntity(libraryEntity)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun add(project: Project, root: VirtualFile) {
|
||||
val workspaceModel = WorkspaceModel.getInstance(project)
|
||||
val libRoot = LibraryRoot(root.toVirtualFileUrl(workspaceModel.getVirtualFileUrlManager()), LibraryRootTypeId.SOURCES)
|
||||
|
||||
var baseModuleDirFile: VirtualFile? = null
|
||||
if (project.isDirectoryBased) {
|
||||
baseModuleDirFile = project.stateStore.directoryStorePath?.refreshAndFindVirtualDirectory()
|
||||
}
|
||||
if (baseModuleDirFile == null) {
|
||||
baseModuleDirFile = project.guessProjectDir()
|
||||
}
|
||||
val baseModuleDir = baseModuleDirFile?.toVirtualFileUrl(workspaceModel.getVirtualFileUrlManager()) ?: return
|
||||
workspaceModel.update("Update Zig std") { builder ->
|
||||
builder.resolve(moduleId)?.let { moduleEntity ->
|
||||
builder.removeEntity(moduleEntity)
|
||||
}
|
||||
val moduleEntitySource = LegacyBridgeJpsEntitySourceFactory.getInstance(project)
|
||||
.createEntitySourceForModule(baseModuleDir, null)
|
||||
|
||||
val moduleEntity = builder.addEntity(ModuleEntity(ZIG_MODULE_ID, emptyList(), moduleEntitySource))
|
||||
|
||||
builder.resolve(libraryId)?.let { libraryEntity ->
|
||||
builder.removeEntity(libraryEntity)
|
||||
}
|
||||
val libraryEntitySource = LegacyBridgeJpsEntitySourceFactory
|
||||
.getInstance(project)
|
||||
.createEntitySourceForProjectLibrary(null)
|
||||
val libraryEntity = LibraryEntity(
|
||||
ZIG_LIBRARY_ID,
|
||||
libraryTableId, emptyList(),
|
||||
libraryEntitySource
|
||||
) {
|
||||
roots.add(libRoot)
|
||||
}
|
||||
builder.addEntity(libraryEntity)
|
||||
builder.modifyModuleEntity(moduleEntity) {
|
||||
val dep = LibraryDependency(libraryId, false, DependencyScope.COMPILE)
|
||||
dependencies.clear()
|
||||
dependencies.add(dep)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getName(
|
||||
toolchain: ZigToolchain?,
|
||||
project: Project
|
||||
): String {
|
||||
val tc = toolchain ?: return "Zig"
|
||||
toolchain.name?.let { return it }
|
||||
runBlocking { tc.zig.getEnv(project) }
|
||||
.mapCatching { it.version }
|
||||
.getOrNull()
|
||||
?.let { return "Zig $it" }
|
||||
return "Zig"
|
||||
}
|
||||
|
||||
suspend fun getRoot(
|
||||
toolchain: ZigToolchain?,
|
||||
project: Project
|
||||
): VirtualFile? {
|
||||
//TODO universal
|
||||
if (toolchain !is LocalZigToolchain) {
|
||||
return null
|
||||
}
|
||||
if (toolchain.std != null) run {
|
||||
val ePath = toolchain.std
|
||||
if (ePath.isAbsolute) {
|
||||
val roots = ePath.refreshAndFindVirtualDirectory() ?: return@run
|
||||
return roots
|
||||
}
|
||||
val stdPath = toolchain.location.resolve(ePath)
|
||||
if (stdPath.isAbsolute) {
|
||||
val roots = stdPath.refreshAndFindVirtualDirectory() ?: return@run
|
||||
return roots
|
||||
}
|
||||
}
|
||||
val stdPath = toolchain.zig.getEnv(project).mapCatching { it.stdPath(toolchain, project) }.getOrNull() ?: return null
|
||||
val roots = stdPath.refreshAndFindVirtualDirectory() ?: return null
|
||||
return roots
|
||||
}
|
|
@ -22,11 +22,12 @@
|
|||
|
||||
package com.falsepattern.zigbrains.project.steps.discovery
|
||||
|
||||
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
|
||||
import com.falsepattern.zigbrains.project.steps.discovery.ZigStepDiscoveryListener.ErrorType
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
|
||||
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
|
||||
|
@ -75,7 +76,7 @@ class ZigStepDiscoveryService(private val project: Project) {
|
|||
|
||||
private tailrec suspend fun doReload() {
|
||||
preReload()
|
||||
val toolchain = project.zigProjectSettings.state.toolchain ?: run {
|
||||
val toolchain = ZigToolchainService.getInstance(project).toolchain ?: run {
|
||||
errorReload(ErrorType.MissingToolchain)
|
||||
return
|
||||
}
|
||||
|
@ -83,13 +84,14 @@ class ZigStepDiscoveryService(private val project: Project) {
|
|||
val result = zig.callWithArgs(
|
||||
project.guessProjectDir()?.toNioPathOrNull(),
|
||||
"build", "-l",
|
||||
timeoutMillis = currentTimeoutSec * 1000L
|
||||
timeoutMillis = currentTimeoutSec * 1000L,
|
||||
ipcProject = project
|
||||
).getOrElse { throwable ->
|
||||
errorReload(ErrorType.MissingZigExe, throwable.message)
|
||||
null
|
||||
}
|
||||
if (result == null) {
|
||||
{}
|
||||
|
||||
} else if (result.checkSuccess(LOG)) {
|
||||
currentTimeoutSec = DEFAULT_TIMEOUT_SEC
|
||||
val lines = result.stdoutLines
|
||||
|
@ -106,7 +108,7 @@ class ZigStepDiscoveryService(private val project: Project) {
|
|||
} else if (result.isTimeout) {
|
||||
timeoutReload(currentTimeoutSec)
|
||||
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)
|
||||
} else {
|
||||
errorReload(ErrorType.GeneralError, result.stderr)
|
||||
|
@ -123,7 +125,7 @@ class ZigStepDiscoveryService(private val project: Project) {
|
|||
}
|
||||
|
||||
private suspend fun dispatchReload() {
|
||||
withEDTContext {
|
||||
withEDTContext(ModalityState.defaultModalityState()) {
|
||||
FileDocumentManager.getInstance().saveAllDocuments()
|
||||
}
|
||||
doReload()
|
||||
|
@ -158,6 +160,6 @@ val Project.zigStepDiscovery get() = service<ZigStepDiscoveryService>()
|
|||
|
||||
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)
|
|
@ -28,7 +28,7 @@ import com.intellij.openapi.project.Project
|
|||
import com.intellij.ui.SimpleTextAttributes
|
||||
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 {
|
||||
icon = displayIcon
|
||||
myName = displayName
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
|
||||
package com.falsepattern.zigbrains.project.steps.ui
|
||||
|
||||
import com.falsepattern.zigbrains.Icons
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.project.execution.build.ZigConfigTypeBuild
|
||||
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.zigStepDiscovery
|
||||
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.RunManager
|
||||
import com.intellij.execution.RunnerAndConfigurationSettings
|
||||
|
@ -38,47 +41,58 @@ import com.intellij.icons.AllIcons
|
|||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.DefaultActionGroup
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.SimpleToolWindowPanel
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.wm.ToolWindow
|
||||
import com.intellij.openapi.wm.ToolWindowManager
|
||||
import com.intellij.ui.AnimatedIcon
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.ui.components.JBScrollPane
|
||||
import com.intellij.ui.components.JBTextArea
|
||||
import com.intellij.ui.components.panels.VerticalLayout
|
||||
import com.intellij.ui.content.Content
|
||||
import com.intellij.ui.content.ContentFactory
|
||||
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.GridBagLayout
|
||||
import java.awt.event.MouseAdapter
|
||||
import java.awt.event.MouseEvent
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.swing.BoxLayout
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.SwingConstants
|
||||
import javax.swing.tree.DefaultMutableTreeNode
|
||||
import javax.swing.tree.DefaultTreeModel
|
||||
import javax.swing.tree.MutableTreeNode
|
||||
import javax.swing.tree.TreePath
|
||||
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
class BuildToolWindowContext(private val project: Project): Disposable {
|
||||
val rootNode: DefaultMutableTreeNode = DefaultMutableTreeNode(BaseNodeDescriptor<Any>(project, project.name, AllIcons.Actions.ProjectDirectory))
|
||||
private val buildZig: DefaultMutableTreeNode = DefaultMutableTreeNode(BaseNodeDescriptor<Any>(project, ZigBrainsBundle.message("build.tool.window.tree.steps.label"), Icons.Zig))
|
||||
init {
|
||||
rootNode.add(buildZig)
|
||||
inner class TreeBox() {
|
||||
val panel = JPanel(VerticalLayout(0))
|
||||
val root = DefaultMutableTreeNode(BaseNodeDescriptor<Any>(project, ""))
|
||||
val model = DefaultTreeModel(root)
|
||||
val tree = Tree(model).also { it.isRootVisible = false }
|
||||
}
|
||||
private val viewPanel = JPanel(VerticalLayout(0))
|
||||
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(stepsBox.panel)
|
||||
stepsBox.panel.setNotScanned()
|
||||
|
||||
private fun setViewportTree(viewport: JBScrollPane) {
|
||||
val model = DefaultTreeModel(rootNode)
|
||||
val tree = Tree(model)
|
||||
tree.expandPath(TreePath(model.getPathToRoot(buildZig)))
|
||||
viewport.setViewportView(tree)
|
||||
tree.addMouseListener(object : MouseAdapter() {
|
||||
stepsBox.tree.addMouseListener(object : MouseAdapter() {
|
||||
override fun mouseClicked(e: MouseEvent) {
|
||||
if (e.clickCount == 2) {
|
||||
val node = 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
|
||||
|
@ -97,6 +111,58 @@ class BuildToolWindowContext(private val project: Project): Disposable {
|
|||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (buildBox != null) {
|
||||
viewPanel.add(JBLabel(ZigBrainsBundle.message("build.tool.window.tree.build.label")))
|
||||
viewPanel.add(buildBox.panel)
|
||||
buildBox.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()) {
|
||||
buildBox.root.removeAllChildren()
|
||||
buildBox.panel.setNoBuilds()
|
||||
return@withEDTContext
|
||||
}
|
||||
val allNodes = ArrayList(ipc.nodes)
|
||||
val existingNodes = ArrayList<ZigIPCService.IPCTreeNode>()
|
||||
val removedNodes = ArrayList<ZigIPCService.IPCTreeNode>()
|
||||
buildBox.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 { buildBox.root.remove(it) }
|
||||
newNodes.forEach { buildBox.root.add(it) }
|
||||
if (removedNodes.isNotEmpty() || newNodes.isNotEmpty()) {
|
||||
buildBox.model.reload(buildBox.root)
|
||||
}
|
||||
if (buildBox.root.childCount == 0) {
|
||||
buildBox.panel.setNoBuilds()
|
||||
} else {
|
||||
buildBox.panel.setViewportBody(buildBox.tree)
|
||||
}
|
||||
for (bn in allNodes) {
|
||||
expandRecursively(buildBox, bn)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createContentPanel(): Content {
|
||||
|
@ -120,51 +186,62 @@ class BuildToolWindowContext(private val project: Project): Disposable {
|
|||
c.weighty = 1.0
|
||||
c.fill = GridBagConstraints.BOTH
|
||||
val viewport = JBScrollPane()
|
||||
viewport.setViewportNoContent()
|
||||
viewport.setViewportView(viewPanel)
|
||||
body.add(viewport, c)
|
||||
val content = ContentFactory.getInstance().createContent(contentPanel, "", false)
|
||||
content.putUserData(VIEWPORT, viewport)
|
||||
return content
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
|
||||
live.set(false)
|
||||
}
|
||||
|
||||
companion object {
|
||||
suspend fun create(project: Project, window: ToolWindow) {
|
||||
withEDTContext {
|
||||
withEDTContext(ModalityState.any()) {
|
||||
val context = BuildToolWindowContext(project)
|
||||
Disposer.register(context, project.zigStepDiscovery.register(context.BuildReloadListener()))
|
||||
Disposer.register(window.disposable, context)
|
||||
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 {
|
||||
override suspend fun preReload() {
|
||||
getViewport(project)?.setViewportLoading()
|
||||
stepsBox.panel.setRunningZigBuild()
|
||||
}
|
||||
|
||||
override suspend fun postReload(steps: List<Pair<String, String?>>) {
|
||||
buildZig.removeAllChildren()
|
||||
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
|
||||
}
|
||||
buildZig.add(DefaultMutableTreeNode(StepNodeDescriptor(project, task, icon, description)))
|
||||
stepsBox.root.add(DefaultMutableTreeNode(StepNodeDescriptor(project, task, icon, description)))
|
||||
}
|
||||
withEDTContext {
|
||||
getViewport(project)?.let { setViewportTree(it) }
|
||||
withEDTContext(ModalityState.any()) {
|
||||
stepsBox.model.reload(stepsBox.root)
|
||||
stepsBox.panel.setViewportBody(stepsBox.tree)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun errorReload(type: ZigStepDiscoveryListener.ErrorType, details: String?) {
|
||||
withEDTContext {
|
||||
getViewport(project)?.setViewportError(ZigBrainsBundle.message(when(type) {
|
||||
withEDTContext(ModalityState.any()) {
|
||||
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"
|
||||
|
@ -174,22 +251,32 @@ class BuildToolWindowContext(private val project: Project): Disposable {
|
|||
}
|
||||
|
||||
override suspend fun timeoutReload(seconds: Int) {
|
||||
withEDTContext {
|
||||
getViewport(project)?.setViewportError(ZigBrainsBundle.message("build.tool.window.status.timeout", seconds), null)
|
||||
withEDTContext(ModalityState.any()) {
|
||||
stepsBox.panel.setViewportError(ZigBrainsBundle.message("build.tool.window.status.timeout", seconds), null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun JBScrollPane.setViewportLoading() {
|
||||
setViewportView(JBLabel(ZigBrainsBundle.message("build.tool.window.status.loading"), AnimatedIcon.Default(), SwingConstants.CENTER))
|
||||
private fun JPanel.setViewportBody(component: Component) {
|
||||
removeAll()
|
||||
add(component)
|
||||
repaint()
|
||||
}
|
||||
|
||||
private fun JBScrollPane.setViewportNoContent() {
|
||||
setViewportView(JBLabel(ZigBrainsBundle.message("build.tool.window.status.not-scanned"), AllIcons.General.Information, SwingConstants.CENTER))
|
||||
private fun JPanel.setRunningZigBuild() {
|
||||
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()
|
||||
result.layout = BoxLayout(result, BoxLayout.Y_AXIS)
|
||||
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)
|
||||
result.add(scroll)
|
||||
}
|
||||
setViewportView(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)
|
||||
setViewportBody(result)
|
||||
}
|
||||
|
||||
private fun getExistingRunConfig(manager: RunManager, stepName: String): RunnerAndConfigurationSettings? {
|
||||
|
@ -222,5 +302,3 @@ private fun getExistingRunConfig(manager: RunManager, stepName: String): RunnerA
|
|||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private val VIEWPORT = Key.create<JBScrollPane>("MODEL")
|
||||
|
|
|
@ -23,12 +23,13 @@
|
|||
package com.falsepattern.zigbrains.project.steps.ui
|
||||
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.wm.ToolWindow
|
||||
import com.intellij.openapi.wm.ToolWindowFactory
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class BuildToolWindowFactory: ToolWindowFactory {
|
||||
class BuildToolWindowFactory: ToolWindowFactory, DumbAware {
|
||||
override fun createToolWindowContent(project: Project, toolWindow: ToolWindow) {
|
||||
project.zigCoroutineScope.launch {
|
||||
BuildToolWindowContext.create(project, toolWindow)
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* 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.toolchain
|
||||
|
||||
import com.falsepattern.zigbrains.direnv.DirenvCmd
|
||||
import com.falsepattern.zigbrains.direnv.emptyEnv
|
||||
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.UserDataHolder
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import kotlinx.serialization.json.*
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
class LocalZigToolchainProvider: ZigToolchainProvider<LocalZigToolchain> {
|
||||
override suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): LocalZigToolchain? {
|
||||
val env = if (project != null && (extraData.getUserData(LocalZigToolchain.DIRENV_KEY) ?: project.zigProjectSettings.state.direnv)) {
|
||||
DirenvCmd.importDirenv(project)
|
||||
} else {
|
||||
emptyEnv
|
||||
}
|
||||
val zigExePath = env.findExecutableOnPATH("zig") ?: return null
|
||||
return LocalZigToolchain(zigExePath.parent)
|
||||
}
|
||||
|
||||
override val serialMarker: String
|
||||
get() = "local"
|
||||
|
||||
override fun deserialize(data: JsonElement): LocalZigToolchain? {
|
||||
if (data !is JsonObject)
|
||||
return null
|
||||
|
||||
val loc = data["location"] as? JsonPrimitive ?: return null
|
||||
val path = loc.content.toNioPathOrNull() ?: return null
|
||||
return LocalZigToolchain(path)
|
||||
}
|
||||
|
||||
override fun canSerialize(toolchain: AbstractZigToolchain): Boolean {
|
||||
return toolchain is LocalZigToolchain
|
||||
}
|
||||
|
||||
override fun serialize(toolchain: LocalZigToolchain): JsonElement {
|
||||
return buildJsonObject {
|
||||
put("location", toolchain.location.pathString)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.toolchain
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainListService.MyState
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.resolve
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.toRef
|
||||
import com.falsepattern.zigbrains.shared.UUIDMapSerializable
|
||||
import com.falsepattern.zigbrains.shared.UUIDStorage
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.State
|
||||
import com.intellij.openapi.components.Storage
|
||||
import com.intellij.openapi.components.service
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
@State(
|
||||
name = "ZigToolchainList",
|
||||
storages = [Storage("zigbrains.xml")]
|
||||
)
|
||||
class ZigToolchainListService: UUIDMapSerializable.Converting<ZigToolchain, ZigToolchain.Ref, MyState>(MyState()) {
|
||||
override fun serialize(value: ZigToolchain) = value.toRef()
|
||||
override fun deserialize(value: ZigToolchain.Ref) = value.resolve()
|
||||
override fun getStorage(state: MyState) = state.toolchains
|
||||
override fun updateStorage(state: MyState, storage: ToolchainStorage) = state.copy(toolchains = storage)
|
||||
|
||||
data class MyState(@JvmField val toolchains: ToolchainStorage = emptyMap())
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(): ZigToolchainListService = service<ZigToolchainListService>()
|
||||
}
|
||||
}
|
||||
|
||||
inline val zigToolchainList: ZigToolchainListService get() = ZigToolchainListService.getInstance()
|
||||
|
||||
private typealias ToolchainStorage = UUIDStorage<ZigToolchain.Ref>
|
|
@ -1,69 +0,0 @@
|
|||
/*
|
||||
* 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.toolchain
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider.Companion.EXTENSION_POINT_NAME
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.UserDataHolder
|
||||
import com.intellij.util.xmlb.Converter
|
||||
import kotlinx.serialization.json.*
|
||||
|
||||
sealed interface ZigToolchainProvider<in T: AbstractZigToolchain> {
|
||||
suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): AbstractZigToolchain?
|
||||
|
||||
val serialMarker: String
|
||||
fun deserialize(data: JsonElement): AbstractZigToolchain?
|
||||
fun canSerialize(toolchain: AbstractZigToolchain): Boolean
|
||||
fun serialize(toolchain: T): JsonElement
|
||||
|
||||
companion object {
|
||||
val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigToolchainProvider<*>>("com.falsepattern.zigbrains.toolchainProvider")
|
||||
|
||||
suspend fun suggestToolchain(project: Project?, extraData: UserDataHolder): AbstractZigToolchain? {
|
||||
return EXTENSION_POINT_NAME.extensionList.firstNotNullOfOrNull { it.suggestToolchain(project, extraData) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun <T: AbstractZigToolchain> ZigToolchainProvider<T>.serialize(toolchain: AbstractZigToolchain) = serialize(toolchain as T)
|
||||
|
||||
class ZigToolchainConverter: Converter<AbstractZigToolchain>() {
|
||||
override fun fromString(value: String): AbstractZigToolchain? {
|
||||
val json = Json.parseToJsonElement(value) as? JsonObject ?: return null
|
||||
val marker = (json["marker"] as? JsonPrimitive)?.contentOrNull ?: return null
|
||||
val data = json["data"] ?: return null
|
||||
val provider = EXTENSION_POINT_NAME.extensionList.find { it.serialMarker == marker } ?: return null
|
||||
return provider.deserialize(data)
|
||||
}
|
||||
|
||||
override fun toString(value: AbstractZigToolchain): String? {
|
||||
val provider = EXTENSION_POINT_NAME.extensionList.find { it.canSerialize(value) } ?: return null
|
||||
return buildJsonObject {
|
||||
put("marker", provider.serialMarker)
|
||||
put("data", provider.serialize(value))
|
||||
}.toString()
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* 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.toolchain
|
||||
|
||||
import com.falsepattern.zigbrains.project.stdlib.ZigSyntheticLibrary
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.shared.asUUID
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.components.*
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.util.xmlb.annotations.Attribute
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
|
||||
@Service(Service.Level.PROJECT)
|
||||
@State(
|
||||
name = "ZigToolchain",
|
||||
storages = [Storage("zigbrains.xml")]
|
||||
)
|
||||
class ZigToolchainService(private val project: Project): SerializablePersistentStateComponent<ZigToolchainService.State>(State()) {
|
||||
var toolchainUUID: UUID?
|
||||
get() = state.toolchain.ifBlank { null }?.asUUID()?.takeIf {
|
||||
if (it in zigToolchainList) {
|
||||
true
|
||||
} else {
|
||||
updateState {
|
||||
it.copy(toolchain = "")
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
set(value) {
|
||||
updateState {
|
||||
it.copy(toolchain = value?.toString() ?: "")
|
||||
}
|
||||
zigCoroutineScope.launch(Dispatchers.EDT) {
|
||||
ZigSyntheticLibrary.reload(project, toolchain)
|
||||
}
|
||||
}
|
||||
|
||||
val toolchain: ZigToolchain?
|
||||
get() = toolchainUUID?.let { zigToolchainList[it] }
|
||||
|
||||
data class State(
|
||||
@JvmField
|
||||
@Attribute
|
||||
var toolchain: String = ""
|
||||
)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(project: Project): ZigToolchainService = project.service<ZigToolchainService>()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* 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.toolchain.base
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.tools.ZigCompilerTool
|
||||
import com.falsepattern.zigbrains.shared.NamedObject
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.util.xmlb.annotations.Attribute
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* These MUST be stateless and interchangeable! (e.g., immutable data class)
|
||||
*/
|
||||
interface ZigToolchain: NamedObject<ZigToolchain> {
|
||||
val zig: ZigCompilerTool get() = ZigCompilerTool(this)
|
||||
|
||||
val extraData: Map<String, String>
|
||||
|
||||
/**
|
||||
* Returned type must be the same class
|
||||
*/
|
||||
fun withExtraData(map: Map<String, String>): ZigToolchain
|
||||
|
||||
fun workingDirectory(project: Project? = null): Path?
|
||||
|
||||
suspend fun patchCommandLine(commandLine: GeneralCommandLine, project: Project? = null): GeneralCommandLine
|
||||
|
||||
fun pathToExecutable(toolName: String, project: Project? = null): Path
|
||||
|
||||
data class Ref(
|
||||
@JvmField
|
||||
@Attribute
|
||||
val marker: String? = null,
|
||||
@JvmField
|
||||
val data: Map<String, String>? = null,
|
||||
@JvmField
|
||||
val extraData: Map<String, String>? = null,
|
||||
)
|
||||
}
|
||||
|
||||
fun <T: ZigToolchain> T.withExtraData(key: String, value: String?): T {
|
||||
val newMap = HashMap<String, String>()
|
||||
newMap.putAll(extraData.filter { (theKey, _) -> theKey != key})
|
||||
if (value != null) {
|
||||
newMap[key] = value
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return withExtraData(newMap) as T
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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.toolchain.base
|
||||
|
||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
||||
import com.falsepattern.zigbrains.project.toolchain.ui.ImmutableElementPanel
|
||||
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
||||
import com.falsepattern.zigbrains.shared.ui.UUIDComboBoxDriver.Companion.wrapModal
|
||||
import com.intellij.openapi.ui.NamedConfigurable
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import java.util.*
|
||||
import java.util.function.Supplier
|
||||
import javax.swing.JComponent
|
||||
|
||||
abstract class ZigToolchainConfigurable<T: ZigToolchain>(
|
||||
val uuid: UUID,
|
||||
tc: T,
|
||||
val data: ZigProjectConfigurationProvider.IUserDataBridge?,
|
||||
private val modal: Boolean
|
||||
): NamedConfigurable<UUID>() {
|
||||
var toolchain: T = tc
|
||||
set(value) {
|
||||
zigToolchainList[uuid] = value
|
||||
field = value
|
||||
}
|
||||
|
||||
init {
|
||||
data?.putUserData(TOOLCHAIN_KEY, Supplier{toolchain})
|
||||
}
|
||||
private var myViews: List<ImmutableElementPanel<T>> = emptyList()
|
||||
|
||||
abstract fun createPanel(): ImmutableElementPanel<T>
|
||||
|
||||
override fun createOptionsPanel(): JComponent? {
|
||||
var views = myViews
|
||||
if (views.isEmpty()) {
|
||||
views = ArrayList<ImmutableElementPanel<T>>()
|
||||
views.add(createPanel())
|
||||
views.addAll(createZigToolchainExtensionPanels(data, if (modal) PanelState.ModalEditor else PanelState.ListEditor))
|
||||
myViews = views
|
||||
}
|
||||
val p = panel {
|
||||
views.forEach { it.attach(this@panel) }
|
||||
}.withMinimumWidth(20)
|
||||
views.forEach { it.reset(toolchain) }
|
||||
return wrapModal(p, modal)
|
||||
}
|
||||
|
||||
override fun getEditableObject(): UUID? {
|
||||
return uuid
|
||||
}
|
||||
|
||||
override fun getBannerSlogan(): @NlsContexts.DetailedDescription String? {
|
||||
return displayName
|
||||
}
|
||||
|
||||
override fun getDisplayName(): @NlsContexts.ConfigurableName String? {
|
||||
return toolchain.name
|
||||
}
|
||||
|
||||
override fun isModified(): Boolean {
|
||||
return myViews.any { it.isModified(toolchain) }
|
||||
}
|
||||
|
||||
override fun apply() {
|
||||
toolchain = myViews.fold(toolchain) { tc, view -> view.apply(tc) ?: tc }
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
myViews.forEach { it.reset(toolchain) }
|
||||
}
|
||||
|
||||
override fun disposeUIResources() {
|
||||
myViews.forEach { it.dispose() }
|
||||
myViews = emptyList()
|
||||
super.disposeUIResources()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TOOLCHAIN_KEY: Key<Supplier<ZigToolchain?>> = Key.create("TOOLCHAIN")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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.toolchain.base
|
||||
|
||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
||||
import com.falsepattern.zigbrains.project.toolchain.ui.ImmutableElementPanel
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
|
||||
private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigToolchainExtensionsProvider>("com.falsepattern.zigbrains.toolchainExtensionsProvider")
|
||||
|
||||
interface ZigToolchainExtensionsProvider {
|
||||
fun <T : ZigToolchain> createExtensionPanel(sharedState: ZigProjectConfigurationProvider.IUserDataBridge?, state: PanelState): ImmutableElementPanel<T>?
|
||||
val index: Int
|
||||
}
|
||||
|
||||
fun <T: ZigToolchain> createZigToolchainExtensionPanels(sharedState: ZigProjectConfigurationProvider.IUserDataBridge?, state: PanelState): List<ImmutableElementPanel<T>> {
|
||||
return EXTENSION_POINT_NAME.extensionList.sortedBy{ it.index }.mapNotNull {
|
||||
it.createExtensionPanel(sharedState, state)
|
||||
}
|
||||
}
|
||||
|
||||
enum class PanelState {
|
||||
ProjectEditor,
|
||||
ListEditor,
|
||||
ModalEditor
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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.toolchain.base
|
||||
|
||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
||||
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.UserDataHolder
|
||||
import com.intellij.ui.SimpleColoredComponent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import java.util.*
|
||||
|
||||
private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigToolchainProvider>("com.falsepattern.zigbrains.toolchainProvider")
|
||||
|
||||
internal interface ZigToolchainProvider {
|
||||
val serialMarker: String
|
||||
fun isCompatible(toolchain: ZigToolchain): Boolean
|
||||
fun deserialize(data: Map<String, String>): ZigToolchain?
|
||||
fun serialize(toolchain: ZigToolchain): Map<String, String>
|
||||
fun matchesSuggestion(toolchain: ZigToolchain, suggestion: ZigToolchain): Boolean
|
||||
fun createConfigurable(uuid: UUID, toolchain: ZigToolchain, data: ZigProjectConfigurationProvider.IUserDataBridge?, modal: Boolean): ZigToolchainConfigurable<*>
|
||||
suspend fun suggestToolchains(project: Project?, data: UserDataHolder): Flow<ZigToolchain>
|
||||
fun render(toolchain: ZigToolchain, component: SimpleColoredComponent, isSuggestion: Boolean, isSelected: Boolean)
|
||||
}
|
||||
|
||||
fun ZigToolchain.Ref.resolve(): ZigToolchain? {
|
||||
val marker = this.marker ?: return null
|
||||
val data = this.data ?: return null
|
||||
val provider = EXTENSION_POINT_NAME.extensionList.find { it.serialMarker == marker } ?: return null
|
||||
return provider.deserialize(data)?.let { tc -> this.extraData?.let { extraData -> tc.withExtraData(extraData) }}
|
||||
}
|
||||
|
||||
fun ZigToolchain.toRef(): ZigToolchain.Ref {
|
||||
val provider = EXTENSION_POINT_NAME.extensionList.find { it.isCompatible(this) } ?: throw IllegalStateException()
|
||||
return ZigToolchain.Ref(provider.serialMarker, provider.serialize(this), this.extraData)
|
||||
}
|
||||
|
||||
fun ZigToolchain.createNamedConfigurable(uuid: UUID, data: ZigProjectConfigurationProvider.IUserDataBridge?, modal: Boolean): ZigToolchainConfigurable<*> {
|
||||
val provider = EXTENSION_POINT_NAME.extensionList.find { it.isCompatible(this) } ?: throw IllegalStateException()
|
||||
return provider.createConfigurable(uuid, this, data, modal)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun suggestZigToolchains(project: Project? = null, data: UserDataHolder = emptyData): Flow<ZigToolchain> {
|
||||
val existing = zigToolchainList.map { (_, tc) -> tc }
|
||||
return EXTENSION_POINT_NAME.extensionList.asFlow().flatMapConcat { ext ->
|
||||
val compatibleExisting = existing.filter { ext.isCompatible(it) }
|
||||
val suggestions = ext.suggestToolchains(project, data)
|
||||
suggestions.filter { suggestion ->
|
||||
compatibleExisting.none { existing -> ext.matchesSuggestion(existing, suggestion) }
|
||||
}
|
||||
}.flowOn(Dispatchers.IO)
|
||||
}
|
||||
|
||||
fun ZigToolchain.render(component: SimpleColoredComponent, isSuggestion: Boolean, isSelected: Boolean) {
|
||||
val provider = EXTENSION_POINT_NAME.extensionList.find { it.isCompatible(this) } ?: throw IllegalStateException()
|
||||
return provider.render(this, component, isSuggestion, isSelected)
|
||||
}
|
||||
|
||||
private val emptyData = object: UserDataHolder {
|
||||
override fun <T : Any?> getUserData(key: Key<T?>): T? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun <T : Any?> putUserData(key: Key<T?>, value: T?) {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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.toolchain.downloader
|
||||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.local.suggestedLocalToolchainPath
|
||||
import com.falsepattern.zigbrains.shared.downloader.Downloader
|
||||
import java.awt.Component
|
||||
|
||||
class LocalToolchainDownloader(component: Component) : Downloader<LocalZigToolchain, ZigVersionInfo>(component) {
|
||||
override val windowTitle get() = ZigBrainsBundle.message("settings.toolchain.downloader.title")
|
||||
override val versionInfoFetchTitle get() = ZigBrainsBundle.message("settings.toolchain.downloader.progress.fetch")
|
||||
override val suggestedPath get() = suggestedLocalToolchainPath
|
||||
override fun downloadProgressTitle(version: ZigVersionInfo) = ZigBrainsBundle.message("settings.toolchain.downloader.progress.install", version.version.rawVersion)
|
||||
override fun localSelector() = LocalToolchainSelector(component)
|
||||
override suspend fun downloadVersionList() = ZigVersionInfo.downloadVersionList()
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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.toolchain.downloader
|
||||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
||||
import com.falsepattern.zigbrains.shared.downloader.LocalSelector
|
||||
import com.falsepattern.zigbrains.shared.withUniqueName
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptor
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||
import com.intellij.platform.ide.progress.ModalTaskOwner
|
||||
import com.intellij.platform.ide.progress.TaskCancellation
|
||||
import com.intellij.platform.ide.progress.withModalProgress
|
||||
import java.awt.Component
|
||||
import java.nio.file.Path
|
||||
|
||||
class LocalToolchainSelector(component: Component): LocalSelector<LocalZigToolchain>(component) {
|
||||
override val windowTitle: String
|
||||
get() = ZigBrainsBundle.message("settings.toolchain.local-selector.title")
|
||||
override val descriptor: FileChooserDescriptor
|
||||
get() = FileChooserDescriptorFactory.createSingleFolderDescriptor()
|
||||
.withTitle(ZigBrainsBundle.message("settings.toolchain.local-selector.chooser.title"))
|
||||
|
||||
override suspend fun verify(path: Path): VerifyResult {
|
||||
var tc = resolve(path, null)
|
||||
var result: VerifyResult
|
||||
if (tc == null) {
|
||||
result = VerifyResult(
|
||||
null,
|
||||
false,
|
||||
AllIcons.General.Error,
|
||||
ZigBrainsBundle.message("settings.toolchain.local-selector.state.invalid"),
|
||||
)
|
||||
} else {
|
||||
val existingToolchain = zigToolchainList
|
||||
.mapNotNull { it.second as? LocalZigToolchain }
|
||||
.firstOrNull { it.location == tc.location }
|
||||
if (existingToolchain != null) {
|
||||
result = VerifyResult(
|
||||
null,
|
||||
true,
|
||||
AllIcons.General.Warning,
|
||||
existingToolchain.name?.let { ZigBrainsBundle.message("settings.toolchain.local-selector.state.already-exists-named", it) }
|
||||
?: ZigBrainsBundle.message("settings.toolchain.local-selector.state.already-exists-unnamed")
|
||||
)
|
||||
} else {
|
||||
result = VerifyResult(
|
||||
null,
|
||||
true,
|
||||
AllIcons.General.Information,
|
||||
ZigBrainsBundle.message("settings.toolchain.local-selector.state.ok")
|
||||
)
|
||||
}
|
||||
}
|
||||
if (tc != null) {
|
||||
tc = zigToolchainList.withUniqueName(tc)
|
||||
}
|
||||
return result.copy(name = tc?.name)
|
||||
}
|
||||
|
||||
override suspend fun resolve(path: Path, name: String?): LocalZigToolchain? {
|
||||
return runCatching { withModalProgress(ModalTaskOwner.component(component), "Resolving toolchain", TaskCancellation.cancellable()) {
|
||||
LocalZigToolchain.tryFromPath(path)?.let { it.withName(name ?: it.name) }
|
||||
} }.getOrNull()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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.toolchain.downloader
|
||||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.shared.downloader.VersionInfo
|
||||
import com.falsepattern.zigbrains.shared.downloader.VersionInfo.Tarball
|
||||
import com.falsepattern.zigbrains.shared.downloader.getTarballIfCompatible
|
||||
import com.falsepattern.zigbrains.shared.downloader.tempPluginDir
|
||||
import com.intellij.openapi.progress.coroutineToIndicator
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.util.asSafely
|
||||
import com.intellij.util.download.DownloadableFileService
|
||||
import com.intellij.util.text.SemVer
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.*
|
||||
|
||||
@JvmRecord
|
||||
data class ZigVersionInfo(
|
||||
override val version: SemVer,
|
||||
override val date: String,
|
||||
val docs: String,
|
||||
val notes: String,
|
||||
val src: Tarball?,
|
||||
override val dist: Tarball
|
||||
): VersionInfo {
|
||||
companion object {
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
suspend fun downloadVersionList(): List<ZigVersionInfo> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val service = DownloadableFileService.getInstance()
|
||||
val tempFile = FileUtil.createTempFile(tempPluginDir, "index", ".json", false, false)
|
||||
val desc = service.createFileDescription("https://ziglang.org/download/index.json", tempFile.name)
|
||||
val downloader = service.createDownloader(listOf(desc), ZigBrainsBundle.message("settings.toolchain.downloader.service.index"))
|
||||
val downloadResults = coroutineToIndicator {
|
||||
downloader.download(tempPluginDir)
|
||||
}
|
||||
if (downloadResults.isEmpty())
|
||||
return@withContext emptyList()
|
||||
val index = downloadResults[0].first
|
||||
val info = index.inputStream().use { Json.decodeFromStream<JsonObject>(it) }
|
||||
index.delete()
|
||||
return@withContext info.mapNotNull { (version, data) -> parseVersion(version, data) }.toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseVersion(versionKey: String, data: JsonElement): ZigVersionInfo? {
|
||||
if (data !is JsonObject)
|
||||
return null
|
||||
|
||||
val versionTag = data["version"]?.asSafely<JsonPrimitive>()?.content
|
||||
|
||||
val version = SemVer.parseFromText(versionTag) ?: SemVer.parseFromText(versionKey)
|
||||
?: return null
|
||||
val date = data["date"]?.asSafely<JsonPrimitive>()?.content ?: ""
|
||||
val docs = data["docs"]?.asSafely<JsonPrimitive>()?.content ?: ""
|
||||
val notes = data["notes"]?.asSafely<JsonPrimitive>()?.content ?: ""
|
||||
val src = data["src"]?.asSafely<JsonObject>()?.let { Json.decodeFromJsonElement<Tarball>(it) }
|
||||
val dist = data.firstNotNullOfOrNull { (dist, tb) -> getTarballIfCompatible(dist, tb) }
|
||||
?: return null
|
||||
|
||||
return ZigVersionInfo(version, date, docs, notes, src, dist)
|
||||
}
|
|
@ -20,27 +20,27 @@
|
|||
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.falsepattern.zigbrains.project.toolchain
|
||||
package com.falsepattern.zigbrains.project.toolchain.local
|
||||
|
||||
import com.falsepattern.zigbrains.direnv.DirenvCmd
|
||||
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
|
||||
import com.falsepattern.zigbrains.direnv.DirenvService
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.guessProjectDir
|
||||
import com.intellij.openapi.util.KeyWithDefaultValue
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.openapi.vfs.toNioPathOrNull
|
||||
import java.nio.file.Path
|
||||
|
||||
class LocalZigToolchain(val location: Path): AbstractZigToolchain() {
|
||||
@JvmRecord
|
||||
data class LocalZigToolchain(val location: Path, val std: Path? = null, override val name: String? = null, override val extraData: Map<String, String> = emptyMap()): ZigToolchain {
|
||||
override fun workingDirectory(project: Project?): Path? {
|
||||
return project?.guessProjectDir()?.toNioPathOrNull()
|
||||
}
|
||||
|
||||
override suspend fun patchCommandLine(commandLine: GeneralCommandLine, project: Project?): GeneralCommandLine {
|
||||
if (project != null && (commandLine.getUserData(DIRENV_KEY) ?: project.zigProjectSettings.state.direnv)) {
|
||||
commandLine.withEnvironment(DirenvCmd.importDirenv(project).env)
|
||||
if (project != null && DirenvService.getStateFor(commandLine, project).isEnabled(project)) {
|
||||
commandLine.withEnvironment(DirenvService.getInstance(project).import().env)
|
||||
}
|
||||
return commandLine
|
||||
}
|
||||
|
@ -50,11 +50,17 @@ class LocalZigToolchain(val location: Path): AbstractZigToolchain() {
|
|||
return location.resolve(exeName)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DIRENV_KEY = KeyWithDefaultValue.create<Boolean>("ZIG_LOCAL_DIRENV")
|
||||
override fun withExtraData(map: Map<String, String>): ZigToolchain {
|
||||
return this.copy(extraData = map)
|
||||
}
|
||||
|
||||
override fun withName(newName: String?): LocalZigToolchain {
|
||||
return this.copy(name = newName)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Throws(ExecutionException::class)
|
||||
fun ensureLocal(toolchain: AbstractZigToolchain): LocalZigToolchain {
|
||||
fun ensureLocal(toolchain: ZigToolchain): LocalZigToolchain {
|
||||
if (toolchain is LocalZigToolchain) {
|
||||
return toolchain
|
||||
} else {
|
||||
|
@ -62,5 +68,20 @@ class LocalZigToolchain(val location: Path): AbstractZigToolchain() {
|
|||
throw ExecutionException("The debugger only supports local zig toolchain")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun tryFromPath(path: Path): LocalZigToolchain? {
|
||||
var tc = LocalZigToolchain(path)
|
||||
if (!tc.zig.fileValid()) {
|
||||
return null
|
||||
}
|
||||
val versionStr = tc.zig
|
||||
.getEnv(null)
|
||||
.getOrNull()
|
||||
?.version
|
||||
if (versionStr != null) {
|
||||
tc = tc.copy(name = "Zig $versionStr")
|
||||
}
|
||||
return tc
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,28 +20,21 @@
|
|||
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.falsepattern.zigbrains.lsp
|
||||
package com.falsepattern.zigbrains.project.toolchain.local
|
||||
|
||||
import com.falsepattern.zigbrains.lsp.settings.ZLSSettingsConfigurable
|
||||
import com.falsepattern.zigbrains.lsp.settings.ZLSSettingsPanel
|
||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
||||
import com.falsepattern.zigbrains.shared.SubConfigurable
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainConfigurable
|
||||
import java.util.*
|
||||
|
||||
class ZLSProjectConfigurationProvider: ZigProjectConfigurationProvider {
|
||||
override fun handleMainConfigChanged(project: Project) {
|
||||
startLSP(project, true)
|
||||
class LocalZigToolchainConfigurable(
|
||||
uuid: UUID,
|
||||
toolchain: LocalZigToolchain,
|
||||
data: ZigProjectConfigurationProvider.IUserDataBridge?,
|
||||
modal: Boolean
|
||||
): ZigToolchainConfigurable<LocalZigToolchain>(uuid, toolchain, data, modal) {
|
||||
override fun createPanel() = LocalZigToolchainPanel()
|
||||
|
||||
override fun setDisplayName(name: String?) {
|
||||
toolchain = toolchain.copy(name = name)
|
||||
}
|
||||
|
||||
override fun createConfigurable(project: Project): SubConfigurable {
|
||||
return ZLSSettingsConfigurable(project)
|
||||
}
|
||||
|
||||
override fun createNewProjectSettingsPanel(): ZigProjectConfigurationProvider.SettingsPanel {
|
||||
return ZLSSettingsPanel(ProjectManager.getInstance().defaultProject)
|
||||
}
|
||||
|
||||
override val priority: Int
|
||||
get() = 1000
|
||||
}
|
|
@ -20,24 +20,16 @@
|
|||
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.falsepattern.zigbrains.project.settings
|
||||
package com.falsepattern.zigbrains.project.toolchain.local
|
||||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.direnv.DirenvCmd
|
||||
import com.falsepattern.zigbrains.project.toolchain.LocalZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainProvider
|
||||
import com.falsepattern.zigbrains.shared.coroutine.launchWithEDT
|
||||
import com.falsepattern.zigbrains.project.toolchain.ui.ImmutableNamedElementPanelBase
|
||||
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.UserDataHolderBase
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.platform.ide.progress.ModalTaskOwner
|
||||
import com.intellij.platform.ide.progress.TaskCancellation
|
||||
import com.intellij.platform.ide.progress.withModalProgress
|
||||
import com.intellij.ui.DocumentAdapter
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.ui.components.JBCheckBox
|
||||
|
@ -52,12 +44,9 @@ import kotlinx.coroutines.launch
|
|||
import javax.swing.event.DocumentEvent
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
class ZigProjectSettingsPanel(private val project: Project) : ZigProjectConfigurationProvider.SettingsPanel {
|
||||
private val direnv = JBCheckBox(ZigBrainsBundle.message("settings.project.label.direnv")).apply { addActionListener {
|
||||
dispatchAutodetect(true)
|
||||
} }
|
||||
class LocalZigToolchainPanel() : ImmutableNamedElementPanelBase<LocalZigToolchain>() {
|
||||
private val pathToToolchain = textFieldWithBrowseButton(
|
||||
project,
|
||||
null,
|
||||
FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.zig-toolchain"))
|
||||
).also {
|
||||
it.textField.document.addDocumentListener(object : DocumentAdapter() {
|
||||
|
@ -68,7 +57,7 @@ class ZigProjectSettingsPanel(private val project: Project) : ZigProjectConfigur
|
|||
Disposer.register(this, it)
|
||||
}
|
||||
private val toolchainVersion = JBTextArea().also { it.isEditable = false }
|
||||
private val stdFieldOverride = JBCheckBox(ZigBrainsBundle.message("settings.project.label.override-std")).apply {
|
||||
private val stdFieldOverride = JBCheckBox().apply {
|
||||
addChangeListener {
|
||||
if (isSelected) {
|
||||
pathToStd.isEnabled = true
|
||||
|
@ -79,82 +68,57 @@ class ZigProjectSettingsPanel(private val project: Project) : ZigProjectConfigur
|
|||
}
|
||||
}
|
||||
private val pathToStd = textFieldWithBrowseButton(
|
||||
project,
|
||||
null,
|
||||
FileChooserDescriptorFactory.createSingleFolderDescriptor().withTitle(ZigBrainsBundle.message("dialog.title.zig-std"))
|
||||
).also { Disposer.register(this, it) }
|
||||
private var debounce: Job? = null
|
||||
|
||||
private fun dispatchAutodetect(force: Boolean) {
|
||||
project.zigCoroutineScope.launchWithEDT {
|
||||
withModalProgress(ModalTaskOwner.component(pathToToolchain), "Detecting Zig...", TaskCancellation.cancellable()) {
|
||||
autodetect(force)
|
||||
}
|
||||
override fun attach(panel: Panel): Unit = with(panel) {
|
||||
super.attach(panel)
|
||||
row(ZigBrainsBundle.message("settings.toolchain.local.path.label")) {
|
||||
cell(pathToToolchain).resizableColumn().align(AlignX.FILL)
|
||||
}
|
||||
row(ZigBrainsBundle.message("settings.toolchain.local.version.label")) {
|
||||
cell(toolchainVersion)
|
||||
}
|
||||
row(ZigBrainsBundle.message("settings.toolchain.local.std.label")) {
|
||||
cell(stdFieldOverride)
|
||||
cell(pathToStd).resizableColumn().align(AlignX.FILL)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun autodetect(force: Boolean) {
|
||||
if (!force && pathToToolchain.text.isNotBlank())
|
||||
return
|
||||
val data = UserDataHolderBase()
|
||||
data.putUserData(LocalZigToolchain.DIRENV_KEY, !project.isDefault && direnv.isSelected && DirenvCmd.direnvInstalled())
|
||||
val tc = ZigToolchainProvider.suggestToolchain(project, data) ?: return
|
||||
if (tc !is LocalZigToolchain) {
|
||||
TODO("Implement non-local zig toolchain in config")
|
||||
}
|
||||
if (force || pathToToolchain.text.isBlank()) {
|
||||
pathToToolchain.text = tc.location.pathString
|
||||
dispatchUpdateUI()
|
||||
}
|
||||
override fun isModified(elem: LocalZigToolchain): Boolean {
|
||||
val name = nameFieldValue ?: return false
|
||||
val location = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull() ?: return false
|
||||
val std = if (stdFieldOverride.isSelected) pathToStd.text.ifBlank { null }?.toNioPathOrNull() else null
|
||||
return name != elem.name || elem.location != location || elem.std != std
|
||||
}
|
||||
|
||||
override var data
|
||||
get() = ZigProjectSettings(
|
||||
direnv.isSelected,
|
||||
stdFieldOverride.isSelected,
|
||||
pathToStd.text.ifBlank { null },
|
||||
pathToToolchain.text.ifBlank { null }
|
||||
)
|
||||
set(value) {
|
||||
direnv.isSelected = value.direnv
|
||||
pathToToolchain.text = value.toolchainPath ?: ""
|
||||
stdFieldOverride.isSelected = value.overrideStdPath
|
||||
pathToStd.text = value.explicitPathToStd ?: ""
|
||||
pathToStd.isEnabled = value.overrideStdPath
|
||||
dispatchUpdateUI()
|
||||
}
|
||||
override fun apply(elem: LocalZigToolchain): LocalZigToolchain? {
|
||||
val location = this.pathToToolchain.text.ifBlank { null }?.toNioPathOrNull() ?: return null
|
||||
val std = if (stdFieldOverride.isSelected) pathToStd.text.ifBlank { null }?.toNioPathOrNull() else null
|
||||
return elem.copy(location = location, std = std, name = nameFieldValue ?: "")
|
||||
}
|
||||
|
||||
override fun attach(p: Panel): Unit = with(p) {
|
||||
data = project.zigProjectSettings.state
|
||||
if (project.isDefault) {
|
||||
row(ZigBrainsBundle.message("settings.project.label.toolchain")) {
|
||||
cell(pathToToolchain).resizableColumn().align(AlignX.FILL)
|
||||
}
|
||||
row(ZigBrainsBundle.message("settings.project.label.toolchain-version")) {
|
||||
cell(toolchainVersion)
|
||||
}
|
||||
override fun reset(elem: LocalZigToolchain?) {
|
||||
nameFieldValue = elem?.name ?: ""
|
||||
this.pathToToolchain.text = elem?.location?.pathString ?: ""
|
||||
val std = elem?.std
|
||||
if (std != null) {
|
||||
stdFieldOverride.isSelected = true
|
||||
pathToStd.text = std.pathString
|
||||
pathToStd.isEnabled = true
|
||||
} else {
|
||||
group(ZigBrainsBundle.message("settings.project.group.title")) {
|
||||
row(ZigBrainsBundle.message("settings.project.label.toolchain")) {
|
||||
cell(pathToToolchain).resizableColumn().align(AlignX.FILL)
|
||||
if (DirenvCmd.direnvInstalled()) {
|
||||
cell(direnv)
|
||||
}
|
||||
}
|
||||
row(ZigBrainsBundle.message("settings.project.label.toolchain-version")) {
|
||||
cell(toolchainVersion)
|
||||
}
|
||||
row(ZigBrainsBundle.message("settings.project.label.std-location")) {
|
||||
cell(pathToStd).resizableColumn().align(AlignX.FILL)
|
||||
cell(stdFieldOverride)
|
||||
}
|
||||
}
|
||||
stdFieldOverride.isSelected = false
|
||||
pathToStd.text = ""
|
||||
pathToStd.isEnabled = false
|
||||
dispatchUpdateUI()
|
||||
}
|
||||
dispatchAutodetect(false)
|
||||
}
|
||||
|
||||
private fun dispatchUpdateUI() {
|
||||
debounce?.cancel("New debounce")
|
||||
debounce = project.zigCoroutineScope.launch {
|
||||
debounce = zigCoroutineScope.launch {
|
||||
updateUI()
|
||||
}
|
||||
}
|
||||
|
@ -173,7 +137,7 @@ class ZigProjectSettingsPanel(private val project: Project) : ZigProjectConfigur
|
|||
}
|
||||
val toolchain = LocalZigToolchain(pathToToolchain)
|
||||
val zig = toolchain.zig
|
||||
val env = zig.getEnv(project).getOrElse { throwable ->
|
||||
val env = zig.getEnv(null).getOrElse { throwable ->
|
||||
throwable.printStackTrace()
|
||||
withEDTContext(ModalityState.any()) {
|
||||
toolchainVersion.text = "[failed to run \"zig env\"]\n${throwable.message}"
|
||||
|
@ -184,7 +148,7 @@ class ZigProjectSettingsPanel(private val project: Project) : ZigProjectConfigur
|
|||
return
|
||||
}
|
||||
val version = env.version
|
||||
val stdPath = env.stdPath(toolchain, project)
|
||||
val stdPath = env.stdPath(toolchain, null)
|
||||
|
||||
withEDTContext(ModalityState.any()) {
|
||||
toolchainVersion.text = version
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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.toolchain.local
|
||||
|
||||
import com.falsepattern.zigbrains.direnv.DirenvService
|
||||
import com.falsepattern.zigbrains.direnv.Env
|
||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainConfigurable
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainProvider
|
||||
import com.falsepattern.zigbrains.shared.downloader.homePath
|
||||
import com.falsepattern.zigbrains.shared.downloader.xdgDataHome
|
||||
import com.falsepattern.zigbrains.shared.ui.renderPathNameComponent
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.UserDataHolder
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.ui.SimpleColoredComponent
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
class LocalZigToolchainProvider: ZigToolchainProvider {
|
||||
override val serialMarker: String
|
||||
get() = "local"
|
||||
|
||||
override fun deserialize(data: Map<String, String>): ZigToolchain? {
|
||||
val location = data["location"]?.toNioPathOrNull() ?: return null
|
||||
val std = data["std"]?.toNioPathOrNull()
|
||||
val name = data["name"]
|
||||
return LocalZigToolchain(location, std, name)
|
||||
}
|
||||
|
||||
override fun isCompatible(toolchain: ZigToolchain): Boolean {
|
||||
return toolchain is LocalZigToolchain
|
||||
}
|
||||
|
||||
override fun serialize(toolchain: ZigToolchain): Map<String, String> {
|
||||
toolchain as LocalZigToolchain
|
||||
val map = HashMap<String, String>()
|
||||
toolchain.location.pathString.let { map["location"] = it }
|
||||
toolchain.std?.pathString?.let { map["std"] = it }
|
||||
toolchain.name?.let { map["name"] = it }
|
||||
return map
|
||||
}
|
||||
|
||||
override fun matchesSuggestion(
|
||||
toolchain: ZigToolchain,
|
||||
suggestion: ZigToolchain
|
||||
): Boolean {
|
||||
toolchain as LocalZigToolchain
|
||||
suggestion as LocalZigToolchain
|
||||
return toolchain.location == suggestion.location
|
||||
}
|
||||
|
||||
override fun createConfigurable(
|
||||
uuid: UUID,
|
||||
toolchain: ZigToolchain,
|
||||
data: ZigProjectConfigurationProvider.IUserDataBridge?,
|
||||
modal: Boolean
|
||||
): ZigToolchainConfigurable<*> {
|
||||
toolchain as LocalZigToolchain
|
||||
return LocalZigToolchainConfigurable(uuid, toolchain, data, modal)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override suspend fun suggestToolchains(project: Project?, data: UserDataHolder): Flow<ZigToolchain> {
|
||||
val env = if (project != null && DirenvService.getStateFor(data, project).isEnabled(project)) {
|
||||
DirenvService.getInstance(project).import()
|
||||
} else {
|
||||
Env.empty
|
||||
}
|
||||
val pathToolchains = env.findAllExecutablesOnPATH("zig").mapNotNull { it.parent }
|
||||
val wellKnown = wellKnown.asFlow().flatMapConcat { dir ->
|
||||
runCatching {
|
||||
Files.newDirectoryStream(dir).use { stream ->
|
||||
stream.toList().filterNotNull().asFlow()
|
||||
}
|
||||
}.getOrElse { emptyFlow() }
|
||||
}
|
||||
val joined = flowOf(pathToolchains, wellKnown).flattenConcat()
|
||||
return joined.mapNotNull { LocalZigToolchain.tryFromPath(it) }
|
||||
}
|
||||
|
||||
override fun render(toolchain: ZigToolchain, component: SimpleColoredComponent, isSuggestion: Boolean, isSelected: Boolean) {
|
||||
toolchain as LocalZigToolchain
|
||||
val name = toolchain.name
|
||||
val path = toolchain.location.pathString
|
||||
renderPathNameComponent(path, name, "Zig", component, isSuggestion, isSelected)
|
||||
}
|
||||
}
|
||||
|
||||
val suggestedLocalToolchainPath: Path? by lazy {
|
||||
wellKnown.getOrNull(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paths to the following list of folders:
|
||||
*
|
||||
* 1. DATA/zig
|
||||
* 2. DATA/zigup
|
||||
* 3. HOME/.zig
|
||||
*
|
||||
* Where DATA is:
|
||||
* - ~/Library on macOS
|
||||
* - %LOCALAPPDATA% on Windows
|
||||
* - $XDG_DATA_HOME (or ~/.local/share if not set) on other OSes
|
||||
*
|
||||
* and HOME is the user home path
|
||||
*/
|
||||
private val wellKnown: List<Path> by lazy {
|
||||
val res = ArrayList<Path>()
|
||||
xdgDataHome?.let {
|
||||
res.add(it.resolve("zig"))
|
||||
res.add(it.resolve("zigup"))
|
||||
}
|
||||
homePath?.let { res.add(it.resolve(".zig")) }
|
||||
res
|
||||
}
|
|
@ -1,164 +0,0 @@
|
|||
/*
|
||||
* 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.toolchain.stdlib
|
||||
|
||||
import com.falsepattern.zigbrains.Icons
|
||||
import com.falsepattern.zigbrains.project.settings.ZigProjectSettings
|
||||
import com.falsepattern.zigbrains.project.settings.zigProjectSettings
|
||||
import com.intellij.navigation.ItemPresentation
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.guessProjectDir
|
||||
import com.intellij.openapi.roots.SyntheticLibrary
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.refreshAndFindVirtualDirectory
|
||||
import com.intellij.platform.backend.workspace.WorkspaceModel
|
||||
import com.intellij.platform.backend.workspace.toVirtualFileUrl
|
||||
import com.intellij.platform.workspace.jps.entities.*
|
||||
import com.intellij.workspaceModel.ide.legacyBridge.LegacyBridgeJpsEntitySourceFactory
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.*
|
||||
import javax.swing.Icon
|
||||
|
||||
class ZigSyntheticLibrary(val project: Project) : SyntheticLibrary(), ItemPresentation {
|
||||
private var state: ZigProjectSettings = project.zigProjectSettings.state.copy()
|
||||
private val roots by lazy {
|
||||
runBlocking {getRoot(state, project)}?.let { setOf(it) } ?: emptySet()
|
||||
}
|
||||
|
||||
private val name by lazy {
|
||||
getName(state, project)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is ZigSyntheticLibrary)
|
||||
return false
|
||||
|
||||
return state == other.state
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return Objects.hash(roots)
|
||||
}
|
||||
|
||||
override fun getPresentableText(): String {
|
||||
return name
|
||||
}
|
||||
|
||||
override fun getIcon(unused: Boolean): Icon {
|
||||
return Icons.Zig
|
||||
}
|
||||
|
||||
override fun getSourceRoots(): Collection<VirtualFile> {
|
||||
return roots
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ZIG_LIBRARY_ID = "Zig SDK"
|
||||
private const val ZIG_MODULE_ID = "Zig"
|
||||
suspend fun reload(project: Project, state: ZigProjectSettings) {
|
||||
val moduleId = ModuleId(ZIG_MODULE_ID)
|
||||
val workspaceModel = WorkspaceModel.getInstance(project)
|
||||
if (moduleId !in workspaceModel.currentSnapshot) {
|
||||
val baseModuleDir = project.guessProjectDir()?.toVirtualFileUrl(workspaceModel.getVirtualFileUrlManager()) ?: return
|
||||
|
||||
val moduleEntitySource = LegacyBridgeJpsEntitySourceFactory.getInstance(project)
|
||||
.createEntitySourceForModule(baseModuleDir, null)
|
||||
|
||||
val moduleEntity = ModuleEntity(ZIG_MODULE_ID, emptyList(), moduleEntitySource)
|
||||
workspaceModel.update("Add new module") {builder ->
|
||||
builder.addEntity(moduleEntity)
|
||||
}
|
||||
}
|
||||
val moduleEntity = workspaceModel.currentSnapshot.resolve(moduleId) ?: return
|
||||
val root = getRoot(state, project) ?: return
|
||||
val libRoot = LibraryRoot(root.toVirtualFileUrl(workspaceModel.getVirtualFileUrlManager()), LibraryRootTypeId.SOURCES)
|
||||
val libraryTableId = LibraryTableId.ProjectLibraryTableId
|
||||
val libraryId = LibraryId(ZIG_LIBRARY_ID, libraryTableId)
|
||||
if (libraryId in workspaceModel.currentSnapshot) {
|
||||
val library = workspaceModel.currentSnapshot.resolve(libraryId) ?: return
|
||||
workspaceModel.update("Update library") { builder ->
|
||||
builder.modifyLibraryEntity(library) {
|
||||
roots.clear()
|
||||
roots.add(libRoot)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val libraryEntitySource = LegacyBridgeJpsEntitySourceFactory
|
||||
.getInstance(project)
|
||||
.createEntitySourceForProjectLibrary(null)
|
||||
val libraryEntity = LibraryEntity(
|
||||
ZIG_LIBRARY_ID,
|
||||
libraryTableId, emptyList(),
|
||||
libraryEntitySource
|
||||
) {
|
||||
roots.add(libRoot)
|
||||
}
|
||||
workspaceModel.update("Add new library") { builder ->
|
||||
builder.addEntity(libraryEntity)
|
||||
}
|
||||
}
|
||||
workspaceModel.update("Link dep") { builder ->
|
||||
builder.modifyModuleEntity(moduleEntity) {
|
||||
dependencies.add(LibraryDependency(libraryId, false, DependencyScope.COMPILE))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getName(
|
||||
state: ZigProjectSettings,
|
||||
project: Project
|
||||
): String {
|
||||
val tc = state.toolchain ?: return "Zig"
|
||||
val version = runBlocking { tc.zig.getEnv(project) }.mapCatching { it.version }.getOrElse { return "Zig" }
|
||||
return "Zig $version"
|
||||
}
|
||||
|
||||
suspend fun getRoot(
|
||||
state: ZigProjectSettings,
|
||||
project: Project
|
||||
): VirtualFile? {
|
||||
val toolchain = state.toolchain
|
||||
if (state.overrideStdPath) run {
|
||||
val ePathStr = state.explicitPathToStd ?: return@run
|
||||
val ePath = ePathStr.toNioPathOrNull() ?: return@run
|
||||
if (ePath.isAbsolute) {
|
||||
val roots = ePath.refreshAndFindVirtualDirectory() ?: return@run
|
||||
return roots
|
||||
} else if (toolchain != null) {
|
||||
val stdPath = toolchain.location.resolve(ePath)
|
||||
if (stdPath.isAbsolute) {
|
||||
val roots = stdPath.refreshAndFindVirtualDirectory() ?: return@run
|
||||
return roots
|
||||
}
|
||||
}
|
||||
}
|
||||
if (toolchain != null) {
|
||||
val stdPath = toolchain.zig.getEnv(project).mapCatching { it.stdPath(toolchain, project) }.getOrNull() ?: return null
|
||||
val roots = stdPath.refreshAndFindVirtualDirectory() ?: return null
|
||||
return roots
|
||||
}
|
||||
return null
|
||||
}
|
|
@ -22,15 +22,13 @@
|
|||
|
||||
package com.falsepattern.zigbrains.project.toolchain.tools
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainEnvironmentSerializable
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.intellij.openapi.project.Project
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.lang.IllegalStateException
|
||||
import java.nio.file.Path
|
||||
|
||||
class ZigCompilerTool(toolchain: AbstractZigToolchain) : ZigTool(toolchain) {
|
||||
class ZigCompilerTool(toolchain: ZigToolchain) : ZigTool(toolchain) {
|
||||
override val toolName: String
|
||||
get() = "zig"
|
||||
|
||||
|
|
|
@ -22,19 +22,26 @@
|
|||
|
||||
package com.falsepattern.zigbrains.project.toolchain.tools
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.AbstractZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.shared.cli.call
|
||||
import com.falsepattern.zigbrains.shared.cli.createCommandLineSafe
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.execution.process.ProcessOutput
|
||||
import com.intellij.openapi.project.Project
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.isRegularFile
|
||||
|
||||
abstract class ZigTool(val toolchain: AbstractZigToolchain) {
|
||||
abstract class ZigTool(val toolchain: ZigToolchain) {
|
||||
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) } }
|
||||
return cli.call(timeoutMillis)
|
||||
return cli.call(timeoutMillis, ipcProject = ipcProject)
|
||||
}
|
||||
|
||||
fun fileValid(): Boolean {
|
||||
val exe = toolchain.pathToExecutable(toolName)
|
||||
return exe.isRegularFile()
|
||||
}
|
||||
|
||||
private suspend fun createBaseCommandLine(
|
||||
|
|
|
@ -19,15 +19,16 @@
|
|||
* 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.toolchain
|
||||
|
||||
package com.falsepattern.zigbrains.project.toolchain.tools
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.io.toNioPathOrNull
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
@JvmRecord
|
||||
@Serializable
|
||||
data class ZigToolchainEnvironmentSerializable(
|
||||
|
@ -49,4 +50,4 @@ data class ZigToolchainEnvironmentSerializable(
|
|||
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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.toolchain.ui
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
|
||||
interface ImmutableElementPanel<T>: Disposable {
|
||||
fun attach(panel: Panel)
|
||||
fun isModified(elem: T): Boolean
|
||||
/**
|
||||
* Returned object must be the exact same class as the provided one.
|
||||
*/
|
||||
fun apply(elem: T): T?
|
||||
fun reset(elem: T?)
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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.toolchain.ui
|
||||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.intellij.ui.components.JBTextField
|
||||
import com.intellij.ui.dsl.builder.AlignX
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
|
||||
abstract class ImmutableNamedElementPanelBase<T>: ImmutableElementPanel<T> {
|
||||
private val nameField = JBTextField(25)
|
||||
|
||||
protected var nameFieldValue: String?
|
||||
get() = nameField.text.ifBlank { null }
|
||||
set(value) {nameField.text = value ?: ""}
|
||||
|
||||
override fun attach(panel: Panel): Unit = with(panel) {
|
||||
row(ZigBrainsBundle.message("settings.toolchain.base.name.label")) {
|
||||
cell(nameField).resizableColumn().align(AlignX.FILL)
|
||||
}
|
||||
separator()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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.toolchain.ui
|
||||
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.downloader.LocalToolchainDownloader
|
||||
import com.falsepattern.zigbrains.project.toolchain.downloader.LocalToolchainSelector
|
||||
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
||||
import com.falsepattern.zigbrains.shared.ui.ListElem
|
||||
import com.falsepattern.zigbrains.shared.withUniqueName
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import java.awt.Component
|
||||
import java.util.*
|
||||
|
||||
internal object ZigToolchainComboBoxHandler {
|
||||
@RequiresBackgroundThread
|
||||
suspend fun onItemSelected(context: Component, elem: ListElem.Pseudo<ZigToolchain>): UUID? = when(elem) {
|
||||
is ListElem.One.Suggested -> zigToolchainList.withUniqueName(elem.instance)
|
||||
is ListElem.Download -> LocalToolchainDownloader(context).download()
|
||||
is ListElem.FromDisk -> LocalToolchainSelector(context).browse()
|
||||
}?.let { zigToolchainList.registerNew(it) }
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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.toolchain.ui
|
||||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider.Companion.PROJECT_KEY
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.createNamedConfigurable
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.suggestZigToolchains
|
||||
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
||||
import com.falsepattern.zigbrains.shared.ui.*
|
||||
import com.intellij.openapi.ui.NamedConfigurable
|
||||
import java.awt.Component
|
||||
import java.util.*
|
||||
|
||||
sealed interface ZigToolchainDriver: UUIDComboBoxDriver<ZigToolchain> {
|
||||
override val theMap get() = zigToolchainList
|
||||
|
||||
override fun createContext(model: ZBModel<ZigToolchain>): ZBContext<ZigToolchain> {
|
||||
return TCContext(null, model)
|
||||
}
|
||||
|
||||
override fun createComboBox(model: ZBModel<ZigToolchain>): ZBComboBox<ZigToolchain> {
|
||||
return TCComboBox(model)
|
||||
}
|
||||
|
||||
override suspend fun resolvePseudo(
|
||||
context: Component,
|
||||
elem: ListElem.Pseudo<ZigToolchain>
|
||||
): UUID? {
|
||||
return ZigToolchainComboBoxHandler.onItemSelected(context, elem)
|
||||
}
|
||||
|
||||
object ForList: ZigToolchainDriver {
|
||||
override suspend fun constructModelList(): List<ListElemIn<ZigToolchain>> {
|
||||
val modelList = ArrayList<ListElemIn<ZigToolchain>>()
|
||||
modelList.addAll(ListElem.fetchGroup())
|
||||
modelList.add(Separator(ZigBrainsBundle.message("settings.toolchain.model.detected.separator"), true))
|
||||
modelList.add(suggestZigToolchains().asPending())
|
||||
return modelList
|
||||
}
|
||||
|
||||
override fun createNamedConfigurable(
|
||||
uuid: UUID,
|
||||
elem: ZigToolchain
|
||||
): NamedConfigurable<UUID> {
|
||||
return elem.createNamedConfigurable(uuid, ZigProjectConfigurationProvider.UserDataBridge(), false)
|
||||
}
|
||||
}
|
||||
|
||||
class ForSelector(val data: ZigProjectConfigurationProvider.IUserDataBridge): ZigToolchainDriver {
|
||||
override suspend fun constructModelList(): List<ListElemIn<ZigToolchain>> {
|
||||
val modelList = ArrayList<ListElemIn<ZigToolchain>>()
|
||||
modelList.add(ListElem.None())
|
||||
modelList.addAll(zigToolchainList.map { it.asActual() }.sortedBy { it.instance.name })
|
||||
modelList.add(Separator("", true))
|
||||
modelList.addAll(ListElem.fetchGroup())
|
||||
modelList.add(Separator(ZigBrainsBundle.message("settings.toolchain.model.detected.separator"), true))
|
||||
modelList.add(suggestZigToolchains(data.getUserData(PROJECT_KEY), data).asPending())
|
||||
return modelList
|
||||
}
|
||||
|
||||
override fun createNamedConfigurable(
|
||||
uuid: UUID,
|
||||
elem: ZigToolchain
|
||||
): NamedConfigurable<UUID> {
|
||||
return elem.createNamedConfigurable(uuid, data, true)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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.toolchain.ui
|
||||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
|
||||
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider.Companion.PROJECT_KEY
|
||||
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.PanelState
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainConfigurable
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.createZigToolchainExtensionPanels
|
||||
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
|
||||
import com.falsepattern.zigbrains.shared.SubConfigurable
|
||||
import com.falsepattern.zigbrains.shared.ui.UUIDMapSelector
|
||||
import com.falsepattern.zigbrains.shared.zigCoroutineScope
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
import java.util.function.Supplier
|
||||
|
||||
class ZigToolchainEditor(private val sharedState: ZigProjectConfigurationProvider.IUserDataBridge):
|
||||
UUIDMapSelector<ZigToolchain>(ZigToolchainDriver.ForSelector(sharedState)),
|
||||
SubConfigurable<Project>,
|
||||
ZigProjectConfigurationProvider.UserDataListener
|
||||
{
|
||||
private var myViews: List<ImmutableElementPanel<ZigToolchain>> = emptyList()
|
||||
init {
|
||||
sharedState.putUserData(ZigToolchainConfigurable.TOOLCHAIN_KEY, Supplier{selectedUUID?.let { zigToolchainList[it] }})
|
||||
sharedState.addUserDataChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onUserDataChanged(key: Key<*>) {
|
||||
if (key == ZigToolchainConfigurable.TOOLCHAIN_KEY)
|
||||
return
|
||||
zigCoroutineScope.launch { listChanged() }
|
||||
}
|
||||
|
||||
|
||||
override fun attach(panel: Panel): Unit = with(panel) {
|
||||
row(ZigBrainsBundle.message(
|
||||
if (sharedState.getUserData(PROJECT_KEY)?.isDefault == true)
|
||||
"settings.toolchain.editor.toolchain-default.label"
|
||||
else
|
||||
"settings.toolchain.editor.toolchain.label")
|
||||
) {
|
||||
attachComboBoxRow(this)
|
||||
}
|
||||
var views = myViews
|
||||
if (views.isEmpty()) {
|
||||
views = ArrayList<ImmutableElementPanel<ZigToolchain>>()
|
||||
views.addAll(createZigToolchainExtensionPanels(sharedState, PanelState.ProjectEditor))
|
||||
myViews = views
|
||||
}
|
||||
views.forEach { it.attach(panel) }
|
||||
}
|
||||
|
||||
override fun onSelection(uuid: UUID?) {
|
||||
sharedState.putUserData(ZigToolchainConfigurable.TOOLCHAIN_KEY, Supplier{selectedUUID?.let { zigToolchainList[it] }})
|
||||
refreshViews(uuid)
|
||||
}
|
||||
|
||||
private fun refreshViews(uuid: UUID?) {
|
||||
val toolchain = uuid?.let { zigToolchainList[it] }
|
||||
myViews.forEach { it.reset(toolchain) }
|
||||
}
|
||||
|
||||
override fun isModified(context: Project): Boolean {
|
||||
if (isEmpty)
|
||||
return false
|
||||
val uuid = selectedUUID
|
||||
if (ZigToolchainService.getInstance(context).toolchainUUID != selectedUUID) {
|
||||
return true
|
||||
}
|
||||
if (uuid == null)
|
||||
return false
|
||||
val tc = zigToolchainList[uuid]
|
||||
if (tc == null)
|
||||
return false
|
||||
return myViews.any { it.isModified(tc) }
|
||||
}
|
||||
|
||||
override fun apply(context: Project) {
|
||||
val uuid = selectedUUID
|
||||
ZigToolchainService.getInstance(context).toolchainUUID = uuid
|
||||
if (uuid == null)
|
||||
return
|
||||
val tc = zigToolchainList[uuid]
|
||||
if (tc == null)
|
||||
return
|
||||
val finalTc = myViews.fold(tc) { acc, view -> view.apply(acc) ?: acc }
|
||||
zigToolchainList[uuid] = finalTc
|
||||
}
|
||||
|
||||
override fun reset(context: Project?) {
|
||||
val project = context ?: ProjectManager.getInstance().defaultProject
|
||||
val svc = ZigToolchainService.getInstance(project)
|
||||
val uuid = svc.toolchainUUID
|
||||
selectedUUID = uuid
|
||||
refreshViews(uuid)
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
super.dispose()
|
||||
sharedState.removeUserDataChangeListener(this)
|
||||
myViews.forEach { it.dispose() }
|
||||
myViews = emptyList()
|
||||
}
|
||||
|
||||
override val newProjectBeforeInitSelector get() = true
|
||||
class Provider: ZigProjectConfigurationProvider {
|
||||
override fun create(sharedState: ZigProjectConfigurationProvider.IUserDataBridge): SubConfigurable<Project>? {
|
||||
return ZigToolchainEditor(sharedState).also { it.reset(sharedState.getUserData(PROJECT_KEY)) }
|
||||
}
|
||||
|
||||
override val index: Int get() = 0
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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.toolchain.ui
|
||||
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.shared.ui.UUIDMapEditor
|
||||
|
||||
class ZigToolchainListEditor : UUIDMapEditor<ZigToolchain>(ZigToolchainDriver.ForList) {
|
||||
override fun getDisplayName() = ZigBrainsBundle.message("settings.toolchain.list.title")
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* 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.toolchain.ui
|
||||
|
||||
import com.falsepattern.zigbrains.Icons
|
||||
import com.falsepattern.zigbrains.ZigBrainsBundle
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
|
||||
import com.falsepattern.zigbrains.project.toolchain.base.render
|
||||
import com.falsepattern.zigbrains.shared.ui.*
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.ui.SimpleTextAttributes
|
||||
import com.intellij.ui.icons.EMPTY_ICON
|
||||
import javax.swing.JList
|
||||
|
||||
class TCComboBox(model: ZBModel<ZigToolchain>): ZBComboBox<ZigToolchain>(model, ::TCCellRenderer)
|
||||
|
||||
class TCContext(project: Project?, model: ZBModel<ZigToolchain>): ZBContext<ZigToolchain>(project, model, ::TCCellRenderer)
|
||||
|
||||
class TCCellRenderer(getModel: () -> ZBModel<ZigToolchain>): ZBCellRenderer<ZigToolchain>(getModel) {
|
||||
override fun customizeCellRenderer(
|
||||
list: JList<out ListElem<ZigToolchain>?>,
|
||||
value: ListElem<ZigToolchain>?,
|
||||
index: Int,
|
||||
selected: Boolean,
|
||||
hasFocus: Boolean
|
||||
) {
|
||||
icon = EMPTY_ICON
|
||||
when (value) {
|
||||
is ListElem.One -> {
|
||||
val (icon, isSuggestion) = when(value) {
|
||||
is ListElem.One.Suggested -> AllIcons.General.Information to true
|
||||
is ListElem.One.Actual -> Icons.Zig to false
|
||||
}
|
||||
this.icon = icon
|
||||
val item = value.instance
|
||||
item.render(this, isSuggestion, index == -1)
|
||||
}
|
||||
|
||||
is ListElem.Download -> {
|
||||
icon = AllIcons.Actions.Download
|
||||
append(ZigBrainsBundle.message("settings.toolchain.model.download.text"))
|
||||
}
|
||||
|
||||
is ListElem.FromDisk -> {
|
||||
icon = AllIcons.General.OpenDisk
|
||||
append(ZigBrainsBundle.message("settings.toolchain.model.from-disk.text"))
|
||||
}
|
||||
is ListElem.Pending -> {
|
||||
icon = AllIcons.Empty
|
||||
append(ZigBrainsBundle.message("settings.toolchain.model.loading.text"), SimpleTextAttributes.GRAYED_ATTRIBUTES)
|
||||
}
|
||||
is ListElem.None, null -> {
|
||||
icon = AllIcons.General.BalloonError
|
||||
append(ZigBrainsBundle.message("settings.toolchain.model.none.text"), SimpleTextAttributes.ERROR_ATTRIBUTES)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -22,33 +22,11 @@
|
|||
|
||||
package com.falsepattern.zigbrains.shared
|
||||
|
||||
import com.intellij.openapi.options.Configurable
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import javax.swing.JComponent
|
||||
interface NamedObject<T: NamedObject<T>> {
|
||||
val name: String?
|
||||
|
||||
abstract class MultiConfigurable(private val configurables: List<SubConfigurable>): Configurable {
|
||||
override fun createComponent(): JComponent? {
|
||||
return panel {
|
||||
for (configurable in configurables) {
|
||||
configurable.createComponent(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isModified(): Boolean {
|
||||
return configurables.any { it.isModified() }
|
||||
}
|
||||
|
||||
override fun apply() {
|
||||
configurables.forEach { it.apply() }
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
configurables.forEach { it.reset() }
|
||||
}
|
||||
|
||||
override fun disposeUIResources() {
|
||||
configurables.forEach { Disposer.dispose(it) }
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returned object must be the exact same class as the called one.
|
||||
*/
|
||||
fun withName(newName: String?): T
|
||||
}
|
|
@ -23,13 +23,71 @@
|
|||
package com.falsepattern.zigbrains.shared
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.options.ConfigurationException
|
||||
import com.intellij.openapi.options.Configurable
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import javax.swing.JComponent
|
||||
|
||||
interface SubConfigurable: Disposable {
|
||||
fun createComponent(panel: Panel)
|
||||
fun isModified(): Boolean
|
||||
@Throws(ConfigurationException::class)
|
||||
fun apply()
|
||||
fun reset()
|
||||
interface SubConfigurable<T>: Disposable {
|
||||
fun attach(panel: Panel)
|
||||
fun isModified(context: T): Boolean
|
||||
fun apply(context: T)
|
||||
fun reset(context: T?)
|
||||
|
||||
val newProjectBeforeInitSelector: Boolean get() = false
|
||||
|
||||
abstract class Adapter<T>: Configurable {
|
||||
private val myConfigurables: MutableList<SubConfigurable<T>> = ArrayList()
|
||||
|
||||
abstract fun instantiate(): List<SubConfigurable<T>>
|
||||
protected abstract val context: T
|
||||
|
||||
override fun createComponent(): JComponent? {
|
||||
val configurables: List<SubConfigurable<T>>
|
||||
synchronized(myConfigurables) {
|
||||
if (myConfigurables.isEmpty()) {
|
||||
disposeConfigurables()
|
||||
}
|
||||
configurables = instantiate()
|
||||
configurables.forEach { it.reset(context) }
|
||||
myConfigurables.clear()
|
||||
myConfigurables.addAll(configurables)
|
||||
}
|
||||
return panel {
|
||||
configurables.forEach { it.attach(this) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun isModified(): Boolean {
|
||||
synchronized(myConfigurables) {
|
||||
return myConfigurables.any { it.isModified(context) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun apply() {
|
||||
synchronized(myConfigurables) {
|
||||
myConfigurables.forEach { it.apply(context) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
synchronized(myConfigurables) {
|
||||
myConfigurables.forEach { it.reset(context) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun disposeUIResources() {
|
||||
synchronized(myConfigurables) {
|
||||
disposeConfigurables()
|
||||
}
|
||||
super.disposeUIResources()
|
||||
}
|
||||
|
||||
private fun disposeConfigurables() {
|
||||
val configurables = ArrayList(myConfigurables)
|
||||
myConfigurables.clear()
|
||||
configurables.forEach { Disposer.dispose(it) }
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
import java.util.*
|
||||
|
||||
fun String.asUUID(): UUID? = UUID.fromString(this)
|
||||
fun UUID.asString(): String = toString()
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
import com.intellij.openapi.components.SerializablePersistentStateComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
|
||||
typealias UUIDStorage<T> = Map<String, T>
|
||||
|
||||
abstract class UUIDMapSerializable<T, S: Any>(init: S): SerializablePersistentStateComponent<S>(init), ChangeTrackingStorage {
|
||||
private val changeListeners = ArrayList<WeakReference<StorageChangeListener>>()
|
||||
|
||||
protected abstract fun getStorage(state: S): UUIDStorage<T>
|
||||
|
||||
protected abstract fun updateStorage(state: S, storage: UUIDStorage<T>): S
|
||||
|
||||
override fun addChangeListener(listener: StorageChangeListener) {
|
||||
synchronized(changeListeners) {
|
||||
changeListeners.add(WeakReference(listener))
|
||||
}
|
||||
}
|
||||
|
||||
override fun removeChangeListener(listener: StorageChangeListener) {
|
||||
synchronized(changeListeners) {
|
||||
changeListeners.removeIf {
|
||||
val v = it.get()
|
||||
v == null || v === listener
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun registerNewUUID(value: T): UUID {
|
||||
var uuid = UUID.randomUUID()
|
||||
updateState {
|
||||
val newMap = HashMap<String, T>()
|
||||
newMap.putAll(getStorage(it))
|
||||
var uuidStr = uuid.asString()
|
||||
while (newMap.containsKey(uuidStr)) {
|
||||
uuid = UUID.randomUUID()
|
||||
uuidStr = uuid.asString()
|
||||
}
|
||||
newMap[uuidStr] = value
|
||||
updateStorage(it, newMap)
|
||||
}
|
||||
notifyChanged()
|
||||
return uuid
|
||||
}
|
||||
|
||||
protected fun setStateUUID(uuid: UUID, value: T) {
|
||||
val str = uuid.asString()
|
||||
updateState {
|
||||
val newMap = HashMap<String, T>()
|
||||
newMap.putAll(getStorage(it))
|
||||
newMap[str] = value
|
||||
updateStorage(it, newMap)
|
||||
}
|
||||
notifyChanged()
|
||||
}
|
||||
|
||||
protected fun getStateUUID(uuid: UUID): T? {
|
||||
return getStorage(state)[uuid.asString()]
|
||||
}
|
||||
|
||||
protected fun hasStateUUID(uuid: UUID): Boolean {
|
||||
return getStorage(state).containsKey(uuid.asString())
|
||||
}
|
||||
|
||||
protected fun removeStateUUID(uuid: UUID) {
|
||||
val str = uuid.asString()
|
||||
updateState {
|
||||
updateStorage(state, getStorage(state).filter { it.key != str })
|
||||
}
|
||||
notifyChanged()
|
||||
}
|
||||
|
||||
private fun notifyChanged() {
|
||||
synchronized(changeListeners) {
|
||||
var i = 0
|
||||
while (i < changeListeners.size) {
|
||||
val v = changeListeners[i].get()
|
||||
if (v == null) {
|
||||
changeListeners.removeAt(i)
|
||||
continue
|
||||
}
|
||||
zigCoroutineScope.launch {
|
||||
v()
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Converting<R, T, S: Any>(init: S):
|
||||
UUIDMapSerializable<T, S>(init),
|
||||
AccessibleStorage<R>,
|
||||
IterableStorage<R>
|
||||
{
|
||||
protected abstract fun serialize(value: R): T
|
||||
protected abstract fun deserialize(value: T): R?
|
||||
override fun registerNew(value: R): UUID {
|
||||
val ser = serialize(value)
|
||||
return registerNewUUID(ser)
|
||||
}
|
||||
override operator fun set(uuid: UUID, value: R) {
|
||||
val ser = serialize(value)
|
||||
setStateUUID(uuid, ser)
|
||||
}
|
||||
override operator fun get(uuid: UUID): R? {
|
||||
return getStateUUID(uuid)?.let { deserialize(it) }
|
||||
}
|
||||
override operator fun contains(uuid: UUID): Boolean {
|
||||
return hasStateUUID(uuid)
|
||||
}
|
||||
override fun remove(uuid: UUID) {
|
||||
removeStateUUID(uuid)
|
||||
}
|
||||
|
||||
override fun iterator(): Iterator<Pair<UUID, R>> {
|
||||
return getStorage(state)
|
||||
.asSequence()
|
||||
.mapNotNull {
|
||||
val uuid = it.key.asUUID() ?: return@mapNotNull null
|
||||
val tc = deserialize(it.value) ?: return@mapNotNull null
|
||||
uuid to tc
|
||||
}.iterator()
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Direct<T, S: Any>(init: S): Converting<T, T, S>(init) {
|
||||
override fun serialize(value: T): T {
|
||||
return value
|
||||
}
|
||||
|
||||
override fun deserialize(value: T): T? {
|
||||
return value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typealias StorageChangeListener = suspend CoroutineScope.() -> Unit
|
||||
|
||||
interface ChangeTrackingStorage {
|
||||
fun addChangeListener(listener: StorageChangeListener)
|
||||
fun removeChangeListener(listener: StorageChangeListener)
|
||||
}
|
||||
|
||||
interface AccessibleStorage<R> {
|
||||
fun registerNew(value: R): UUID
|
||||
operator fun set(uuid: UUID, value: R)
|
||||
operator fun get(uuid: UUID): R?
|
||||
operator fun contains(uuid: UUID): Boolean
|
||||
fun remove(uuid: UUID)
|
||||
}
|
||||
|
||||
interface IterableStorage<R>: Iterable<Pair<UUID, R>>
|
||||
|
||||
fun <R: NamedObject<R>, T: R> IterableStorage<R>.withUniqueName(value: T): T {
|
||||
val baseName = value.name ?: ""
|
||||
var index = 0
|
||||
var currentName = baseName
|
||||
val names = this.map { (_, existing) -> existing.name }
|
||||
while (names.any { it == currentName }) {
|
||||
index++
|
||||
currentName = "$baseName ($index)"
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return value.withName(currentName) as T
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
import com.intellij.openapi.progress.EmptyProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.util.io.Decompressor
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.name
|
||||
|
||||
enum class Unarchiver {
|
||||
ZIP {
|
||||
override val extension = "zip"
|
||||
override fun createDecompressor(file: Path) = Decompressor.Zip(file)
|
||||
},
|
||||
TAR_GZ {
|
||||
override val extension = "tar.gz"
|
||||
override fun createDecompressor(file: Path) = Decompressor.Tar(file)
|
||||
},
|
||||
TAR_XZ {
|
||||
override val extension = "tar.xz"
|
||||
override fun createDecompressor(file: Path) = Decompressor.Tar(file)
|
||||
},
|
||||
VSIX {
|
||||
override val extension = "vsix"
|
||||
override fun createDecompressor(file: Path) = Decompressor.Zip(file)
|
||||
};
|
||||
|
||||
protected abstract val extension: String
|
||||
protected abstract fun createDecompressor(file: Path): Decompressor
|
||||
|
||||
companion object {
|
||||
@Throws(IOException::class)
|
||||
fun unarchive(archivePath: Path, dst: Path, prefix: String? = null) {
|
||||
val unarchiver = entries.find { archivePath.name.endsWith(it.extension) }
|
||||
?: error("Unexpected archive type: $archivePath")
|
||||
val dec = unarchiver.createDecompressor(archivePath)
|
||||
val indicator = ProgressManager.getInstance().progressIndicator ?: EmptyProgressIndicator()
|
||||
indicator.isIndeterminate = true
|
||||
indicator.text = "Extracting archive"
|
||||
dec.filter {
|
||||
indicator.text2 = it
|
||||
indicator.checkCanceled()
|
||||
true
|
||||
}
|
||||
if (prefix != null) {
|
||||
dec.removePrefixPath(prefix)
|
||||
}
|
||||
dec.extract(dst)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,10 +23,17 @@
|
|||
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.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
|
||||
|
@ -105,14 +112,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,
|
||||
|
@ -130,9 +129,28 @@ fun createCommandLineSafe(
|
|||
return Result.success(cli)
|
||||
}
|
||||
|
||||
suspend fun GeneralCommandLine.call(timeoutMillis: Long = Long.MAX_VALUE): Result<ProcessOutput> {
|
||||
@Throws(ExecutionException::class)
|
||||
suspend fun GeneralCommandLine.startIPCAwareProcess(project: Project?, emulateTerminal: Boolean = false): ZigProcessHandler.IPCAware {
|
||||
val original = this.commandLineString
|
||||
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.IPCAware(original, 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 process = createProcess()
|
||||
val handler = startIPCAwareProcess(ipcProject)
|
||||
val process = handler.process
|
||||
val exit = withTimeoutOrNull(timeoutMillis) {
|
||||
process.awaitExit()
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue