Compare commits

..

No commits in common. "master" and "toolchains" have entirely different histories.

74 changed files with 352 additions and 474 deletions

View file

@ -17,55 +17,6 @@ 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

View file

@ -15,7 +15,6 @@ 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
@ -72,7 +71,11 @@ 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.
Before you can properly use the plugin, you need to select or download the Zig toolchain and language server in `Settings` -> `Languages & Frameworks` -> `Zig`.
## 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
## Debugging
@ -86,7 +89,6 @@ 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).

View file

@ -6,15 +6,15 @@ import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
plugins {
kotlin("jvm") version "2.1.10" apply false
kotlin("plugin.serialization") version "2.1.10" apply false
kotlin("jvm") version "2.0.21" apply false
kotlin("plugin.serialization") version "2.0.21" 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", "251")
val publishVersions = listOf("241", "242", "243")
val pluginVersionFull get() = "$pluginVersion-$pluginSinceBuild"
val pluginVersion: String by project
val pluginSinceBuild: String by project

View file

@ -23,7 +23,7 @@
set -e
declare -a branches=("master" "243" "242" "241")
declare -a branches=("master" "242" "241")
DEFAULT_BRANCH="${branches[0]}"

View file

@ -14,8 +14,6 @@ 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") })
}

View file

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

View file

@ -24,6 +24,8 @@ package com.falsepattern.zigbrains.debugger.runner.base
import com.falsepattern.zigbrains.project.run.ZigProcessHandler
import com.falsepattern.zigbrains.shared.cli.startIPCAwareProcess
import com.falsepattern.zigbrains.shared.ipc.IPCUtil
import com.falsepattern.zigbrains.shared.ipc.ipc
import com.intellij.execution.ExecutionException
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.process.ProcessEvent
@ -41,7 +43,7 @@ import kotlinx.coroutines.withContext
class PreLaunchProcessListener(val console: ConsoleView) : ProcessListener {
var isBuildFailed: Boolean = false
private set
lateinit var processHandler: ZigProcessHandler.IPCAware
lateinit var processHandler: ProcessHandler
private set
@Throws(ExecutionException::class)
@ -51,7 +53,7 @@ class PreLaunchProcessListener(val console: ConsoleView) : ProcessListener {
this@PreLaunchProcessListener.processHandler = processHandler
hook(processHandler)
processHandler.startNotify()
withContext(Dispatchers.IO) {
withContext(Dispatchers.Default) {
processHandler.process.awaitExit()
}
runInterruptible {

View file

@ -27,7 +27,6 @@ import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
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
@ -37,7 +36,6 @@ 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,
@ -51,14 +49,13 @@ 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(profileState.environment.project, commandLine))
throw ExecutionException(ZigDebugBundle.message("debug.base.compile.failed.generic", cliString))
throw ExecutionException(ZigDebugBundle.message("debug.base.compile.failed.generic"))
return withContext(Dispatchers.IO) {
Files.list(tmpDir).use { stream ->

View file

@ -24,7 +24,6 @@ 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.base.ZigToolchain
@ -42,7 +41,6 @@ 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
@ -84,13 +82,11 @@ abstract class ZigDebugRunnerBase<ProfileState : ZigProfileState<*>> : ZigProgra
}
} catch (e: ExecutionException) {
console.print("\n", ConsoleViewContentType.ERROR_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)
}
e.message?.let { listener.console.print(it, ConsoleViewContentType.SYSTEM_OUTPUT) }
throw e;
}
if (listener.isBuildFailed) {
val executionResult = DefaultExecutionResult(console, listener.processHandler.unwrap())
val executionResult = DefaultExecutionResult(console, listener.processHandler)
return@reportProgress withEDTContext(ModalityState.any()) {
val runContentBuilder = RunContentBuilder(executionResult, environment)
runContentBuilder.showRunContent(null)

View file

@ -48,6 +48,7 @@ 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.IOException
import java.net.URL

View file

@ -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 with command: {0}
debug.base.compile.failed.generic=Failed to compile executable
debug.base.compile.failed.no-exe=Failed to find compiled binary
debug.build.compile.failed.multiple-exe=Multiple compiled binaries found
debug.build.compile.failed.no-workdir=Cannot find working directory to run debugged executable

View file

@ -9,15 +9,12 @@ 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-jvm:$serializationVersion") {
isTransitive = false
}
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3")
}
//region grammars

View file

@ -26,6 +26,7 @@ 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.firstOrNull
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import org.jetbrains.annotations.NonNls

View file

@ -26,6 +26,7 @@ import com.intellij.execution.filters.TextConsoleBuilderImpl
import com.intellij.execution.ui.ConsoleView
import com.intellij.openapi.project.Project
import com.intellij.terminal.TerminalExecutionConsole
import java.nio.file.Path
class ZigConsoleBuilder(private val project: Project, private val emulateTerminal: Boolean = false): TextConsoleBuilderImpl(project) {
override fun createConsole(): ConsoleView {

View file

@ -35,6 +35,8 @@ import com.intellij.execution.configurations.PtyCommandLine
import com.intellij.execution.process.ProcessHandler
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.platform.ide.progress.ModalTaskOwner
import com.intellij.platform.util.progress.reportProgress
import com.intellij.platform.util.progress.reportRawProgress
import kotlin.io.path.pathString
abstract class ZigProfileState<T: ZigExecConfig<T>> (

View file

@ -1,56 +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.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"
}

View file

@ -24,16 +24,12 @@ 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>() {
@ -42,39 +38,14 @@ class ZigConfigProducerBuild: ZigConfigProducer<ZigExecConfigBuild>() {
}
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
}
if (theFile.name != "build.zig")
return false
configuration.name = ZigBrainsBundle.message("configuration.build.marker-name")
return true
}
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
}
return filePath.parent == (configuration.workingDirectory.path ?: return false)
}
override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean {

View file

@ -23,14 +23,14 @@
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.ZigContainerMembers
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
import com.intellij.psi.PsiElement
import com.intellij.psi.util.childrenOfType
import java.nio.file.Path
class ZigConfigProducerRun: ZigConfigProducer<ZigExecConfigRun>() {
@ -39,10 +39,8 @@ class ZigConfigProducerRun: ZigConfigProducer<ZigExecConfigRun>() {
}
override fun setupConfigurationFromContext(configuration: ZigExecConfigRun, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean {
if (!psiFile.hasMainFunction()) {
return false
}
if (theFile.findBuildZig() != null) {
val members = psiFile.childrenOfType<ZigContainerMembers>().firstOrNull() ?: return false
if (members.containerDeclarationList.none { it.decl?.fnProto?.identifier?.textMatches("main") == true }) {
return false
}
configuration.filePath.path = filePath
@ -58,3 +56,5 @@ class ZigConfigProducerRun: ZigConfigProducer<ZigExecConfigRun>() {
return self.configurationType is ZigConfigTypeRun
}
}
private val LINE_MARKER = ZigLineMarkerRun()

View file

@ -24,14 +24,14 @@ 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.ZigContainerMembers
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
import com.intellij.psi.PsiElement
import com.intellij.psi.util.childrenOfType
import java.nio.file.Path
class ZigConfigProducerTest: ZigConfigProducer<ZigExecConfigTest>() {
@ -40,10 +40,8 @@ class ZigConfigProducerTest: ZigConfigProducer<ZigExecConfigTest>() {
}
override fun setupConfigurationFromContext(configuration: ZigExecConfigTest, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean {
if (!psiFile.hasTests()) {
return false
}
if (theFile.findBuildZig() != null) {
val members = psiFile.childrenOfType<ZigContainerMembers>().firstOrNull() ?: return false
if (members.containerDeclarationList.none { it.testDecl != null }) {
return false
}
configuration.filePath.path = filePath

View file

@ -23,8 +23,10 @@
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

View file

@ -24,29 +24,21 @@ 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.execution.process.KillableProcessHandler
import com.intellij.openapi.util.Key
import com.pty4j.PtyProcess
import java.nio.charset.Charset
open class ZigProcessHandler : KillableProcessHandler {
class ZigProcessHandler : KillableProcessHandler {
constructor(commandLine: GeneralCommandLine) : super(commandLine) {
setHasPty(commandLine is PtyCommandLine)
setShouldDestroyProcessRecursively(!hasPty())
}
protected constructor (process: Process, commandLine: String, charset: Charset) : super(process, commandLine, charset) {
constructor (process: Process, commandLine: String, charset: Charset) : super(process, commandLine, charset) {
setHasPty(process is PtyProcess)
setShouldDestroyProcessRecursively(!hasPty())
}
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)
}
}
}

View file

@ -75,40 +75,16 @@ class ZigSyntheticLibrary(val project: Project) : SyntheticLibrary(), ItemPresen
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 moduleId = ModuleId(ZIG_MODULE_ID)
val workspaceModel = WorkspaceModel.getInstance(project)
val root = getRoot(toolchain, project) ?: return
val libRoot = LibraryRoot(root.toVirtualFileUrl(workspaceModel.getVirtualFileUrlManager()), LibraryRootTypeId.SOURCES)
val libraryTableId = LibraryTableId.ProjectLibraryTableId
val libraryId = LibraryId(ZIG_LIBRARY_ID, libraryTableId)
var baseModuleDirFile: VirtualFile? = null
if (project.isDirectoryBased) {

View file

@ -28,10 +28,7 @@ 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
import com.intellij.openapi.components.*
@Service(Service.Level.APP)
@State(

View file

@ -27,12 +27,16 @@ 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.components.SerializablePersistentStateComponent
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.util.xmlb.annotations.Attribute
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.*
import java.util.UUID
@Service(Service.Level.PROJECT)
@State(

View file

@ -26,7 +26,9 @@ 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.openapi.util.Key
import com.intellij.util.xmlb.annotations.Attribute
import com.intellij.util.xmlb.annotations.MapAnnotation
import java.nio.file.Path
/**

View file

@ -25,12 +25,12 @@ 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.project.Project
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.UUID
import java.util.function.Supplier
import javax.swing.JComponent
@ -38,7 +38,7 @@ abstract class ZigToolchainConfigurable<T: ZigToolchain>(
val uuid: UUID,
tc: T,
val data: ZigProjectConfigurationProvider.IUserDataBridge?,
private val modal: Boolean
val modal: Boolean
): NamedConfigurable<UUID>() {
var toolchain: T = tc
set(value) {
@ -65,7 +65,7 @@ abstract class ZigToolchainConfigurable<T: ZigToolchain>(
views.forEach { it.attach(this@panel) }
}.withMinimumWidth(20)
views.forEach { it.reset(toolchain) }
return wrapModal(p, modal)
return p
}
override fun getEditableObject(): UUID? {

View file

@ -31,8 +31,13 @@ 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.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flowOn
import java.util.UUID
import kotlin.collections.none
private val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigToolchainProvider>("com.falsepattern.zigbrains.toolchainProvider")

View file

@ -24,15 +24,18 @@ 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.project.toolchain.local.getSuggestedLocalToolchainPath
import com.falsepattern.zigbrains.shared.downloader.Downloader
import com.falsepattern.zigbrains.shared.downloader.LocalSelector
import com.intellij.openapi.util.NlsContexts
import java.awt.Component
import java.nio.file.Path
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()
override fun getSuggestedPath() = getSuggestedLocalToolchainPath()
}

View file

@ -23,18 +23,37 @@
package com.falsepattern.zigbrains.project.toolchain.downloader
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
import com.falsepattern.zigbrains.project.toolchain.zigToolchainList
import com.falsepattern.zigbrains.shared.coroutine.asContextElement
import com.falsepattern.zigbrains.shared.coroutine.launchWithEDT
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
import com.falsepattern.zigbrains.shared.downloader.LocalSelector
import com.falsepattern.zigbrains.shared.withUniqueName
import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.icons.AllIcons
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.fileChooser.FileChooser
import com.intellij.openapi.fileChooser.FileChooserDescriptor
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
import com.intellij.openapi.ui.DialogBuilder
import com.intellij.openapi.util.Disposer
import com.intellij.platform.ide.progress.ModalTaskOwner
import com.intellij.platform.ide.progress.TaskCancellation
import com.intellij.platform.ide.progress.withModalProgress
import com.intellij.ui.DocumentAdapter
import com.intellij.ui.components.JBLabel
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.util.concurrency.annotations.RequiresEdt
import java.awt.Component
import java.nio.file.Path
import java.util.concurrent.atomic.AtomicBoolean
import javax.swing.event.DocumentEvent
import kotlin.io.path.pathString
class LocalToolchainSelector(component: Component): LocalSelector<LocalZigToolchain>(component) {
override val windowTitle: String

View file

@ -23,19 +23,46 @@
package com.falsepattern.zigbrains.project.toolchain.downloader
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.shared.Unarchiver
import com.falsepattern.zigbrains.shared.downloader.VersionInfo
import com.falsepattern.zigbrains.shared.downloader.VersionInfo.Tarball
import com.falsepattern.zigbrains.shared.downloader.downloadTarball
import com.falsepattern.zigbrains.shared.downloader.flattenDownloadDir
import com.falsepattern.zigbrains.shared.downloader.getTarballIfCompatible
import com.falsepattern.zigbrains.shared.downloader.tempPluginDir
import com.falsepattern.zigbrains.shared.downloader.unpackTarball
import com.intellij.openapi.application.PathManager
import com.intellij.openapi.progress.EmptyProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.coroutineToIndicator
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.platform.util.progress.*
import com.intellij.util.asSafely
import com.intellij.util.download.DownloadableFileService
import com.intellij.util.io.createDirectories
import com.intellij.util.io.delete
import com.intellij.util.io.move
import com.intellij.util.system.CpuArch
import com.intellij.util.system.OS
import com.intellij.util.text.SemVer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.decodeFromStream
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.deleteRecursively
import kotlin.io.path.isDirectory
import kotlin.io.path.name
@JvmRecord
data class ZigVersionInfo(

View file

@ -28,8 +28,11 @@ 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.Key
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.openapi.vfs.toNioPathOrNull
import com.intellij.util.keyFMap.KeyFMap
import java.nio.file.Path
@JvmRecord

View file

@ -24,7 +24,7 @@ package com.falsepattern.zigbrains.project.toolchain.local
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainConfigurable
import java.util.*
import java.util.UUID
class LocalZigToolchainConfigurable(
uuid: UUID,

View file

@ -73,8 +73,8 @@ class LocalZigToolchainPanel() : ImmutableNamedElementPanelBase<LocalZigToolchai
).also { Disposer.register(this, it) }
private var debounce: Job? = null
override fun attach(panel: Panel): Unit = with(panel) {
super.attach(panel)
override fun attach(p: Panel): Unit = with(p) {
super.attach(p)
row(ZigBrainsBundle.message("settings.toolchain.local.path.label")) {
cell(pathToToolchain).resizableColumn().align(AlignX.FILL)
}
@ -87,23 +87,23 @@ class LocalZigToolchainPanel() : ImmutableNamedElementPanelBase<LocalZigToolchai
}
}
override fun isModified(elem: LocalZigToolchain): Boolean {
override fun isModified(toolchain: 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
return name != toolchain.name || toolchain.location != location || toolchain.std != std
}
override fun apply(elem: LocalZigToolchain): LocalZigToolchain? {
override fun apply(toolchain: 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 ?: "")
return toolchain.copy(location = location, std = std, name = nameFieldValue ?: "")
}
override fun reset(elem: LocalZigToolchain?) {
nameFieldValue = elem?.name ?: ""
this.pathToToolchain.text = elem?.location?.pathString ?: ""
val std = elem?.std
override fun reset(toolchain: LocalZigToolchain?) {
nameFieldValue = toolchain?.name ?: ""
this.pathToToolchain.text = toolchain?.location?.pathString ?: ""
val std = toolchain?.std
if (std != null) {
stdFieldOverride.isSelected = true
pathToStd.text = std.pathString

View file

@ -28,18 +28,27 @@ import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvid
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.FileUtil
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.openapi.util.text.StringUtil
import com.intellij.ui.SimpleColoredComponent
import com.intellij.ui.SimpleTextAttributes
import com.intellij.util.system.OS
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flattenConcat
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapNotNull
import java.nio.file.Files
import java.nio.file.Path
import java.util.*
import java.util.UUID
import kotlin.io.path.isDirectory
import kotlin.io.path.pathString
class LocalZigToolchainProvider: ZigToolchainProvider {
@ -93,7 +102,7 @@ class LocalZigToolchainProvider: ZigToolchainProvider {
Env.empty
}
val pathToolchains = env.findAllExecutablesOnPATH("zig").mapNotNull { it.parent }
val wellKnown = wellKnown.asFlow().flatMapConcat { dir ->
val wellKnown = getWellKnown().asFlow().flatMapConcat { dir ->
runCatching {
Files.newDirectoryStream(dir).use { stream ->
stream.toList().filterNotNull().asFlow()
@ -112,8 +121,8 @@ class LocalZigToolchainProvider: ZigToolchainProvider {
}
}
val suggestedLocalToolchainPath: Path? by lazy {
wellKnown.getOrNull(0)
fun getSuggestedLocalToolchainPath(): Path? {
return getWellKnown().getOrNull(0)
}
/**
@ -130,12 +139,18 @@ val suggestedLocalToolchainPath: Path? by lazy {
*
* 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"))
private fun getWellKnown(): List<Path> {
val home = System.getProperty("user.home")?.toNioPathOrNull() ?: return emptyList()
val xdgDataHome = when(OS.CURRENT) {
OS.macOS -> home.resolve("Library")
OS.Windows -> System.getenv("LOCALAPPDATA")?.toNioPathOrNull()
else -> System.getenv("XDG_DATA_HOME")?.toNioPathOrNull() ?: home.resolve(Path.of(".local", "share"))
}
homePath?.let { res.add(it.resolve(".zig")) }
res
val res = ArrayList<Path>()
if (xdgDataHome != null && xdgDataHome.isDirectory()) {
res.add(xdgDataHome.resolve("zig"))
res.add(xdgDataHome.resolve("zigup"))
}
res.add(home.resolve(".zig"))
return res
}

View file

@ -24,6 +24,7 @@ package com.falsepattern.zigbrains.project.toolchain.tools
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
import com.intellij.openapi.project.Project
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import java.nio.file.Path

View file

@ -26,7 +26,7 @@ import com.intellij.openapi.Disposable
import com.intellij.ui.dsl.builder.Panel
interface ImmutableElementPanel<T>: Disposable {
fun attach(panel: Panel)
fun attach(p: Panel)
fun isModified(elem: T): Boolean
/**
* Returned object must be the exact same class as the provided one.

View file

@ -23,6 +23,7 @@
package com.falsepattern.zigbrains.project.toolchain.ui
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.shared.NamedObject
import com.intellij.ui.components.JBTextField
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.Panel
@ -34,7 +35,7 @@ abstract class ImmutableNamedElementPanelBase<T>: ImmutableElementPanel<T> {
get() = nameField.text.ifBlank { null }
set(value) {nameField.text = value ?: ""}
override fun attach(panel: Panel): Unit = with(panel) {
override fun attach(p: Panel): Unit = with(p) {
row(ZigBrainsBundle.message("settings.toolchain.base.name.label")) {
cell(nameField).resizableColumn().align(AlignX.FILL)
}

View file

@ -30,7 +30,7 @@ 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.*
import java.util.UUID
internal object ZigToolchainComboBoxHandler {
@RequiresBackgroundThread

View file

@ -29,10 +29,20 @@ 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.falsepattern.zigbrains.shared.ui.ListElem
import com.falsepattern.zigbrains.shared.ui.ListElemIn
import com.falsepattern.zigbrains.shared.ui.Separator
import com.falsepattern.zigbrains.shared.ui.UUIDComboBoxDriver
import com.falsepattern.zigbrains.shared.ui.ZBComboBox
import com.falsepattern.zigbrains.shared.ui.ZBContext
import com.falsepattern.zigbrains.shared.ui.ZBModel
import com.falsepattern.zigbrains.shared.ui.asActual
import com.falsepattern.zigbrains.shared.ui.asPending
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.NamedConfigurable
import com.intellij.openapi.util.UserDataHolder
import java.awt.Component
import java.util.*
import java.util.UUID
sealed interface ZigToolchainDriver: UUIDComboBoxDriver<ZigToolchain> {
override val theMap get() = zigToolchainList

View file

@ -39,7 +39,7 @@ 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.UUID
import java.util.function.Supplier
class ZigToolchainEditor(private val sharedState: ZigProjectConfigurationProvider.IUserDataBridge):
@ -60,7 +60,7 @@ class ZigToolchainEditor(private val sharedState: ZigProjectConfigurationProvide
}
override fun attach(panel: Panel): Unit = with(panel) {
override fun attach(p: Panel): Unit = with(p) {
row(ZigBrainsBundle.message(
if (sharedState.getUserData(PROJECT_KEY)?.isDefault == true)
"settings.toolchain.editor.toolchain-default.label"
@ -75,7 +75,7 @@ class ZigToolchainEditor(private val sharedState: ZigProjectConfigurationProvide
views.addAll(createZigToolchainExtensionPanels(sharedState, PanelState.ProjectEditor))
myViews = views
}
views.forEach { it.attach(panel) }
views.forEach { it.attach(p) }
}
override fun onSelection(uuid: UUID?) {
@ -89,8 +89,6 @@ class ZigToolchainEditor(private val sharedState: ZigProjectConfigurationProvide
}
override fun isModified(context: Project): Boolean {
if (isEmpty)
return false
val uuid = selectedUUID
if (ZigToolchainService.getInstance(context).toolchainUUID != selectedUUID) {
return true

View file

@ -26,7 +26,11 @@ 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.falsepattern.zigbrains.shared.ui.ListElem
import com.falsepattern.zigbrains.shared.ui.ZBCellRenderer
import com.falsepattern.zigbrains.shared.ui.ZBComboBox
import com.falsepattern.zigbrains.shared.ui.ZBContext
import com.falsepattern.zigbrains.shared.ui.ZBModel
import com.intellij.icons.AllIcons
import com.intellij.openapi.project.Project
import com.intellij.ui.SimpleTextAttributes

View file

@ -27,6 +27,7 @@ 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 java.util.ArrayList
import javax.swing.JComponent
interface SubConfigurable<T>: Disposable {

View file

@ -22,7 +22,7 @@
package com.falsepattern.zigbrains.shared
import java.util.*
import java.util.UUID
fun String.asUUID(): UUID? = UUID.fromString(this)
fun UUID.asString(): String = toString()

View file

@ -26,7 +26,8 @@ import com.intellij.openapi.components.SerializablePersistentStateComponent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import java.lang.ref.WeakReference
import java.util.*
import java.util.UUID
import kotlin.collections.any
typealias UUIDStorage<T> = Map<String, T>

View file

@ -130,14 +130,13 @@ fun createCommandLineSafe(
}
@Throws(ExecutionException::class)
suspend fun GeneralCommandLine.startIPCAwareProcess(project: Project?, emulateTerminal: Boolean = false): ZigProcessHandler.IPCAware {
val original = this.commandLineString
suspend fun GeneralCommandLine.startIPCAwareProcess(project: Project?, emulateTerminal: Boolean = false): ZigProcessHandler {
val ipc = if (project != null && !emulateTerminal) IPCUtil.wrapWithIPC(this) else null
val cli = ipc?.cli ?: this
if (emulateTerminal && OS.CURRENT != OS.Windows && !cli.environment.contains("TERM")) {
cli.withEnvironment("TERM", "xterm-256color")
}
val handler = ZigProcessHandler.IPCAware(original, cli)
val handler = ZigProcessHandler(cli)
ProcessTerminatedListener.attach(handler)
if (ipc != null) {

View file

@ -45,7 +45,7 @@ import com.intellij.ui.dsl.builder.panel
import com.intellij.util.concurrency.annotations.RequiresEdt
import java.awt.Component
import java.nio.file.Path
import java.util.*
import java.util.Vector
import javax.swing.DefaultComboBoxModel
import javax.swing.JList
import javax.swing.event.DocumentEvent
@ -76,10 +76,10 @@ abstract class Downloader<T, V: VersionInfo>(val component: Component) {
protected abstract val windowTitle: String
protected abstract val versionInfoFetchTitle: @NlsContexts.ProgressTitle String
protected abstract val suggestedPath: Path?
protected abstract fun downloadProgressTitle(version: V): @NlsContexts.ProgressTitle String
protected abstract fun localSelector(): LocalSelector<T>
protected abstract suspend fun downloadVersionList(): List<V>
protected abstract fun getSuggestedPath(): Path?
@RequiresEdt
private fun selectVersion(info: List<V>, selector: LocalSelector<T>): Pair<Path, V>? {
@ -131,7 +131,7 @@ abstract class Downloader<T, V: VersionInfo>(val component: Component) {
})
var archiveSizeCell: Cell<*>? = null
fun detect(item: V) {
outputPath.text = suggestedPath?.resolve(item.version.rawVersion)?.pathString ?: ""
outputPath.text = getSuggestedPath()?.resolve(item.version.rawVersion)?.pathString ?: ""
val size = item.dist.size
val sizeMb = size / (1024f * 1024f)
archiveSizeCell?.comment?.text = ZigBrainsBundle.message("settings.shared.downloader.archive-size.text", "%.2fMB".format(sizeMb))

View file

@ -41,13 +41,11 @@ import com.intellij.ui.components.textFieldWithBrowseButton
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.panel
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.intellij.util.system.OS
import java.awt.Component
import java.nio.file.Path
import java.util.concurrent.atomic.AtomicBoolean
import javax.swing.Icon
import javax.swing.event.DocumentEvent
import kotlin.io.path.isDirectory
import kotlin.io.path.pathString
abstract class LocalSelector<T>(val component: Component) {
@ -138,15 +136,3 @@ abstract class LocalSelector<T>(val component: Component) {
val errorText: String,
)
}
val homePath: Path? by lazy {
System.getProperty("user.home")?.toNioPathOrNull()?.takeIf { it.isDirectory() }
}
val xdgDataHome: Path? by lazy {
when(OS.CURRENT) {
OS.macOS -> homePath?.resolve("Library")
OS.Windows -> System.getenv("LOCALAPPDATA")?.toNioPathOrNull()
else -> System.getenv("XDG_DATA_HOME")?.toNioPathOrNull() ?: homePath?.resolve(Path.of(".local", "share"))
}?.takeIf { it.isDirectory() }
}

View file

@ -23,6 +23,7 @@
package com.falsepattern.zigbrains.shared.downloader
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.toolchain.downloader.ZigVersionInfo
import com.falsepattern.zigbrains.shared.Unarchiver
import com.falsepattern.zigbrains.shared.downloader.VersionInfo.Tarball
import com.intellij.openapi.application.PathManager

View file

@ -24,11 +24,8 @@ package com.falsepattern.zigbrains.shared.ui
import com.falsepattern.zigbrains.shared.UUIDMapSerializable
import com.intellij.openapi.ui.NamedConfigurable
import com.intellij.ui.components.JBScrollPane
import java.awt.Component
import java.awt.Dimension
import java.util.*
import javax.swing.JComponent
import java.util.UUID
interface UUIDComboBoxDriver<T> {
val theMap: UUIDMapSerializable.Converting<T, *, *>
@ -37,15 +34,4 @@ interface UUIDComboBoxDriver<T> {
fun createComboBox(model: ZBModel<T>): ZBComboBox<T>
suspend fun resolvePseudo(context: Component, elem: ListElem.Pseudo<T>): UUID?
fun createNamedConfigurable(uuid: UUID, elem: T): NamedConfigurable<UUID>
companion object {
fun wrapModal(component: JComponent, modal: Boolean): JComponent {
if (modal) {
component.preferredSize = Dimension(640, 480)
return component
} else {
return JBScrollPane(component)
}
}
}
}

View file

@ -39,7 +39,7 @@ import com.intellij.util.IconUtil
import com.intellij.util.asSafely
import com.intellij.util.concurrency.annotations.RequiresEdt
import kotlinx.coroutines.launch
import java.util.*
import java.util.UUID
import javax.swing.JComponent
import javax.swing.tree.DefaultTreeModel

View file

@ -28,10 +28,12 @@ import com.falsepattern.zigbrains.shared.StorageChangeListener
import com.falsepattern.zigbrains.shared.coroutine.asContextElement
import com.falsepattern.zigbrains.shared.coroutine.launchWithEDT
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
import com.falsepattern.zigbrains.shared.ui.ListElem
import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.EDT
import com.intellij.openapi.application.ModalityState
import com.intellij.openapi.application.runInEdt
import com.intellij.openapi.observable.util.whenListChanged
import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.ui.DialogWrapper
@ -39,11 +41,12 @@ import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.Row
import com.intellij.util.concurrency.annotations.RequiresEdt
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.awt.event.ItemEvent
import java.util.*
import java.util.UUID
import javax.swing.JButton
abstract class UUIDMapSelector<T>(val driver: UUIDComboBoxDriver<T>): Disposable {
@ -79,8 +82,6 @@ abstract class UUIDMapSelector<T>(val driver: UUIDComboBoxDriver<T>): Disposable
}
}
protected val isEmpty: Boolean get() = model.isEmpty
protected open fun onSelection(uuid: UUID?) {}
private fun refreshButtonState(item: ListElem<*>) {
@ -161,7 +162,7 @@ abstract class UUIDMapSelector<T>(val driver: UUIDComboBoxDriver<T>): Disposable
protected fun attachComboBoxRow(row: Row): Unit = with(row) {
cell(comboBox).resizableColumn().align(AlignX.FILL)
button(ZigBrainsBundle.message("settings.toolchain.editor.toolchain.edit-button.name")) {
button(ZigBrainsBundle.message("settings.toolchain.editor.toolchain.edit-button.name")) { e ->
zigCoroutineScope.launchWithEDT(comboBox.asContextElement()) {
var selectedUUID = comboBox.selectedUUID ?: return@launchWithEDT
val elem = driver.theMap[selectedUUID] ?: return@launchWithEDT

View file

@ -24,7 +24,7 @@ package com.falsepattern.zigbrains.shared.ui
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import java.util.*
import java.util.UUID
sealed interface ListElemIn<T>

View file

@ -31,7 +31,12 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.ComboBox
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import com.intellij.ui.*
import com.intellij.ui.CellRendererPanel
import com.intellij.ui.CollectionComboBoxModel
import com.intellij.ui.ColoredListCellRenderer
import com.intellij.ui.GroupHeaderSeparator
import com.intellij.ui.SimpleColoredComponent
import com.intellij.ui.SimpleTextAttributes
import com.intellij.ui.components.panels.OpaquePanel
import com.intellij.ui.popup.list.ComboBoxPopup
import com.intellij.util.concurrency.annotations.RequiresEdt
@ -42,11 +47,13 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.awt.BorderLayout
import java.awt.Component
import java.util.*
import java.util.IdentityHashMap
import java.util.UUID
import java.util.function.Consumer
import javax.accessibility.AccessibleContext
import javax.swing.JList
import javax.swing.border.Border
import kotlin.io.path.pathString
class ZBComboBoxPopup<T>(
context: ZBContext<T>,
@ -250,16 +257,16 @@ abstract class ZBCellRenderer<T>(val getModel: () -> ZBModel<T>) : ColoredListCe
}
fun renderPathNameComponent(path: String, name: String?, nameFallback: String, component: SimpleColoredComponent, isSuggestion: Boolean, isSelected: Boolean) {
val thePath = presentDetectedPath(path)
val path = presentDetectedPath(path)
val primary: String
var secondary: String?
val tooltip: String?
if (isSuggestion) {
primary = thePath
primary = path
secondary = name
} else {
primary = name ?: nameFallback
secondary = thePath
secondary = path
}
if (isSelected) {
tooltip = secondary
@ -277,12 +284,12 @@ fun renderPathNameComponent(path: String, name: String?, nameFallback: String, c
fun presentDetectedPath(home: String, maxLength: Int = 50, suffixLength: Int = 30): String {
//for macOS, let's try removing Bundle internals
var theHome = home
theHome = StringUtil.trimEnd(theHome, "/Contents/Home") //NON-NLS
theHome = StringUtil.trimEnd(theHome, "/Contents/MacOS") //NON-NLS
theHome = FileUtil.getLocationRelativeToUserHome(theHome, false)
theHome = StringUtil.shortenTextWithEllipsis(theHome, maxLength, suffixLength)
return theHome
var home = home
home = StringUtil.trimEnd(home, "/Contents/Home") //NON-NLS
home = StringUtil.trimEnd(home, "/Contents/MacOS") //NON-NLS
home = FileUtil.getLocationRelativeToUserHome(home, false)
home = StringUtil.shortenTextWithEllipsis(home, maxLength, suffixLength)
return home
}
private val EMPTY_ICON = EmptyIcon.create(1, 16)

View file

@ -91,8 +91,7 @@ configuration.test.marker-name=all tests in {0}
configuration.build.name=Zig build
configuration.build.suggested-name=Build
configuration.build.description=Execute "zig build" with custom steps
configuration.build.marker-run=Build and Run
configuration.build.marker-test=Build and Test
configuration.build.marker-name=Build and Run
settings.project.group.title=Zig Settings
settings.project.label.direnv=Use direnv
settings.project.label.toolchain=Toolchain location

View file

@ -1,13 +1,13 @@
pluginName=ZigBrains
pluginRepositoryUrl=https://github.com/FalsePattern/ZigBrains
pluginVersion=25.2.0
pluginVersion=24.0.1
pluginSinceBuild=251
pluginSinceBuild=243
pluginUntilBuild=
ideaCommunityVersion=2025.1
clionVersion=2025.1
ideaCommunityVersion=2024.3
clionVersion=2024.3
useInstaller=true
javaVersion=21
# ideaCommunity / clion
@ -17,8 +17,6 @@ lsp4jVersion=0.21.1
lsp4ijVersion=0.12.0
lsp4ijNightly=false
serializationVersion=1.7.3
kotlin.stdlib.default.dependency=false
kotlin.code.style=official
org.gradle.configuration-cache=true

View file

@ -8,15 +8,12 @@ val lsp4ijVersion: String by project
val lsp4jVersion: String by project
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-jvm:$serializationVersion") {
isTransitive = false
}
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3")
compileOnly("com.redhat.devtools.intellij:lsp4ij:$lsp4ijVersion")
compileOnly("org.eclipse.lsp4j:org.eclipse.lsp4j:$lsp4jVersion")
implementation(project(":core")) {

View file

@ -24,6 +24,7 @@ package com.falsepattern.zigbrains.lsp
import com.falsepattern.zigbrains.lsp.config.ZLSConfigProviderBase
import com.falsepattern.zigbrains.lsp.zls.zls
import com.falsepattern.zigbrains.project.toolchain.ZigToolchainService
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.notification.Notification
import com.intellij.notification.NotificationType

View file

@ -68,14 +68,7 @@ class ZigLanguageServerFactory: LanguageServerFactory, LanguageServerEnablementS
}
features.inlayHintFeature = object: LSPInlayHintFeature() {
override fun isEnabled(file: PsiFile): Boolean {
val settings = project.zls?.settings ?: return false
if (!settings.inlayHints)
return false
val maxFileSizeKb = settings.inlayHintsMaxFileSizeKb
if (maxFileSizeKb == 0)
return true
val fileSizeKb = file.fileDocument.textLength / 1024
return fileSizeKb <= maxFileSizeKb
return project.zls?.settings?.inlayHints == true
}
}
return features

View file

@ -23,6 +23,8 @@
package com.falsepattern.zigbrains.lsp.settings
import com.falsepattern.zigbrains.lsp.config.SemanticTokens
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
import com.intellij.openapi.project.Project
import com.intellij.util.xmlb.annotations.Attribute
import org.jetbrains.annotations.NonNls
@ -30,7 +32,6 @@ import org.jetbrains.annotations.NonNls
data class ZLSSettings(
@JvmField @Attribute val zlsConfigPath: @NonNls String = "",
@JvmField @Attribute val inlayHints: Boolean = true,
@JvmField @Attribute val inlayHintsMaxFileSizeKb: Int = 128,
@JvmField @Attribute val enable_snippets: Boolean = true,
@JvmField @Attribute val enable_argument_placeholders: Boolean = true,
@JvmField @Attribute val completion_label_details: Boolean = true,

View file

@ -35,9 +35,6 @@ import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.Panel
import com.intellij.ui.dsl.builder.Row
import org.jetbrains.annotations.PropertyKey
import javax.swing.text.AttributeSet
import javax.swing.text.DocumentFilter
import javax.swing.text.PlainDocument
@Suppress("PrivatePropertyName")
class ZLSSettingsPanel() : ImmutableElementPanel<ZLSSettings> {
@ -47,27 +44,6 @@ class ZLSSettingsPanel() : ImmutableElementPanel<ZLSSettings> {
.withTitle(ZLSBundle.message("settings.zls-config-path.browse.title"))
).also { Disposer.register(this, it) }
private val inlayHints = JBCheckBox()
private val inlayHintsMaxFileSize = ExtendableTextField(5).also { (it.document as PlainDocument).documentFilter = object: DocumentFilter() {
override fun insertString(fb: FilterBypass?, offset: Int, string: String?, attr: AttributeSet?) {
if (string != null && !string.isEmpty() && string.toIntOrNull() == null) {
return
}
super.insertString(fb, offset, string, attr)
}
override fun replace(
fb: FilterBypass?,
offset: Int,
length: Int,
text: String?,
attrs: AttributeSet?
) {
if (text != null && !text.isEmpty() && text.toIntOrNull() == null) {
return
}
super.replace(fb, offset, length, text, attrs)
}
} }
private val enable_snippets = JBCheckBox()
private val enable_argument_placeholders = JBCheckBox()
private val completion_label_details = JBCheckBox()
@ -89,7 +65,7 @@ class ZLSSettingsPanel() : ImmutableElementPanel<ZLSSettings> {
private val build_runner_path = ExtendableTextField()
private val global_cache_path = ExtendableTextField()
override fun attach(panel: Panel): Unit = with(panel) {
override fun attach(p: Panel): Unit = with(p) {
fancyRow(
"settings.zls-config-path.label",
"settings.zls-config-path.tooltip"
@ -123,13 +99,6 @@ class ZLSSettingsPanel() : ImmutableElementPanel<ZLSSettings> {
"settings.inlay-hints-enable.label",
"settings.inlay-hints-enable.tooltip"
) { cell(inlayHints) }
fancyRow(
"settings.inlay-hints-max-size.label",
"settings.inlay-hints-max-size.tooltip",
) {
cell(inlayHintsMaxFileSize)
text(ZLSBundle.message("settings.inlay-hints-max-size.unit"))
}
fancyRow(
"settings.inlay_hints_show_variable_type_hints.label",
"settings.inlay_hints_show_variable_type_hints.tooltip"
@ -205,7 +174,6 @@ class ZLSSettingsPanel() : ImmutableElementPanel<ZLSSettings> {
get() = ZLSSettings(
zlsConfigPath.text,
inlayHints.isSelected,
inlayHintsMaxFileSize.text.toIntOrNull() ?: 128,
enable_snippets.isSelected,
enable_argument_placeholders.isSelected,
completion_label_details.isSelected,
@ -230,7 +198,6 @@ class ZLSSettingsPanel() : ImmutableElementPanel<ZLSSettings> {
set(value) {
zlsConfigPath.text = value.zlsConfigPath
inlayHints.isSelected = value.inlayHints
inlayHintsMaxFileSize.text = value.inlayHintsMaxFileSizeKb.toString()
enable_snippets.isSelected = value.enable_snippets
enable_argument_placeholders.isSelected = value.enable_argument_placeholders
completion_label_details.isSelected = value.completion_label_details

View file

@ -22,14 +22,15 @@
package com.falsepattern.zigbrains.lsp.zls
import com.falsepattern.zigbrains.shared.ui.UUIDComboBoxDriver.Companion.wrapModal
import com.intellij.openapi.ui.NamedConfigurable
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.NlsSafe
import com.intellij.ui.dsl.builder.panel
import java.util.*
import java.awt.Dimension
import java.util.UUID
import javax.swing.JComponent
class ZLSConfigurable(val uuid: UUID, zls: ZLSVersion, private val modal: Boolean): NamedConfigurable<UUID>() {
class ZLSConfigurable(val uuid: UUID, zls: ZLSVersion): NamedConfigurable<UUID>() {
var zls: ZLSVersion = zls
set(value) {
zlsInstallations[uuid] = value
@ -59,7 +60,8 @@ class ZLSConfigurable(val uuid: UUID, zls: ZLSVersion, private val modal: Boolea
val p = panel {
view.attach(this@panel)
}
return wrapModal(p, modal)
p.preferredSize = Dimension(640, 480)
return p
}
override fun getDisplayName(): @NlsContexts.ConfigurableName String? {

View file

@ -25,10 +25,7 @@ package com.falsepattern.zigbrains.lsp.zls
import com.falsepattern.zigbrains.lsp.zls.ZLSInstallationsService.MyState
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
import com.intellij.openapi.components.*
@Service(Service.Level.APP)
@State(

View file

@ -22,8 +22,8 @@
package com.falsepattern.zigbrains.lsp.zls
import com.falsepattern.zigbrains.lsp.ZLSBundle
import com.falsepattern.zigbrains.lsp.settings.ZLSSettingsPanel
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
import com.falsepattern.zigbrains.project.toolchain.ui.ImmutableNamedElementPanelBase
import com.falsepattern.zigbrains.shared.cli.call
import com.falsepattern.zigbrains.shared.cli.createCommandLineSafe
@ -49,7 +49,7 @@ import kotlin.io.path.pathString
class ZLSPanel() : ImmutableNamedElementPanelBase<ZLSVersion>() {
private val pathToZLS = textFieldWithBrowseButton(
null,
FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor().withTitle(ZLSBundle.message("dialog.title.zls"))
FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor().withTitle("Path to the zls executable")
).also {
it.textField.document.addDocumentListener(object : DocumentAdapter() {
override fun textChanged(e: DocumentEvent) {
@ -62,36 +62,36 @@ class ZLSPanel() : ImmutableNamedElementPanelBase<ZLSVersion>() {
private var settingsPanel: ZLSSettingsPanel? = null
private var debounce: Job? = null
override fun attach(panel: Panel): Unit = with(panel) {
super.attach(panel)
row(ZLSBundle.message("settings.panel.path.label")) {
override fun attach(p: Panel): Unit = with(p) {
super.attach(p)
row("Path:") {
cell(pathToZLS).resizableColumn().align(AlignX.FILL)
}
row(ZLSBundle.message("settings.panel.version.label")) {
row("Version:") {
cell(zlsVersion)
}
val sp = ZLSSettingsPanel()
panel.collapsibleGroup(ZLSBundle.message("settings.panel.settings.group.label"), indent = false) {
p.collapsibleGroup("Settings", indent = false) {
sp.attach(this@collapsibleGroup)
}
settingsPanel = sp
}
override fun isModified(elem: ZLSVersion): Boolean {
override fun isModified(version: ZLSVersion): Boolean {
val name = nameFieldValue ?: return false
val path = this.pathToZLS.text.ifBlank { null }?.toNioPathOrNull() ?: return false
return name != elem.name || elem.path != path || settingsPanel?.isModified(elem.settings) == true
return name != version.name || version.path != path || settingsPanel?.isModified(version.settings) == true
}
override fun apply(elem: ZLSVersion): ZLSVersion? {
override fun apply(version: ZLSVersion): ZLSVersion? {
val path = this.pathToZLS.text.ifBlank { null }?.toNioPathOrNull() ?: return null
return elem.copy(path = path, name = nameFieldValue ?: "", settings = settingsPanel?.apply(elem.settings) ?: elem.settings)
return version.copy(path = path, name = nameFieldValue ?: "", settings = settingsPanel?.apply(version.settings) ?: version.settings)
}
override fun reset(elem: ZLSVersion?) {
nameFieldValue = elem?.name ?: ""
this.pathToZLS.text = elem?.path?.pathString ?: ""
settingsPanel?.reset(elem?.settings)
override fun reset(version: ZLSVersion?) {
nameFieldValue = version?.name ?: ""
this.pathToZLS.text = version?.path?.pathString ?: ""
settingsPanel?.reset(version?.settings)
dispatchUpdateUI()
}

View file

@ -28,7 +28,7 @@ import com.falsepattern.zigbrains.project.toolchain.base.withExtraData
import com.falsepattern.zigbrains.shared.asString
import com.falsepattern.zigbrains.shared.asUUID
import com.intellij.openapi.project.Project
import java.util.*
import java.util.UUID
fun <T: ZigToolchain> T.withZLS(uuid: UUID?): T {
return withExtraData("zls_uuid", uuid?.asString())

View file

@ -26,11 +26,12 @@ import com.falsepattern.zigbrains.lsp.settings.ZLSSettings
import com.falsepattern.zigbrains.shared.NamedObject
import com.falsepattern.zigbrains.shared.cli.call
import com.falsepattern.zigbrains.shared.cli.createCommandLineSafe
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.util.text.SemVer
import com.intellij.util.xmlb.annotations.Attribute
import com.intellij.util.xmlb.annotations.Tag
import java.nio.file.Path
import com.intellij.util.xmlb.annotations.Attribute
import kotlin.io.path.isDirectory
import kotlin.io.path.isExecutable
import kotlin.io.path.isRegularFile
import kotlin.io.path.pathString
@ -81,7 +82,6 @@ data class ZLSVersion(val path: Path, override val name: String? = null, val set
@Attribute
val name: String? = "",
@JvmField
@Tag
val settings: ZLSSettings = ZLSSettings()
) {
fun resolve(): ZLSVersion? {

View file

@ -22,24 +22,27 @@
package com.falsepattern.zigbrains.lsp.zls.downloader
import com.falsepattern.zigbrains.lsp.ZLSBundle
import com.falsepattern.zigbrains.lsp.zls.ZLSVersion
import com.falsepattern.zigbrains.lsp.zls.ui.suggestedZLSPath
import com.falsepattern.zigbrains.lsp.zls.ui.getSuggestedZLSPath
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider.IUserDataBridge
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainConfigurable
import com.falsepattern.zigbrains.shared.downloader.Downloader
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.util.system.OS
import java.awt.Component
import java.nio.file.Path
import kotlin.io.path.isDirectory
class ZLSDownloader(component: Component, private val data: IUserDataBridge?) : Downloader<ZLSVersion, ZLSVersionInfo>(component) {
override val windowTitle get() = ZLSBundle.message("settings.downloader.title")
override val versionInfoFetchTitle get() = ZLSBundle.message("settings.downloader.progress.fetch")
override val suggestedPath get() = suggestedZLSPath
override fun downloadProgressTitle(version: ZLSVersionInfo) = ZLSBundle.message("settings.downloader.progress.install", version.version.rawVersion)
override val windowTitle get() = "Install ZLS"
override val versionInfoFetchTitle get() = "Fetching zls version information"
override fun downloadProgressTitle(version: ZLSVersionInfo) = "Installing ZLS ${version.version.rawVersion}"
override fun localSelector() = ZLSLocalSelector(component)
override suspend fun downloadVersionList(): List<ZLSVersionInfo> {
val toolchain = data?.getUserData(ZigToolchainConfigurable.TOOLCHAIN_KEY)?.get()
val project = data?.getUserData(ZigProjectConfigurationProvider.PROJECT_KEY)
return ZLSVersionInfo.downloadVersionInfoFor(toolchain, project)
}
override fun getSuggestedPath() = getSuggestedZLSPath()
}

View file

@ -22,7 +22,6 @@
package com.falsepattern.zigbrains.lsp.zls.downloader
import com.falsepattern.zigbrains.lsp.ZLSBundle
import com.falsepattern.zigbrains.lsp.zls.ZLSVersion
import com.falsepattern.zigbrains.lsp.zls.zlsInstallations
import com.falsepattern.zigbrains.shared.downloader.LocalSelector
@ -37,9 +36,9 @@ import kotlin.io.path.isDirectory
class ZLSLocalSelector(component: Component) : LocalSelector<ZLSVersion>(component) {
override val windowTitle: String
get() = ZLSBundle.message("settings.local-selector.title")
get() = "Select ZLS from disk"
override val descriptor: FileChooserDescriptor
get() = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor().withTitle(ZLSBundle.message("settings.local-selector.chooser.title"))
get() = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor().withTitle("ZLS binary")
override suspend fun browse(preSelected: Path?): ZLSVersion? {
if (preSelected?.isDirectory() == true) {
@ -55,12 +54,12 @@ class ZLSLocalSelector(component: Component) : LocalSelector<ZLSVersion>(compone
null,
false,
AllIcons.General.Error,
ZLSBundle.message("settings.local-selector.state.invalid"),
"Invalid ZLS path",
) else VerifyResult(
null,
true,
AllIcons.General.Information,
ZLSBundle.message("settings.local-selector.state.ok")
"ZLS path OK"
)
if (zls != null) {
zls = zlsInstallations.withUniqueName(zls)

View file

@ -22,8 +22,8 @@
package com.falsepattern.zigbrains.lsp.zls.downloader
import com.falsepattern.zigbrains.lsp.ZLSBundle
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchain
import com.falsepattern.zigbrains.project.toolchain.downloader.ZigVersionInfo
import com.falsepattern.zigbrains.shared.downloader.VersionInfo
import com.falsepattern.zigbrains.shared.downloader.VersionInfo.Tarball
import com.falsepattern.zigbrains.shared.downloader.getTarballIfCompatible
@ -37,7 +37,12 @@ import com.intellij.util.text.SemVer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.decodeFromStream
import java.net.URLEncoder
@JvmRecord
@ -59,7 +64,7 @@ data class ZLSVersionInfo(
val service = DownloadableFileService.getInstance()
val tempFile = FileUtil.createTempFile(tempPluginDir, "zls_version_info", ".json", false, false)
val desc = service.createFileDescription(url, tempFile.name)
val downloader = service.createDownloader(listOf(desc), ZLSBundle.message("settings.downloader.service.index"))
val downloader = service.createDownloader(listOf(desc), "ZLS version information")
val downloadResults = coroutineToIndicator {
downloader.download(tempPluginDir)
}

View file

@ -33,14 +33,21 @@ import com.falsepattern.zigbrains.lsp.zls.zlsInstallations
import com.falsepattern.zigbrains.project.settings.ZigProjectConfigurationProvider
import com.falsepattern.zigbrains.project.toolchain.base.ZigToolchainConfigurable.Companion.TOOLCHAIN_KEY
import com.falsepattern.zigbrains.shared.UUIDMapSerializable
import com.falsepattern.zigbrains.shared.downloader.homePath
import com.falsepattern.zigbrains.shared.downloader.xdgDataHome
import com.falsepattern.zigbrains.shared.ui.*
import com.falsepattern.zigbrains.shared.ui.ListElem
import com.falsepattern.zigbrains.shared.ui.ListElem.One.Actual
import com.falsepattern.zigbrains.shared.ui.ListElemIn
import com.falsepattern.zigbrains.shared.ui.Separator
import com.falsepattern.zigbrains.shared.ui.UUIDComboBoxDriver
import com.falsepattern.zigbrains.shared.ui.ZBComboBox
import com.falsepattern.zigbrains.shared.ui.ZBContext
import com.falsepattern.zigbrains.shared.ui.ZBModel
import com.falsepattern.zigbrains.shared.ui.asPending
import com.falsepattern.zigbrains.shared.withUniqueName
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.NamedConfigurable
import com.intellij.openapi.util.SystemInfo
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.util.system.OS
import com.intellij.util.text.SemVer
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
@ -50,7 +57,7 @@ import kotlinx.coroutines.flow.flowOn
import java.awt.Component
import java.nio.file.Files
import java.nio.file.Path
import java.util.*
import java.util.UUID
import kotlin.io.path.isDirectory
import kotlin.io.path.isExecutable
import kotlin.io.path.isRegularFile
@ -67,6 +74,10 @@ sealed interface ZLSDriver: UUIDComboBoxDriver<ZLSVersion> {
return ZLSComboBox(model)
}
override fun createNamedConfigurable(uuid: UUID, elem: ZLSVersion): NamedConfigurable<UUID> {
return ZLSConfigurable(uuid, elem)
}
override suspend fun resolvePseudo(
context: Component,
elem: ListElem.Pseudo<ZLSVersion>
@ -89,10 +100,6 @@ sealed interface ZLSDriver: UUIDComboBoxDriver<ZLSVersion> {
return res
}
override fun createNamedConfigurable(uuid: UUID, elem: ZLSVersion): NamedConfigurable<UUID> {
return ZLSConfigurable(uuid, elem, false)
}
override val data: ZigProjectConfigurationProvider.IUserDataBridge?
get() = null
}
@ -113,10 +120,6 @@ sealed interface ZLSDriver: UUIDComboBoxDriver<ZLSVersion> {
res.add(suggestZLSVersions(project, data, toolchainVersion).asPending())
return res
}
override fun createNamedConfigurable(uuid: UUID, elem: ZLSVersion): NamedConfigurable<UUID> {
return ZLSConfigurable(uuid, elem, true)
}
}
}
@ -146,16 +149,16 @@ private fun suggestZLSVersions(project: Project? = null, data: ZigProjectConfigu
emitIfCompatible(path, toolchainVersion)
}
val exe = if (SystemInfo.isWindows) "zls.exe" else "zls"
wellKnownZLS.forEach { wellKnown ->
getWellKnownZLS().forEach { wellKnown ->
runCatching {
Files.newDirectoryStream(wellKnown).use { stream ->
stream.asSequence().filterNotNull().forEach streamForEach@{ dir ->
stream.asSequence().filterNotNull().forEach { dir ->
val path = dir.resolve(exe)
if (!path.isRegularFile() || !path.isExecutable()) {
return@streamForEach
return@forEach
}
if (existing.any { it.path == path }) {
return@streamForEach
return@forEach
}
emitIfCompatible(path, toolchainVersion)
}
@ -192,8 +195,8 @@ private fun numericVersionEquals(a: SemVer, b: SemVer): Boolean {
}
val suggestedZLSPath: Path? by lazy {
wellKnownZLS.getOrNull(0)
fun getSuggestedZLSPath(): Path? {
return getWellKnownZLS().getOrNull(0)
}
/**
@ -209,9 +212,17 @@ val suggestedZLSPath: Path? by lazy {
*
* and HOME is the user home path
*/
private val wellKnownZLS: List<Path> by lazy {
private fun getWellKnownZLS(): List<Path> {
val home = System.getProperty("user.home")?.toNioPathOrNull() ?: return emptyList()
val xdgDataHome = when(OS.CURRENT) {
OS.macOS -> home.resolve("Library")
OS.Windows -> System.getenv("LOCALAPPDATA")?.toNioPathOrNull()
else -> System.getenv("XDG_DATA_HOME")?.toNioPathOrNull() ?: home.resolve(Path.of(".local", "share"))
}
val res = ArrayList<Path>()
xdgDataHome?.let { res.add(it.resolve("zls")) }
homePath?.let { res.add(it.resolve(".zls")) }
res
if (xdgDataHome != null && xdgDataHome.isDirectory()) {
res.add(xdgDataHome.resolve("zls"))
}
res.add(home.resolve(".zls"))
return res
}

View file

@ -22,6 +22,8 @@
package com.falsepattern.zigbrains.lsp.zls.ui
import com.falsepattern.zigbrains.lsp.ZLSStarter
import com.falsepattern.zigbrains.lsp.startLSP
import com.falsepattern.zigbrains.lsp.zls.ZLSVersion
import com.falsepattern.zigbrains.lsp.zls.withZLS
import com.falsepattern.zigbrains.lsp.zls.zlsUUID
@ -55,21 +57,19 @@ class ZLSEditor<T: ZigToolchain>(private val sharedState: ZigProjectConfiguratio
}
}
override fun isModified(elem: T): Boolean {
if (isEmpty)
return false
return elem.zlsUUID != selectedUUID
override fun isModified(toolchain: T): Boolean {
return toolchain.zlsUUID != selectedUUID
}
override fun apply(elem: T): T {
return elem.withZLS(selectedUUID)
override fun apply(toolchain: T): T {
return toolchain.withZLS(selectedUUID)
}
override fun reset(elem: T?) {
selectedUUID = elem?.zlsUUID
override fun reset(toolchain: T?) {
selectedUUID = toolchain?.zlsUUID
zigCoroutineScope.launch {
listChanged()
selectedUUID = elem?.zlsUUID
selectedUUID = toolchain?.zlsUUID
}
}

View file

@ -25,7 +25,12 @@ package com.falsepattern.zigbrains.lsp.zls.ui
import com.falsepattern.zigbrains.lsp.LSPIcons
import com.falsepattern.zigbrains.lsp.ZLSBundle
import com.falsepattern.zigbrains.lsp.zls.ZLSVersion
import com.falsepattern.zigbrains.shared.ui.*
import com.falsepattern.zigbrains.shared.ui.ListElem
import com.falsepattern.zigbrains.shared.ui.ZBCellRenderer
import com.falsepattern.zigbrains.shared.ui.ZBComboBox
import com.falsepattern.zigbrains.shared.ui.ZBContext
import com.falsepattern.zigbrains.shared.ui.ZBModel
import com.falsepattern.zigbrains.shared.ui.renderPathNameComponent
import com.intellij.icons.AllIcons
import com.intellij.openapi.project.Project
import com.intellij.ui.SimpleTextAttributes

View file

@ -1,12 +1,14 @@
settings.group.title=ZLS Settings
settings.zls-path.label=Executable path
settings.zls-path.tooltip=Path to the ZLS Binary
settings.zls-path.browse.title=Path to the ZLS Binary
settings.zls-version.label=Detected ZLS version
settings.zls-config-path.label=Config path
settings.zls-config-path.tooltip=Leave empty to use built-in config generated from the settings below
settings.zls-config-path.browse.title=Path to the Custom ZLS Config File (Optional)
settings.inlay-hints-group.label=Inlay Hints
settings.inlay-hints-enable.label=Enable
settings.inlay-hints-enable.tooltip=Toggle this to enable/disable all inlay hints
settings.inlay-hints-max-size.label=Maximum Size
settings.inlay-hints-max-size.tooltip=The maximum size of a zig file to show inlay hints for.\nInlay hints in very large files make the IDE lag.\nSet to 0 to disable.
settings.inlay-hints-max-size.unit=KB
settings.enable_snippets.label=Enable snippets
settings.enable_snippets.tooltip=Enables snippet completions when the client also supports them
settings.enable_argument_placeholders.label=Enable argument placeholders
@ -48,6 +50,8 @@ settings.build_runner_path.tooltip=Specify a custom build runner to resolve buil
settings.global_cache_path.label=Global cache path
settings.global_cache_path.tooltip=Path to a directory that will be used as zig's cache. Will default to `${KnownFolders.Cache}/zls`.
notification.group.zigbrains-lsp=ZigBrains LSP Integration
notification.message.could-not-detect.content=Could not detect ZLS binary, please configure it
notification.message.zls-exe-path-invalid.content=ZLS executable path could not be parsed: {0}
notification.message.zls-exe-not-exists.content=ZLS executable does not exist: {0}
notification.message.zls-exe-not-executable.content=ZLS executable is not an executable file: {0}
notification.message.zls-config-not-exists.content=ZLS config file does not exist: {0}
@ -57,6 +61,7 @@ notification.message.zls-config-autogen-failed.content=Failed to autogenerate ZL
notification.banner.zls-not-running=Zig Language Server is not running. Check the [Language Servers] tool menu!
notification.banner.zls-bad-config=Zig Language Server is misconfigured. Check [Settings | Languages \\& Frameworks | Zig]!
progress.title.create-connection-provider=Creating ZLS connection provider
progress.title.validate=Validating ZLS
# suppress inspection "UnusedProperty"
lsp.zls.name=Zig Language Server
# suppress inspection "UnusedProperty"
@ -68,15 +73,3 @@ settings.model.none.text=<No ZLS>
settings.model.loading.text=Loading\u2026
settings.model.from-disk.text=Add ZLS from disk\u2026
settings.model.download.text=Download ZLS\u2026
settings.downloader.title=Install ZLS
settings.downloader.progress.fetch=Fetching ZLS version information
settings.downloader.progress.install=Installing ZLS {}
settings.downloader.service.index=ZLS version information
settings.local-selector.title=Select ZLS from disk
settings.local-selector.chooser.title=ZLS Binary
settings.local-selector.state.invalid=Invalid ZLS path
settings.local-selector.state.ok=ZLS path OK
dialog.title.zls=Path to the ZLS Executable
settings.panel.path.label=Path:
settings.panel.version.label=Version:
settings.panel.settings.group.label=Settings:

View file

@ -23,6 +23,8 @@
set -e
declare -a branches=("dev" "master" "242" "241")
die () {
echo >&2 "$@"
exit 1
@ -30,4 +32,8 @@ die () {
[ "$#" -eq 1 ] || die "1 argument required, $# provided"
git push --atomic "$1" "dev" "master" "243" "242" "241"
for i in "${branches[@]}"
do
echo "Pushing branch $i"
git push "$1" "$i"
done

View file

@ -4,7 +4,7 @@
<vendor>FalsePattern</vendor>
<depends config-file="zigbrains-core.xml">com.intellij.modules.platform</depends>
<depends config-file="zigbrains-lsp.xml">com.redhat.devtools.lsp4ij</depends>
<depends config-file="zigbrains-lsp.xml" optional="true">com.redhat.devtools.lsp4ij</depends>
<depends config-file="zigbrains-debugger.xml" optional="true">com.intellij.modules.cidr.debugger</depends>
<depends config-file="zigbrains-cidr.xml" optional="true">com.intellij.cidr.base</depends>
<depends config-file="zigbrains-clion.xml" optional="true">com.intellij.clion</depends>