Compare commits

...

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

512 changed files with 21337 additions and 17889 deletions

100
.gitignore vendored
View file

@ -1,93 +1,5 @@
# Created by https://www.toptal.com/developers/gitignore/api/intellij+all,gradle,java
# Edit at https://www.toptal.com/developers/gitignore?templates=intellij+all,gradle,java
### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
.idea/artifacts
.idea/compiler.xml
.idea/jarRepositories.xml
.idea/modules.xml
.idea/*.iml
.idea/modules
*.iml
*.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij+all Patch ###
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.
.idea/*
!.idea/codeStyles
!.idea/runConfigurations
# Created by https://www.toptal.com/developers/gitignore/api/gradle,java
# Edit at https://www.toptal.com/developers/gitignore?templates=gradle,java
### Java ###
# Compiled class file
@ -142,9 +54,11 @@ gradle-app.setting
# Java heap dump
*.hprof
# End of https://www.toptal.com/developers/gitignore/api/intellij+all,gradle,java
# End of https://www.toptal.com/developers/gitignore/api/gradle,java
.idea
.intellijPlatform
jbr
secrets
.idea/runConfigurations/*.xml
.intellijPlatform
!**/src/**/build/
.kotlin

View file

@ -1,92 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JavaCodeStyleSettings>
<option name="ANNOTATION_PARAMETER_WRAP" value="2" />
<option name="ALIGN_MULTILINE_ANNOTATION_PARAMETERS" value="true" />
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99999" />
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99999" />
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
<value />
</option>
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="" withSubpackages="true" static="false" />
<emptyLine />
<package name="net.minecraft" withSubpackages="true" static="false" />
<package name="net.minecraftforge" withSubpackages="true" static="false" />
<package name="cpw" withSubpackages="true" static="false" />
<emptyLine />
<package name="javax" withSubpackages="true" static="false" />
<package name="java" withSubpackages="true" static="false" />
<emptyLine />
<package name="" withSubpackages="true" static="true" />
</value>
</option>
<option name="JD_ADD_BLANK_AFTER_PARM_COMMENTS" value="true" />
<option name="JD_ADD_BLANK_AFTER_RETURN" value="true" />
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<ScalaCodeStyleSettings>
<option name="MULTILINE_STRING_CLOSING_QUOTES_ON_NEW_LINE" value="true" />
</ScalaCodeStyleSettings>
<codeStyleSettings language="Glsl">
<option name="SPACE_BEFORE_COLON" value="false" />
</codeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="KEEP_LINE_BREAKS" value="false" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
<option name="ALIGN_MULTILINE_ASSIGNMENT" value="true" />
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
<option name="ALIGN_MULTILINE_METHOD_BRACKETS" value="true" />
<option name="ALIGN_MULTILINE_PARENTHESIZED_EXPRESSION" value="true" />
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
<option name="CALL_PARAMETERS_WRAP" value="1" />
<option name="RESOURCE_LIST_WRAP" value="1" />
<option name="EXTENDS_LIST_WRAP" value="1" />
<option name="THROWS_LIST_WRAP" value="1" />
<option name="EXTENDS_KEYWORD_WRAP" value="1" />
<option name="THROWS_KEYWORD_WRAP" value="1" />
<option name="METHOD_CALL_CHAIN_WRAP" value="5" />
<option name="BINARY_OPERATION_WRAP" value="1" />
<option name="TERNARY_OPERATION_WRAP" value="1" />
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
<option name="FOR_STATEMENT_WRAP" value="1" />
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
<option name="ASSIGNMENT_WRAP" value="1" />
<option name="ASSERT_STATEMENT_WRAP" value="1" />
<option name="ASSERT_STATEMENT_COLON_ON_NEXT_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<option name="PARAMETER_ANNOTATION_WRAP" value="1" />
<option name="VARIABLE_ANNOTATION_WRAP" value="5" />
<option name="ENUM_CONSTANTS_WRAP" value="2" />
<option name="WRAP_ON_TYPING" value="0" />
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="TypeScript">
<indentOptions>
<option name="INDENT_SIZE" value="2" />
<option name="CONTINUATION_INDENT_SIZE" value="2" />
<option name="TAB_SIZE" value="2" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View file

@ -1,5 +0,0 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View file

@ -17,6 +17,323 @@ 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
- LSP
- Error/Warning banner at the top of the editor when ZLS is misconfigured/not running
- ZLS version indicator in the zig settings
- Toolchain
- More descriptive error messages when toolchain detection fails
### Changed
- Project
- !!BREAKING CHANGE!! Changed file format of zig tasks to store command line arguments as strings instead of string lists.
This (and newer) versions of the plugin will automatically upgrade tasks from 21.1.0 and before.
### Fixed
- Debugging
- Breakpoints could not be placed inside zig code in Android Studio
- Project
- Zig run/debug configuration command line arguments would lose quotes around arguments
## [21.1.0]
### Added
- Zon
- ZLS integration
### Changed
- Zon
- Fully refactored the parser for parity with the zig parser
## [21.0.0]
### Added
- Zig
- Changing the zig standard library path in the project settings now properly updates the dependency
- ZLS
- All of the config options are now exposed in the GUI
### Changed
- Project
- New project panel is now much more compact
### Fixed
- Zig
- `zig env` failure causes an IDE error
- A local toolchain disappearing (std directory or zig exe deleted) is now handled properly
## [20.3.0]
- Zig
- Improved default colors
## [20.2.2]
### Fixed
- Debugging
- `zig build run` would run the process twice, one without, one with debugging
## [20.2.1]
### Fixed
- Zig
- Lexer error when a zig file has a comment or multiline string at the end of file without trailing newline
## [20.2.0]
### Added
- Zig
- Live template support
## [20.1.3]
### Added
- Project
- `.zig-cache` directory added to autogenerated gitignore in the project generator
### Fixed
- Project
- Zig Build tool window crashes when opening remote projects
## [20.1.2]
### Fixed
- Zig
- Source file path highlighter made the terminal lag with some files
- Non-terminating rule in lexer could make the editor hang
## [20.1.1]
### Fixed
- Zig
- Unterminated string at the end of the file soft-locks the editor
- Trailing commas in for loop parameters don't get parsed correctly
## [20.1.0]
### Added
- Zig
- String, character literal, and `@"identifier"` quote matching
### Fixed
- Zon
- Broken string quote handling
## [20.0.4]
### Fixed
- Renamed Zig new file task to "Zig File" and moved to the file creation group
## [20.0.3]
### Fixed
- Project
- "Save all documents" hanging when trying to run a zig file
## [20.0.2]
### Added
- Zig
- Escape sequence highlighting in char literals
### Changed
- Project
- Direnv now only runs automatically in trusted projects
- Toolchain autodetection is now done in the background on project load
### Fixed
- Zig
- Unicode characters in char literals triggered an error
## [20.0.1]
### Fixed
- Project
- IDE freezes when opening a zig project / doing zig init
- Test tasks don't work and try to run the file as an executable
- Zig
- Struct fields being styled as static fields instead of instance fields
## [20.0.0]
### Added
- Debugging
- Progress indicator while zig is compiling the debuggable exe
### Changed
- The entire plugin has been re-implemented in Kotlin
### Fixed
- Most of the internals have been rewritten to be fully asynchronous, so freezes should happen way less
## [19.3.0]
### Added
@ -24,7 +341,7 @@ Changelog structure reference:
- Toolchains, Run Configurations
- [Direnv](https://github.com/direnv/direnv) support
### Fixed
### Fixed
- Zig
- Missing description for string conversion intentions

View file

@ -1,53 +0,0 @@
# Code of Merit
1. The project creators, lead developers, core team, constitute
the managing members of the project and have final say in every decision
of the project, technical or otherwise, including overruling previous decisions.
There are no limitations to this decisional power.
2. Contributions are an expected result of your membership on the project.
Don't expect others to do your work or help you with your work forever.
3. All members have the same opportunities to seek any challenge they want
within the project.
4. Authority or position in the project will be proportional
to the accrued contribution. Seniority must be earned.
5. Software is evolutive: the better implementations must supersede lesser
implementations. Technical advantage is the primary evaluation metric.
6. This is a space for technical prowess; topics outside of the project
will not be tolerated.
7. Non technical conflicts will be discussed in a separate space. Disruption
of the project will not be allowed.
8. Individual characteristics, including but not limited to,
body, sex, sexual preference, race, language, religion, nationality,
or political preferences are irrelevant in the scope of the project and
will not be taken into account concerning your value or that of your contribution
to the project.
9. Discuss or debate the idea, not the person.
10. There is no room for ambiguity: Ambiguity will be met with questioning;
further ambiguity will be met with silence. It is the responsibility
of the originator to provide requested context.
11. If something is illegal outside the scope of the project, it is illegal
in the scope of the project. This Code of Merit does not take precedence over
governing law.
12. This Code of Merit governs the technical procedures of the project not the
activities outside of it.
13. Participation on the project equates to agreement of this Code of Merit.
14. No objectives beyond the stated objectives of this project are relevant
to the project. Any intent to deviate the project from its original purpose
of existence will constitute grounds for remedial action which may include
expulsion from the project.
This document is adapted from the Code of Merit, version 1.0.
See: https://codeofmerit.org/.

View file

@ -1,19 +0,0 @@
For now this project is still pretty small, but here are some general guidelines on how to contribute to this repository
- This project ships with a code style config in .idea, your IDE should automatically apply it when you pull the repo.
When making pull requests, please try to keep to this style as much as possible.
- Use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/), and scope as much as you can.
- Do not edit the relationship tree between modules without unanimous agreement from the project lead.
If you want an "upstream" module (for instance, the LSP) receive data from a "downstream" module
(for instance, the project module), you should use service providers, dependency injections, or other ways to implement
the dataflow in a way where the upstream module is not directly aware of downstream modules.
The main purpose of this is to avoid any circular dependencies, which could cause proprietary IDE-only features
(for instance, the CLion debugger module) to be depended on by FOSS modules. This restriction is non-negotiable.
- Any complex language inspection, syntax action, or similar, should be done by ZLS, and ZigBrains will just act as an
adapter for these features. This notion of "complexity" is determined by the project maintainer. Open an issue
that explains the feature before you start implementing anything!

42
LICENSE
View file

@ -1,36 +1,35 @@
All code in this project, unless specified differently, is licensed under the Apache 2.0 license.
Graphical assets derived from the Zig logo are governed by a different license, included below.
--------------------------------
ZigBrains
Copyright (C) 2023-2025 FalsePattern
All Rights Reserved
Copyright 2023-2024 FalsePattern
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
This program 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.
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
--------------------------------
The following sections are the licenses of code derived from third party projects:
--------------------------------
The code inside the `lsp` package is derived from the LSP4IntelliJ project, with various modifications, fixes, and
additions to fix any outstanding issues i was having with the original code. (https://github.com/ballerina-platform/lsp4intellij)
The original code is Copyright WSO2 Inc., licensed under the `Apache 2.0` license.
--------------------------------
The art assets inside src/art/zig, and all copies of them, are derived from the official Zig Programming Language logo,
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.
--------------------------------
@ -41,4 +40,7 @@ developed by the intellij-rust team (https://github.com/intellij-rust), licensed
All of the licenses listed here are available in the following files, bundled with the plugin:
- licenses/APACHE_2.0.LICENSE
- licenses/CC_BY_SA_4.0.LICENSE
- licenses/INTELLIJ-RUST.LICENSE
- licenses/GPL3.LICENSE
- licenses/INTELLIJ-RUST.LICENSE
- licenses/LGPL3.LICENSE
- licenses/ZLS.LICENSE

View file

@ -1,36 +0,0 @@
## What is this?
This document describes the relationships between the different "modules" of ZigBrains.
These relationships are strictly enforced, and modules that do not directly (or indirectly) depend on another
module cannot access their classes.
The primary purpose of this strictly enforced module system is to avoid code compatible with open source IDEs (IDEA/PyCharm Community)
from depending on code that only works on proprietary IDEs (IDEA Ultimate/CLion/...).
NOTE: These "modules" are in no way related to the module system introduced in Java 9. They're just called the same.
The suffix after the module name signifies which IDE it depends on.
- IC: IDEA Community
- CL: CLion
IC modules MUST NOT depend on CL modules, as this violates the restrictions set forth above.
## Modules
### Common (IC)
### Zig (IC)
- Common (IC)
### Project (IC)
- Common (IC)
- Zig (IC)
### Zon (IC)
- Common (IC)
### Debugger (IU/CL)
- Common (IC)
- Zig (IC)
- Project (IC)

View file

@ -1,10 +1,10 @@
# ZigBrains
Zig language support for IntelliJ IDEA, CLion, and other JetBrains IDEs.
Zig language support for IntelliJ IDEA, CLion, and other JetBrains IDEs. Now written in Kotlin!
## Installing
# Installing
You can either install this plugin from the [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/22456-zigbrains), or from FalsePattern's [website](https://falsepattern.com/zigbrains).
You can either install this plugin from the [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/22456-zigbrains), or from [FalsePattern's website](https://falsepattern.com/zigbrains).
See [the quick setup guide](#quick-setup-guide-for-zig-and-zls) for how to set up language server integration.
@ -15,53 +15,13 @@ 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
- `2023.3.*`: https://falsepattern.com/zigbrains/updatePlugins-233.xml
- `2023.2.*`: https://falsepattern.com/zigbrains/updatePlugins-232.xml
4. Click `OK`, and your IDE should now automatically detect the latest version
(both in the Installed tab and in the Marketplace tab), even if it's not yet verified on the official JetBrains marketplace yet.
## Developer guide
### All platforms
After importing the gradle project, you need to run the `build setup -> generateSources` tasks.
### NixOS
In addition to the generated sources, you also need to run the `build setup -> nixos_jbr` task, otherwise java will
complain about missing files
## Special Thanks
- The [ZigTools](https://github.com/zigtools/) team for developing the Zig Language Server.
- [HTGAzureX1212](https://github.com/HTGAzureX1212) for developing [intellij-zig](https://github.com/intellij-zig/intellij-zig),
which served as a fantastic reference for deep IDE integration features.
- The members of the `Zig Programming Language` discord server's `#tooling-dev` channel for providing encouragement,
feedback, and lots of bug reports.
- The Ballerina Platform developers for `lsp4intellij`, the language server connector between the IntelliJ platform
and the Eclipse LSP4J project.
- The developers of the [intellij-rust](https://github.com/intellij-rust/intellij-rust/) plugin for providing an
excellent example on how to write debugger support that doesn't depend on CLion.
- All the people who have generously funded the project
- gree7
- xceno
- AnErrupTion
- Every contributor who helped with bugfixes and extra features
- [gatesn](https://github.com/gatesn)
- [MarioAriasC](https://github.com/MarioAriasC)
- [JensvandeWiel](https://github.com/JensvandeWiel)
- And everyone who actively reported issues and helped ironing out all the remaining problems
## Versioning scheme
To reduce confusion and to better utilize semver, the plugin uses the following versioning scheme:
@ -74,24 +34,45 @@ Note: before version 11, the version scheme used was 0.X.Y, without separate pat
As this plugin will constantly be evolving together with the zig language, it makes no sense to keep the 0 prefix,
and might as well utilize the full semver string for extra information.
# Credits
## Supporters
- ### [Techatrix](https://github.com/Techatrix)
- ### [nuxusr](https://github.com/nuxusr)
- gree7
- xceno
- AnErrupTion
## Contributors
- [gatesn](https://github.com/gatesn)
- [MarioAriasC](https://github.com/MarioAriasC)
- [JensvandeWiel](https://github.com/JensvandeWiel)
## Additional Thanks
- The [ZigTools](https://github.com/zigtools/) team for developing the Zig Language Server.
- [HTGAzureX1212](https://github.com/HTGAzureX1212) for developing [intellij-zig](https://github.com/intellij-zig/intellij-zig),
which served as a fantastic reference for deep IDE integration features.
- The members of the `Zig Programming Language` discord server's `#tooling-dev` channel for providing encouragement,
feedback, and lots of bug reports.
- The developers of [LSP4IJ](https://github.com/redhat-developer/lsp4ij) for providing a really good LSP backend
- The developers of the [intellij-rust](https://github.com/intellij-rust/intellij-rust/) plugin for providing an
excellent example on how to write debugger support that doesn't depend on CLion.
- And everyone who actively reported issues and helped ironing out all the remaining problems
# Description
<!-- 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
## Features
- Integration with the Zig Language Server (ZLS)
- Basic syntax highlighting if ZLS is not available
- Run and debug configurations for Zig projects (Debugging has some IDE limitations, and on Windows you need to do some extra setup, see below)
- Direnv support for loading environment variables from `.envrc` files (requires the direnv executable to be installed system-wide)
- Language injection support in Zig strings
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
@ -105,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).

View file

@ -1,414 +1,211 @@
import de.undercouch.gradle.tasks.download.Download
import groovy.xml.XmlParser
import org.jetbrains.changelog.Changelog
import org.jetbrains.changelog.markdownToHTML
import org.jetbrains.grammarkit.tasks.GenerateLexerTask
import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType
import org.jetbrains.intellij.platform.gradle.tasks.PatchPluginXmlTask
import org.jetbrains.intellij.platform.gradle.tasks.PublishPluginTask
import org.jetbrains.intellij.platform.gradle.utils.extensionProvider
fun properties(key: String) = providers.gradleProperty(key) as Provider<String>
fun environment(key: String) = providers.environmentVariable(key) as Provider<String>
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask
plugins {
java
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`
`java-library`
id("org.jetbrains.intellij.platform") version("2.1.0")
id("org.jetbrains.changelog") version("2.2.1")
id("org.jetbrains.grammarkit") version("2022.3.2.2")
id("de.undercouch.download") version("5.6.0")
}
val publishVersions = listOf("232", "233", "241", "242", "243")
val gitVersion: groovy.lang.Closure<String> by extra
val grammarKitGenDir = "build/generated/sources/grammarkit/java"
val rootPackage = "com.falsepattern.zigbrains"
val rootPackagePath = rootPackage.replace('.', '/')
// Keep these in sync with whatever the oldest IDE version we're targeting in gradle.properties needs
val javaLangVersion: JavaLanguageVersion = JavaLanguageVersion.of(21)
val javaVersion = JavaVersion.VERSION_21
val baseIDE = properties("baseIDE").get()
val ideaVersion = properties("ideaVersion").get()
val clionVersion = properties("clionVersion").get()
val clionPlugins = listOf("com.intellij.clion", "com.intellij.cidr.lang", "com.intellij.cidr.base", "com.intellij.nativeDebug")
val lsp4jVersion = "0.21.1"
val lsp4ijVersion = "0.7.0"
val lsp4ijNightly = lsp4ijVersion.contains("-")
val lsp4ijDepString = "${if (lsp4ijNightly) "nightly." else ""}com.jetbrains.plugins:com.redhat.devtools.lsp4ij:$lsp4ijVersion"
val publishVersions = listOf("241", "242", "243", "251")
val pluginVersionFull get() = "$pluginVersion-$pluginSinceBuild"
val pluginVersion: String by project
val pluginSinceBuild: String by project
val pluginUntilBuild: String by project
val javaVersion = property("javaVersion").toString().toInt()
val lsp4ijVersion: String by project
val runIdeTarget: String by project
val lsp4ijNightly = property("lsp4ijNightly").toString().toBoolean()
val useInstaller = property("useInstaller").toString().toBoolean()
val lsp4ijPluginString = "com.redhat.devtools.lsp4ij:$lsp4ijVersion${if (lsp4ijNightly) "@nightly" else ""}"
val ideaCommunityVersion: String by project
val clionVersion: String by project
val lsp4ijDep: DependencyHandler.() -> Unit = {
intellijPlatformPluginDependency(lsp4ijDepString)
compileOnlyApi(lsp4ijDepString)
compileOnlyApi("org.eclipse.lsp4j:org.eclipse.lsp4j:$lsp4jVersion")
group = "com.falsepattern"
version = pluginVersionFull
subprojects {
apply(plugin = "java")
apply(plugin = "org.jetbrains.kotlin.jvm")
apply(plugin = "org.jetbrains.intellij.platform.module")
apply(plugin = "idea")
extensions.configure<KotlinJvmProjectExtension>("kotlin") {
jvmToolchain(javaVersion)
}
tasks.withType<KotlinCompilationTask<*>>().configureEach {
compilerOptions {
freeCompilerArgs.addAll("-Xlambdas=indy")
}
}
}
tasks {
wrapper {
gradleVersion = properties("gradleVersion").get()
processResources {
from("LICENSE")
from("licenses") {
into("licenses")
}
}
}
fun pluginVersion(): Provider<String> {
return provider {
System.getenv("RELEASE_VERSION")
}.orElse(properties("pluginVersion"))
}
fun pluginVersionFull(): Provider<String> {
return pluginVersion().map { it + "-" + properties("pluginSinceBuild").get() }
}
allprojects {
apply {
plugin("org.jetbrains.intellij.platform")
}
repositories {
mavenCentral()
intellijPlatform {
localPlatformArtifacts {
content {
includeGroup("bundledPlugin")
}
}
marketplace {
content {
includeGroup("com.jetbrains.plugins")
includeGroup("nightly.com.jetbrains.plugins")
}
}
snapshots {
content {
includeModule("com.jetbrains.intellij.clion", "clion")
includeModule("com.jetbrains.intellij.idea", "ideaIC")
includeModule("com.jetbrains.intellij.idea", "ideaIU")
}
}
}
}
dependencies {
compileOnly("org.projectlombok:lombok:1.18.32")
annotationProcessor("org.projectlombok:lombok:1.18.32")
if (path !in listOf(":", ":plugin", ":debugger", ":cidr")) {
intellijPlatform {
intellijIdeaCommunity(ideaVersion, useInstaller = false)
}
idea {
module {
isDownloadSources = true
}
}
if (path in listOf(":zig", ":zon")) {
apply {
plugin("org.jetbrains.grammarkit")
}
sourceSets {
main {
java {
srcDirs(
"${grammarKitGenDir}/lexer",
"${grammarKitGenDir}/parser"
)
}
}
}
tasks {
generateLexer {
enabled = true
purgeOldFiles = true
}
generateParser {
enabled = true
targetRootOutputDir = file("${grammarKitGenDir}/parser")
}
register<DefaultTask>("generateGrammars") {
description = "Generate source code from parser/lexer definitions"
group = "build setup"
dependsOn("generateLexer")
dependsOn("generateParser")
}
compileJava {
dependsOn("generateGrammars")
}
}
}
configure<JavaPluginExtension> {
java {
toolchain {
languageVersion.set(javaLangVersion)
languageVersion = JavaLanguageVersion.of(javaVersion)
@Suppress("UnstableApiUsage")
vendor = JvmVendorSpec.JETBRAINS
}
sourceCompatibility = javaVersion
targetCompatibility = javaVersion
sourceCompatibility = JavaVersion.toVersion(javaVersion)
targetCompatibility = JavaVersion.toVersion(javaVersion)
}
tasks.withType(JavaCompile::class) {
options.encoding = "UTF-8"
}
group = properties("pluginGroup").get()
version = pluginVersionFull().get()
tasks {
runIde { enabled = false }
prepareSandbox { enabled = false }
buildSearchableOptions { enabled = false }
verifyPlugin { enabled = false }
buildPlugin { enabled = false }
signPlugin { enabled = false }
verifyPluginProjectConfiguration { enabled = false }
withType<PatchPluginXmlTask> {
sinceBuild = properties("pluginSinceBuild")
untilBuild = properties("pluginUntilBuild").flatMap {provider { it.ifBlank { null } }}
}
}
intellijPlatform {
instrumentCode = false
buildSearchableOptions = false
}
}
project(":common") {
}
project(":zig") {
apply {
plugin("java-library")
}
dependencies {
implementation(project(":common"))
lsp4ijDep()
intellijPlatform {
plugin(lsp4ijPluginString)
bundledPlugin("org.intellij.intelliLang")
}
}
tasks {
generateLexer {
sourceFile = file("src/main/grammar/Zig.flex")
targetOutputDir = file("${grammarKitGenDir}/lexer/${rootPackagePath}/zig/lexer")
}
register<GenerateLexerTask>("generateStringLexer") {
sourceFile = file("src/main/grammar/ZigString.flex")
targetOutputDir = file("${grammarKitGenDir}/lexer/${rootPackagePath}/zig/stringlexer")
purgeOldFiles = true
}
generateParser {
sourceFile = file("src/main/grammar/Zig.bnf")
pathToParser = "${rootPackagePath}/zig/psi/ZigParser.java"
pathToPsiRoot = "${rootPackagePath}/zig/psi"
}
named("generateGrammars") {
dependsOn("generateStringLexer")
}
}
}
project(":project") {
dependencies {
implementation(project(":common"))
implementation(project(":zig"))
}
}
project(":cidr") {
dependencies {
implementation(project(":common"))
implementation(project(":project"))
intellijPlatform {
clion(clionVersion, useInstaller = false)
for (p in clionPlugins) {
bundledPlugin(p)
}
}
}
}
project(":debugger") {
dependencies {
implementation(project(":zig"))
implementation(project(":project"))
implementation(project(":common"))
implementation("org.eclipse.lsp4j:org.eclipse.lsp4j.debug:$lsp4jVersion") {
exclude("org.eclipse.lsp4j", "org.eclipse.lsp4j")
exclude("org.eclipse.lsp4j", "org.eclipse.lsp4j.jsonrpc")
exclude("com.google.code.gson", "gson")
}
intellijPlatform {
clion(clionVersion, useInstaller = false)
for (p in clionPlugins) {
bundledPlugin(p)
}
}
}
val genOutputDir = layout.buildDirectory.dir("generated-resources")
sourceSets["main"].resources.srcDir(genOutputDir)
tasks {
register<Download>("downloadProps") {
src("https://falsepattern.com/zigbrains/msvc.properties")
dest(genOutputDir.map { it.file("msvc.properties") })
}
processResources {
dependsOn("downloadProps")
}
}
}
project(":zon") {
dependencies {
implementation(project(":common"))
}
tasks {
generateLexer {
sourceFile = file("src/main/grammar/Zon.flex")
targetOutputDir = file("${grammarKitGenDir}/lexer/${rootPackagePath}/zon/lexer")
}
generateParser {
sourceFile = file("src/main/grammar/Zon.bnf")
pathToParser = "${rootPackagePath}/zon/psi/ZonParser.java"
pathToPsiRoot = "${rootPackagePath}/zon/psi"
}
}
}
project(":plugin") {
dependencies {
implementation(project(":common"))
implementation(project(":zig"))
implementation(project(":project"))
implementation(project(":zon"))
implementation(project(":cidr"))
implementation(project(":debugger"))
intellijPlatform {
zipSigner()
pluginVerifier()
when (baseIDE) {
"idea" -> intellijIdeaCommunity(ideaVersion, useInstaller = false)
"clion" -> clion(clionVersion, useInstaller = false)
}
plugin(lsp4ijPluginString)
}
}
intellijPlatform {
projectName = "ZigBrains"
pluginConfiguration {
name = properties("pluginName")
description = providers.fileContents(rootProject.layout.projectDirectory.file("README.md")).asText.map {
val start = "<!-- Plugin description -->"
val end = "<!-- Plugin description end -->"
with(it.lines()) {
if (!containsAll(listOf(start, end))) {
throw GradleException("Plugin description section not found in README.md:\n$start ... $end")
}
subList(indexOf(start) + 1, indexOf(end)).joinToString("\n").let(::markdownToHTML)
repositories {
exclusiveContent {
forRepository {
maven {
setUrl("https://mvn.falsepattern.com/releases")
name = "mavenpattern"
}
}
changeNotes = pluginVersion().map { pluginVersion ->
with(rootProject.changelog) {
renderItem(
(getOrNull(pluginVersion) ?: getUnreleased())
.withHeader(false)
.withEmptySections(false),
Changelog.OutputType.HTML,
)
}
}
version = pluginVersionFull()
}
signing {
certificateChainFile = rootProject.file("secrets/chain.crt")
privateKeyFile = rootProject.file("secrets/private.pem")
password = environment("PRIVATE_KEY_PASSWORD")
}
pluginVerification {
ides {
ide(IntelliJPlatformType.IntellijIdeaCommunity, ideaVersion, useInstaller = false)
ide(IntelliJPlatformType.IntellijIdeaUltimate, ideaVersion, useInstaller = false)
ide(IntelliJPlatformType.CLion, clionVersion, useInstaller = false)
filter {
includeModule("com.redhat.devtools.intellij", "lsp4ij")
}
}
}
mavenCentral()
tasks {
runIde {
enabled = true
}
prepareSandbox {
enabled = true
}
verifyPlugin {
enabled = true
}
verifyPluginProjectConfiguration {
enabled = true
}
signPlugin {
enabled = true
}
verifyPluginSignature {
dependsOn(signPlugin)
}
buildPlugin {
enabled = true
intellijPlatform {
defaultRepositories()
snapshots()
}
}
}
dependencies {
intellijPlatform {
when (baseIDE) {
"idea" -> intellijIdeaCommunity(ideaVersion, useInstaller = false)
"clion" -> clion(clionVersion, useInstaller = false)
when(runIdeTarget) {
"ideaCommunity" -> create(IntelliJPlatformType.IntellijIdeaCommunity, ideaCommunityVersion, useInstaller = useInstaller)
"clion" -> create(IntelliJPlatformType.CLion, clionVersion, useInstaller = useInstaller)
}
pluginVerifier()
zipSigner()
plugin(lsp4ijPluginString)
}
runtimeOnly(project(":core"))
runtimeOnly(project(":cidr"))
runtimeOnly(project(":lsp"))
}
intellijPlatform {
pluginConfiguration {
version = pluginVersionFull
description = providers.fileContents(layout.projectDirectory.file("README.md")).asText.map {
val start = "<!-- Plugin description -->"
val end = "<!-- Plugin description end -->"
with(it.lines()) {
if (!containsAll(listOf(start, end))) {
throw GradleException("Plugin description section not found in README.md:\n$start ... $end")
}
subList(indexOf(start) + 1, indexOf(end)).joinToString("\n").let(::markdownToHTML)
}
}
val changelog = project.changelog
changeNotes = provider { pluginVersion }.map { pluginVersion ->
with(changelog) {
renderItem(
(getOrNull(pluginVersion) ?: getUnreleased())
.withHeader(false)
.withEmptySections(false),
Changelog.OutputType.HTML
)
}
}
ideaVersion {
sinceBuild = pluginSinceBuild
if (pluginUntilBuild.isNotBlank()) {
untilBuild = pluginUntilBuild
} else {
untilBuild = provider { null }
}
}
}
signing {
certificateChainFile = file("secrets/chain.crt")
privateKeyFile = file("secrets/private.pem")
password = providers.environmentVariable("PRIVATE_KEY_PASSWORD")
}
pluginVerification {
ides {
select {
types = listOf(
IntelliJPlatformType.IntellijIdeaCommunity,
IntelliJPlatformType.IntellijIdeaUltimate,
IntelliJPlatformType.CLion
)
}
}
}
buildSearchableOptions = false
instrumentCode = false
}
changelog {
groups.empty()
repositoryUrl = providers.gradleProperty("pluginRepositoryUrl")
}
tasks {
generateLexer {
publishPlugin {
dependsOn(patchChangelog)
}
compileJava {
enabled = false
}
generateParser {
classes {
enabled = false
}
verifyPluginSignature {
certificateChainFile = file("secrets/chain.crt")
inputArchiveFile = signPlugin.map { it.signedArchiveFile }.get()
dependsOn(signPlugin)
}
publishPlugin {
enabled = false
}
}
fun distFile(it: String) = layout.buildDirectory.file("dist/ZigBrains-${pluginVersion().get()}-$it-signed.zip")
fun distFile(it: String) = layout.buildDirectory.file("dist/ZigBrains-$pluginVersion-$it-signed.zip")
publishVersions.forEach {
tasks.register<PublishPluginTask>("jbpublish-$it").configure {
archiveFile = distFile(it)
token = environment("IJ_PUBLISH_TOKEN")
channels = if (pluginVersion().get().contains("-")) listOf("nightly") else listOf("default")
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")
@ -420,7 +217,7 @@ publishing {
create<MavenPublication>("maven") {
groupId = "com.falsepattern"
artifactId = "zigbrains"
version = pluginVersion().get()
version = pluginVersion
publishVersions.forEach {
artifact(distFile(it)) {
@ -440,22 +237,4 @@ publishing {
}
}
}
}
fun File.isPluginJar(): Boolean {
if (!isFile) return false
if (extension != "jar") return false
return zipTree(this).files.any { it.isManifestFile() }
}
fun File.isManifestFile(): Boolean {
if (extension != "xml") return false
val rootNode = try {
val parser = XmlParser()
parser.parse(this)
} catch (e: Exception) {
logger.error("Failed to parse $path", e)
return false
}
return rootNode.name() == "idea-plugin"
}
}

View file

@ -1,23 +1,29 @@
#!/bin/sh
#
# Copyright 2023-2024 FalsePattern
# This file is part of ZigBrains.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# Copyright (C) 2023-2025 FalsePattern
# All Rights Reserved
#
# http://www.apache.org/licenses/LICENSE-2.0
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# 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/>.
#
set -e
declare -a branches=("master" "242" "241" "233" "232")
declare -a branches=("master" "243" "242" "241")
DEFAULT_BRANCH="${branches[0]}"
@ -64,7 +70,7 @@ fi
for i in "${branches[@]}"
do
echo "Building branch $i"
git checkout "$i" && ./gradlew :plugin:verifyPluginSignature
git checkout "$i" && ./gradlew verifyPluginSignature
RESULT=$?
if [ $RESULT != 0 ]; then
echo "Failed to build plugin on branch $i!"
@ -76,6 +82,6 @@ git checkout "$DEFAULT_BRANCH"
mkdir -p build/dist
cp modules/plugin/build/distributions/*-signed.zip build/dist/
cp build/distributions/*-signed.zip build/dist/
./gradlew publish

46
cidr/build.gradle.kts Normal file
View file

@ -0,0 +1,46 @@
import de.undercouch.gradle.tasks.download.Download
import org.jetbrains.intellij.platform.gradle.Constants
import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType
plugins {
id("de.undercouch.download") version("5.6.0")
}
val lsp4jVersion: String by project
val clionVersion: String by project
val useInstaller = property("useInstaller").toString().toBoolean()
val genOutputDir = layout.buildDirectory.dir("generated-resources")
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") })
}
processResources {
dependsOn("downloadProps")
}
}
dependencies {
intellijPlatform {
create(IntelliJPlatformType.CLion, clionVersion, useInstaller = useInstaller)
bundledPlugins("com.intellij.clion", "com.intellij.cidr.base", "com.intellij.nativeDebug")
}
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")
exclude("com.google.code.gson", "gson")
}
compileOnly("org.eclipse.lsp4j:org.eclipse.lsp4j:$lsp4jVersion")
}
configurations[Constants.Configurations.INTELLIJ_PLATFORM_BUNDLED_PLUGINS].dependencies.configureEach {
if (this is ExternalModuleDependency) {
this.isTransitive = false
}
}

View file

@ -0,0 +1,38 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2025 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.cidr
import com.intellij.openapi.project.Project
import com.jetbrains.cidr.project.workspace.CidrWorkspace
import com.jetbrains.cidr.project.workspace.OCRootsSynchronizer
import java.io.File
class ZigWorkspace(project: Project) : CidrWorkspace(project) {
override fun collectRootsInfo(p0: File?): OCRootsSynchronizer.RootsInfo {
return OCRootsSynchronizer.RootsInfo()
}
override fun getClientKey(): String {
return "ZIG_WORKSPACE"
}
}

View file

@ -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.cidr
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.vfs.toNioPathOrNull
import com.jetbrains.cidr.project.workspace.CidrWorkspace
import com.jetbrains.cidr.project.workspace.CidrWorkspaceManager
import com.jetbrains.cidr.project.workspace.CidrWorkspaceProvider
import java.io.IOException
import java.nio.file.Files
import kotlin.io.path.pathString
class ZigWorkspaceProvider: CidrWorkspaceProvider {
override fun getWorkspace(project: Project): CidrWorkspace? {
getExistingWorkspace(project)?.let { return it }
val projectDir = project.guessProjectDir()?.toNioPathOrNull() ?: return null
try {
Files.walk(projectDir).use { files ->
if (files.anyMatch { it.fileName.pathString.let { it2 -> it2.endsWith(".zig") || it2.endsWith(".zig.zon") } }) {
return ZigWorkspace(project)
}
}
} catch (e: IOException) {
e.printStackTrace()
}
return null
}
override fun loadWorkspace(project: Project) {
if (getExistingWorkspace(project) != null)
return
val workspace = getWorkspace(project)
if (workspace != null) {
val manager = CidrWorkspaceManager.getInstance(project)
manager.markInitializing(workspace)
manager.markInitialized(workspace)
manager.markLoading(workspace)
manager.markLoaded(workspace)
}
}
}
private fun getExistingWorkspace(project: Project): ZigWorkspace? {
val workspaces = CidrWorkspaceManager.getInstance(project).workspaces.keys
for (ws in workspaces)
if (ws is ZigWorkspace)
return ws
return null
}

View file

@ -0,0 +1,67 @@
/*
* 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.clion
import com.falsepattern.zigbrains.debugger.ZigDebuggerDriverConfigurationProvider
import com.falsepattern.zigbrains.debugger.settings.ZigDebuggerSettings
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.SystemInfo
import com.jetbrains.cidr.cpp.execution.debugger.backend.CLionGDBDriverConfiguration
import com.jetbrains.cidr.cpp.execution.debugger.backend.CLionLLDBDriverConfiguration
import com.jetbrains.cidr.cpp.toolchains.CPPDebugger
import com.jetbrains.cidr.cpp.toolchains.CPPToolchains
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
class ZigClionDebuggerDriverConfigurationProvider: ZigDebuggerDriverConfigurationProvider() {
override suspend fun getDebuggerConfiguration(
project: Project,
isElevated: Boolean,
emulateTerminal: Boolean
): DebuggerDriverConfiguration? {
if (SystemInfo.isWindows)
return null
if (!ZigDebuggerSettings.instance.useClion)
return null
val toolchains = CPPToolchains.getInstance()
var toolchain = toolchains.getToolchainByNameOrDefault("Zig")
if (toolchain == null || !toolchain.isDebuggerSupported) {
LOG.info("Couldn't find debug-compatible C++ toolchain with name \"Zig\"")
toolchain = toolchains.defaultToolchain
}
if (toolchain == null || !toolchain.isDebuggerSupported) {
LOG.info("Couldn't find debug-compatible C++ default toolchain")
return null
}
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)
}
}
}
private val LOG = logger<ZigClionDebuggerDriverConfigurationProvider>()

View file

@ -0,0 +1,33 @@
/*
* 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.debugbridge
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
interface ZigDebuggerDriverConfigurationProviderBase {
companion object {
val EXTENSION_POINT_NAME = ExtensionPointName.create<ZigDebuggerDriverConfigurationProviderBase>("com.falsepattern.zigbrains.debuggerDriverProvider")
}
suspend fun <T> getDebuggerConfiguration(project: Project, isElevated: Boolean, emulateTerminal: Boolean, klass: Class<T>): T?
}

View file

@ -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.debugger
import com.falsepattern.zigbrains.shared.ZBFeatures
class DebuggerFeatures: ZBFeatures {
override fun getDebug(): Boolean {
return true
}
}

View file

@ -0,0 +1,51 @@
/*
* 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.debugger
import com.intellij.DynamicBundle
import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.PropertyKey
import java.util.function.Supplier
internal object ZigDebugBundle {
@NonNls
const val BUNDLE = "zigbrains.debugger.Bundle"
private val INSTANCE = DynamicBundle(ZigDebugBundle::class.java, BUNDLE)
fun message(
key: @PropertyKey(resourceBundle = BUNDLE) String,
vararg params: Any
): @Nls String {
return INSTANCE.getMessage(key, *params)
}
fun lazyMessage(
key: @PropertyKey(resourceBundle = BUNDLE) String,
vararg params: Any
): Supplier<@Nls String> {
return INSTANCE.getLazyMessage(key, *params)
}
}

View file

@ -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.debugger
import com.falsepattern.zigbrains.debugbridge.ZigDebuggerDriverConfigurationProviderBase
import com.intellij.openapi.project.Project
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
abstract class ZigDebuggerDriverConfigurationProvider: ZigDebuggerDriverConfigurationProviderBase {
final override suspend fun <T> getDebuggerConfiguration(
project: Project,
isElevated: Boolean,
emulateTerminal: Boolean,
klass: Class<T>
): T? {
if (klass != DebuggerDriverConfiguration::class.java)
return null
@Suppress("UNCHECKED_CAST")
return getDebuggerConfiguration(project, isElevated, emulateTerminal) as T
}
protected abstract suspend fun getDebuggerConfiguration(project: Project, isElevated: Boolean, emulateTerminal: Boolean): DebuggerDriverConfiguration?
}

View file

@ -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.debugger
import com.jetbrains.cidr.execution.debugger.CidrDebuggerEditorsExtensionBase
class ZigDebuggerEditorsExtension: CidrDebuggerEditorsExtensionBase() {
}

View file

@ -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.debugger
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriver.DebuggerLanguage
object ZigDebuggerLanguage: DebuggerLanguage {
override fun toString(): String {
return "Zig"
}
}

View file

@ -0,0 +1,41 @@
/*
* 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.debugger
import com.falsepattern.zigbrains.project.execution.base.ZigExecConfig
import com.intellij.execution.configurations.RunProfile
import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider
import com.jetbrains.cidr.execution.debugger.CidrDebuggerLanguageSupport
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriver
class ZigDebuggerLanguageSupport: CidrDebuggerLanguageSupport() {
override fun getSupportedDebuggerLanguages(): Set<DebuggerDriver.DebuggerLanguage> {
return setOf(ZigDebuggerLanguage)
}
override fun createEditor(profile: RunProfile?): XDebuggerEditorsProvider? {
if (profile !is ZigExecConfig<*>)
return null
return createEditorProvider()
}
}

View file

@ -0,0 +1,157 @@
/*
* 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.debugger
import com.falsepattern.zigbrains.debugger.settings.ZigDebuggerSettings
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
import com.intellij.openapi.ui.Messages
import com.jetbrains.cidr.ArchitectureType
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
import com.jetbrains.cidr.execution.debugger.backend.gdb.GDBDriverConfiguration
import com.jetbrains.cidr.execution.debugger.backend.lldb.LLDBDriverConfiguration
import java.io.File
import kotlin.io.path.pathString
class ZigDefaultDebuggerDriverConfigurationProvider: ZigDebuggerDriverConfigurationProvider() {
override suspend fun getDebuggerConfiguration(project: Project, isElevated: Boolean, emulateTerminal: Boolean): DebuggerDriverConfiguration? {
val settings = ZigDebuggerSettings.instance
val service = zigDebuggerToolchainService
val kind = settings.debuggerKind
if (!availabilityCheck(project, kind))
return null
return when(val availability = service.debuggerAvailability(kind)) {
DebuggerAvailability.Bundled -> when(kind) {
DebuggerKind.LLDB -> ZigLLDBDriverConfiguration(isElevated, emulateTerminal)
DebuggerKind.GDB -> ZigGDBDriverConfiguration(isElevated, emulateTerminal)
DebuggerKind.MSVC -> throw AssertionError("MSVC is never bundled")
}
is DebuggerAvailability.Binaries -> when(val binary = availability.binaries) {
is LLDBBinaries -> ZigCustomBinariesLLDBDriverConfiguration(binary, isElevated, emulateTerminal)
is GDBBinaries -> ZigCustomBinariesGDBDriverConfiguration(binary, isElevated, emulateTerminal)
is MSVCBinaries -> ZigMSVCDriverConfiguration(binary, isElevated, emulateTerminal)
else -> throw AssertionError("Unreachable")
}
DebuggerAvailability.Unavailable,
DebuggerAvailability.NeedToDownload,
DebuggerAvailability.NeedToUpdate -> throw AssertionError("Unreachable")
}
}
}
private suspend fun availabilityCheck(project: Project, kind: DebuggerKind): Boolean {
val service = zigDebuggerToolchainService
val availability = service.debuggerAvailability(kind)
val (message, action) = when(availability) {
DebuggerAvailability.Unavailable -> return false
DebuggerAvailability.NeedToDownload ->
ZigDebugBundle.message("debugger.run.unavailable.reason.download") to ZigDebugBundle.message("debugger.run.unavailable.reason.download.button")
DebuggerAvailability.NeedToUpdate ->
ZigDebugBundle.message("debugger.run.unavailable.reason.update") to ZigDebugBundle.message("debugger.run.unavailable.reason.update.button")
DebuggerAvailability.Bundled,
is DebuggerAvailability.Binaries -> return true
}
val downloadDebugger = if (!ZigDebuggerSettings.instance.downloadAutomatically) {
showDialog(project, message, action)
} else {
true
}
if (downloadDebugger) {
val result = withEDTContext(ModalityState.any()) {
service.downloadDebugger(project, kind)
}
if (result is ZigDebuggerToolchainService.DownloadResult.Ok) {
return true
}
}
return false
}
private suspend fun showDialog(project: Project, message: String, action: String): Boolean {
val doNotAsk = object: DoNotAskOption.Adapter() {
override fun rememberChoice(isSelected: Boolean, exitCode: Int) {
if (exitCode == Messages.OK) {
ZigDebuggerSettings.instance.downloadAutomatically = isSelected
}
}
}
return withEDTContext(ModalityState.any()) {
MessageDialogBuilder
.okCancel(ZigDebugBundle.message("debugger.run.unavailable"), message)
.yesText(action)
.icon(Messages.getErrorIcon())
.doNotAsk(doNotAsk)
.ask(project)
}
}
private open class ZigLLDBDriverConfiguration(private val isElevated: Boolean, private val emulateTerminal: Boolean): LLDBDriverConfiguration() {
override fun getDriverName() = "Zig LLDB"
override fun isElevated() = isElevated
override fun emulateTerminal() = emulateTerminal
}
private class ZigCustomBinariesLLDBDriverConfiguration(
private val binaries: LLDBBinaries,
isElevated: Boolean,
emulateTerminal: Boolean
) : ZigLLDBDriverConfiguration(isElevated, emulateTerminal) {
override fun useSTLRenderers() = false
override fun getLLDBFrameworkFile(architectureType: ArchitectureType): File = binaries.frameworkFile.toFile()
override fun getLLDBFrontendFile(architectureType: ArchitectureType): File = binaries.frontendFile.toFile()
}
private open class ZigGDBDriverConfiguration(private val isElevated: Boolean, private val emulateTerminal: Boolean): GDBDriverConfiguration() {
override fun getDriverName() = "Zig GDB"
override fun isAttachSupported() = false
override fun isElevated() = isElevated
override fun emulateTerminal() = emulateTerminal
}
private class ZigCustomBinariesGDBDriverConfiguration(
private val binaries: GDBBinaries,
isElevated: Boolean,
emulateTerminal: Boolean
) : ZigGDBDriverConfiguration(isElevated, emulateTerminal) {
override fun getGDBExecutablePath() = binaries.gdbFile.pathString
}
private class ZigMSVCDriverConfiguration(
private val binaries: MSVCBinaries,
private val isElevated: Boolean,
private val emulateTerminal: Boolean
): MSVCDriverConfiguration() {
override val debuggerExecutable get() = binaries.msvcFile
override fun getDriverName() = "Zig MSVC"
override fun getConsoleLanguage() = ZigLanguage
override fun isElevated() = isElevated
override fun emulateTerminal() = emulateTerminal
}

View file

@ -0,0 +1,33 @@
/*
* 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.debugger
import com.falsepattern.zigbrains.zig.ZigFileType
import com.intellij.openapi.fileTypes.FileType
import com.jetbrains.cidr.execution.debugger.breakpoints.CidrLineBreakpointFileTypesProvider
class ZigLineBreakpointFileTypesProvider: CidrLineBreakpointFileTypesProvider {
override fun getFileTypes(): Set<FileType> {
return setOf(ZigFileType)
}
}

View file

@ -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.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, { Filter.EMPTY_ARRAY }, true)

View file

@ -0,0 +1,49 @@
/*
* 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.debugger
import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.falsepattern.zigbrains.zig.ZigFileType
import com.intellij.openapi.project.Project
import com.intellij.xdebugger.XSourcePosition
import com.jetbrains.cidr.execution.debugger.backend.LLValue
import com.jetbrains.cidr.execution.debugger.evaluation.LocalVariablesFilterHandler
import kotlinx.coroutines.async
import kotlinx.coroutines.future.asCompletableFuture
import java.util.concurrent.CompletableFuture
class ZigLocalVariablesFilterHandler: LocalVariablesFilterHandler {
override fun filterVars(proj: Project, pos: XSourcePosition, vars: List<LLValue>): CompletableFuture<List<LLValue>> {
return proj.zigCoroutineScope.async {
val vf = pos.file
if (vf.fileType == ZigFileType) {
return@async ArrayList(vars)
}
return@async listOf()
}.asCompletableFuture()
}
override fun canFilterAtPos(proj: Project, pos: XSourcePosition): Boolean {
return pos.file.fileType == ZigFileType
}
}

View file

@ -0,0 +1,68 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2025 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.debugger.dap
import java.io.IOException
import java.io.InterruptedIOException
import java.io.PipedInputStream
import java.io.PipedOutputStream
class BlockingPipedInputStream(src: PipedOutputStream, pipeSize: Int) : PipedInputStream(src, pipeSize) {
var closed = false
@Synchronized
override fun read(): Int {
if (closed) {
throw IOException("stream closed")
} else {
while (super.`in` < 0) {
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
this as java.lang.Object
this.notifyAll()
try {
this.wait(750)
} catch (e: InterruptedException) {
throw InterruptedIOException()
}
}
val ret = buffer[this.out++].toUInt()
if (this.out >= buffer.size) {
this.out = 0
}
if (this.`in` == this.out) {
this.`in` = -1
}
return ret.toInt()
}
}
override fun close() {
closed = true
super.close()
}
}

View file

@ -0,0 +1,41 @@
/*
* 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.debugger.dap
import com.intellij.openapi.util.Expirable
import com.intellij.openapi.util.Pair
import com.intellij.openapi.util.UserDataHolderEx
import com.jetbrains.cidr.execution.debugger.backend.*
import org.eclipse.lsp4j.debug.InitializeRequestArguments
abstract class DAPDebuggerDriverConfiguration: DebuggerDriverConfiguration() {
override fun createEvaluationContext(driver: DebuggerDriver, expirable: Expirable?, llThread: LLThread, llFrame: LLFrame, data: UserDataHolderEx): EvaluationContext {
return object : EvaluationContext(driver, expirable, llThread, llFrame, data) {
override fun convertToRValue(data: LLValueData, pair: Pair<LLValue, String>): String {
return cast(pair.second, pair.first?.type)
}
}
}
abstract fun customizeInitializeArguments(initArgs: InitializeRequestArguments)
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,262 @@
/*
* 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.debugger.dap
import com.falsepattern.zigbrains.debugger.ZigDebuggerLanguage
import com.intellij.openapi.application.readAction
import com.intellij.openapi.editor.Document
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.findDocument
import com.jetbrains.cidr.execution.debugger.backend.*
import com.jetbrains.cidr.execution.debugger.memory.Address
import com.jetbrains.cidr.execution.debugger.memory.AddressRange
import org.eclipse.lsp4j.debug.*
import java.nio.file.InvalidPathException
import java.nio.file.Path
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import java.util.regex.Pattern
object Util {
fun threadJBFromDAP(DAPThread: Thread): LLThread {
return LLThread(DAPThread.id.toLong(), null, null, DAPThread.name, null)
}
fun threadDAPFromJB(JBThread: LLThread): Thread {
val DAPThread = Thread()
DAPThread.id = JBThread.getId().toInt()
return DAPThread
}
fun breakpointJBFromDAP(DAPBreakpoint: Breakpoint): LLBreakpoint {
val source = DAPBreakpoint.source
var sourcePath = if (source == null) "" else Objects.requireNonNullElseGet(
source.path
) { Objects.requireNonNullElse(source.origin, "unknown") }
sourcePath = toJBPath(sourcePath)
return LLBreakpoint(
DAPBreakpoint.id,
sourcePath,
Objects.requireNonNullElse<Int>(DAPBreakpoint.line, 0) - 1,
null
)
}
fun getLocation(DAPBreakpoint: Breakpoint): LLBreakpointLocation? {
val ref = DAPBreakpoint.instructionReference ?: return null
val addr = ref.substring(2).toLong(16)
var fl: FileLocation? = null
val src = DAPBreakpoint.source
if (src != null) {
fl = FileLocation(src.path, DAPBreakpoint.line)
}
return LLBreakpointLocation(DAPBreakpoint.id.toString() + "", Address.fromUnsignedLong(addr), fl)
}
fun breakpointDAPFromJB(JBBreakpoint: LLBreakpoint): Breakpoint {
val DAPBreakpoint = Breakpoint()
DAPBreakpoint.id = JBBreakpoint.getId()
DAPBreakpoint.line = JBBreakpoint.getOrigLine() + 1
val source = Source()
source.path = JBBreakpoint.getOrigFile()
DAPBreakpoint.source = source
DAPBreakpoint.message = JBBreakpoint.getCondition()
return DAPBreakpoint
}
fun moduleJBFromDAP(DAPModule: Module): LLModule {
return LLModule(toJBPath(DAPModule.path))
}
fun moduleDAPFromJB(JBModule: LLModule): Module {
val DAPModule = Module()
DAPModule.path = toJBPath(JBModule.path)
DAPModule.name = JBModule.name
return DAPModule
}
fun frameJBFromDAP(
DAPFrame: StackFrame,
helperBreakpoint: DAPDriver.MappedBreakpoint?,
modules: Map<Int, DAPDriver.MappedModule>
): LLFrame {
val ptr = parseAddress(DAPFrame.instructionPointerReference)
val name = DAPFrame.name
val inline = name.startsWith("[Inline Frame] ")
val function = name.substring(name.indexOf('!') + 1, name.indexOf('('))
val moduleID = DAPFrame.moduleId
var moduleName: String? = null
if (moduleID != null) {
if (moduleID.isRight) {
moduleName = moduleID.right
} else {
val module = modules[moduleID.left]!!
moduleName = module.java.name
}
}
var line = DAPFrame.line
var sourcePath: String?
run {
val src = DAPFrame.source
sourcePath = if (src == null) null else toJBPath(src.path)
}
if (helperBreakpoint != null) {
if (line == 0) {
line = helperBreakpoint.dap.line
}
if (sourcePath == null) {
val src = helperBreakpoint.dap.source
if (src != null) {
sourcePath = toJBPath(src.path)
}
}
}
return LLFrame(
DAPFrame.id,
function,
sourcePath,
null,
line - 1,
ptr,
ZigDebuggerLanguage,
false,
inline,
moduleName
)
}
fun toSource(path: String): Source {
val src = Source()
val absolute = Path.of(path).toAbsolutePath()
src.name = absolute.fileName.toString()
src.path = toWinPath(absolute.toString())
return src
}
fun toWinPath(path: String): String {
return path.replace('/', '\\')
}
fun toJBPath(path: String): String {
return path.replace('\\', '/')
}
fun parseAddressNullable(address: String?): Long? {
if (address == null) return null
return parseAddress(address)
}
fun parseAddress(address: String?): Long {
if (address == null) return 0L
if (!address.startsWith("0x")) return java.lang.Long.parseUnsignedLong(address)
return java.lang.Long.parseUnsignedLong(address.substring(2), 16)
}
fun stringifyAddress(address: Long): String {
return "0x" + java.lang.Long.toHexString(address)
}
private val HEX_FIX_REGEX: Pattern = Pattern.compile("([0-9A-F]+)(?<!\\W)h")
suspend fun instructionJBFromDAP(
DAPInstruction: DisassembledInstruction,
loc: Source?,
startLineIn: Int?,
endLineIn: Int?,
uniq: Boolean,
symbol: LLSymbolOffset?
): LLInstruction {
var startLine = startLineIn
var endLine = endLineIn
val address: Address = Address.parseHexString(DAPInstruction.address)
val byteStrings =
DAPInstruction.instructionBytes.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val bytes = ArrayList<Byte>(byteStrings.size)
for (byteString in byteStrings) {
bytes.add(byteString.toInt(16).toByte())
}
var comment: String? = null
if (loc != null && startLine != null && endLine != null && uniq) run {
val pathStr = toJBPath(loc.path)
val path: Path
try {
path = Path.of(pathStr)
} catch (ignored: InvalidPathException) {
return@run
}
val text = readAction {
val file = VfsUtil.findFile(path, true) ?: return@readAction null
val doc: Document = file.findDocument() ?: return@readAction null
doc.immutableCharSequence.toString().split("(\r\n|\r|\n)".toRegex()).dropLastWhile { it.isEmpty() }
.toTypedArray()
}
if (text == null) return@run
startLine -= 1
endLine -= 1
if (text.size <= endLine) return@run
comment = text[endLine]
}
val nicerDisassembly = StringBuilder()
val disassembly = DAPInstruction.instruction
val matcher = HEX_FIX_REGEX.matcher(disassembly)
var prevEnd = 0
while (matcher.find()) {
nicerDisassembly.append(disassembly, prevEnd, matcher.start())
val hex = matcher.group(1).lowercase(Locale.getDefault())
nicerDisassembly.append("0x").append(hex)
prevEnd = matcher.end()
}
if (prevEnd < disassembly.length) nicerDisassembly.append(disassembly, prevEnd, disassembly.length)
return LLInstruction.create(
address,
bytes,
nicerDisassembly.toString(),
comment,
symbol
)
}
fun memoryJBFromDAP(DAPMemory: ReadMemoryResponse): LLMemoryHunk {
val address = parseAddress(DAPMemory.address)
val bytes = Base64.getDecoder().decode(DAPMemory.data)
val range = AddressRange(
Address.fromUnsignedLong(address),
Address.fromUnsignedLong(address + bytes.size - 1)
)
return LLMemoryHunk(range, bytes)
}
@Throws(com.intellij.execution.ExecutionException::class)
fun <T> get(future: CompletableFuture<T>): T {
try {
return future[4, TimeUnit.SECONDS]
} catch (e: InterruptedException) {
throw com.intellij.execution.ExecutionException(e)
} catch (e: TimeoutException) {
throw com.intellij.execution.ExecutionException(e)
} catch (e: ExecutionException) {
throw com.intellij.execution.ExecutionException(e.cause)
}
}
}

View file

@ -0,0 +1,52 @@
/*
* 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.debugger.execution.binary
import com.falsepattern.zigbrains.Icons
import com.falsepattern.zigbrains.debugger.ZigDebugBundle
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.ConfigurationTypeBase
import com.intellij.execution.configurations.RunConfiguration
import com.intellij.openapi.project.Project
class ZigConfigTypeBinary: ConfigurationTypeBase(
IDENTIFIER,
ZigDebugBundle.message("configuration.binary.name"),
ZigDebugBundle.message("configuration.binary.description"),
Icons.Zig
) {
init {
addFactory(ConfigFactoryBinary(this))
}
class ConfigFactoryBinary(type: ZigConfigTypeBinary): ConfigurationFactory(type) {
override fun createTemplateConfiguration(project: Project): RunConfiguration {
return ZigExecConfigBinary(project, this)
}
override fun getId(): String {
return IDENTIFIER
}
}
}
private const val IDENTIFIER = "ZIGBRAINS_BINARY"

View file

@ -0,0 +1,59 @@
/*
* 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.debugger.execution.binary
import com.falsepattern.zigbrains.debugger.ZigDebugBundle
import com.falsepattern.zigbrains.project.execution.base.*
import com.intellij.execution.Executor
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.openapi.project.Project
class ZigExecConfigBinary(project: Project, factory: ConfigurationFactory) : ZigExecConfig<ZigExecConfigBinary>(project, factory, ZigDebugBundle.message("exec.type.binary.label")) {
var exePath = FilePathConfigurable("exePath", ZigDebugBundle.message("exec.option.label.binary.exe-path"))
private set
var args = ArgsConfigurable("args", ZigDebugBundle.message("exec.option.label.binary.args"))
private set
override val suggestedName: String
get() = ZigDebugBundle.message("configuration.binary.suggested-name")
override suspend fun buildCommandLineArgs(debug: Boolean): List<String> {
return args.argsSplit()
}
override fun getConfigurables(): List<ZigConfigurable<*>> {
return super.getConfigurables() + listOf(exePath, args)
}
override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState<ZigExecConfigBinary> {
return ZigProfileStateBinary(environment, this)
}
override fun clone(): ZigExecConfigBinary {
val clone = super.clone()
clone.exePath = exePath.clone()
clone.args = args.clone()
return clone
}
}

View file

@ -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.debugger.execution.binary
import com.falsepattern.zigbrains.debugger.ZigDebugBundle
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
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: ZigToolchain, debug: Boolean): GeneralCommandLine {
val cli = GeneralCommandLine()
val cfg = configuration
cfg.workingDirectory.path?.let { cli.withWorkingDirectory(it) }
cli.withExePath(cfg.exePath.path?.pathString ?: throw ExecutionException(ZigDebugBundle.message("exception.missing-exe-path")))
cli.withCharset(Charsets.UTF_8)
cli.addParameters(cfg.args.args)
return cli
}
}

View file

@ -0,0 +1,30 @@
/*
* 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.debugger.runner.base
import com.intellij.execution.ExecutionException
interface PreLaunchAware {
@Throws(ExecutionException::class)
suspend fun preLaunch(listener: PreLaunchProcessListener)
}

View file

@ -0,0 +1,77 @@
/*
* 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.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
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
import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withContext
class PreLaunchProcessListener(val console: ConsoleView) : ProcessListener {
var isBuildFailed: Boolean = false
private set
lateinit var processHandler: ZigProcessHandler.IPCAware
private set
@Throws(ExecutionException::class)
suspend fun executeCommandLineWithHook(project: Project, commandLine: GeneralCommandLine): Boolean {
return withProgressText(commandLine.commandLineString) {
val processHandler = commandLine.startIPCAwareProcess(project)
this@PreLaunchProcessListener.processHandler = processHandler
hook(processHandler)
processHandler.startNotify()
withContext(Dispatchers.IO) {
processHandler.process.awaitExit()
}
runInterruptible {
processHandler.waitFor()
}
return@withProgressText isBuildFailed
}
}
fun hook(handler: ProcessHandler) {
console.attachToProcess(handler)
handler.addProcessListener(this)
}
override fun processTerminated(event: ProcessEvent) {
if (event.exitCode != 0) {
isBuildFailed = true
} else {
isBuildFailed = false
console.print("Build Successful. Starting debug session. \n", ConsoleViewContentType.NORMAL_OUTPUT)
}
}
}

View file

@ -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.debugger.runner.base
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
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
import java.io.File
class ZigDebugEmitBinaryInstaller<ProfileState: ZigProfileState<*>>(
private val profileState: ProfileState,
private val toolchain: ZigToolchain,
private val executableFile: File,
private val exeArgs: List<String>
): Installer {
override fun install(): GeneralCommandLine {
val cfg = profileState.configuration
val cli = PtyCommandLine().withConsoleMode(false).withExePath(executableFile.absolutePath)
cfg.workingDirectory.path?.let { x -> cli.withWorkingDirectory(x) }
cli.addParameters(exeArgs)
cli.withCharset(Charsets.UTF_8)
cli.withRedirectErrorStream(true)
return profileState.configuration.project.zigCoroutineScope.async{
profileState.configuration.patchCommandLine(cli)
}.asCompletableFuture().join()
}
override fun getExecutableFile(): File {
return executableFile
}
}

View file

@ -0,0 +1,44 @@
/*
* 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.debugger.runner.base
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
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
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
abstract class ZigDebugParametersBase<ProfileState: ZigProfileState<*>>(
private val driverConfiguration: DebuggerDriverConfiguration,
protected val toolchain: ZigToolchain,
protected val profileState: ProfileState
): RunParameters() {
override fun getDebuggerDriverConfiguration(): DebuggerDriverConfiguration {
return driverConfiguration
}
override fun getArchitectureId(): String {
return ArchitectureType.forVmCpuArch(CpuArch.CURRENT).id
}
}

View file

@ -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.debugger.runner.base
import com.falsepattern.zigbrains.debugger.ZigDebugBundle
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
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
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: ZigToolchain,
profileState: ProfileState,
) : ZigDebugParametersBase<ProfileState>(driverConfiguration, toolchain, profileState), PreLaunchAware {
@Volatile
protected lateinit var executableFile: File
private set
@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))
return withContext(Dispatchers.IO) {
Files.list(tmpDir).use { stream ->
stream.filter { !it.fileName.endsWith(".o") }
.filter { it.isExecutable() }
.findFirst()
.map { it.toFile() }
.orNull()
}
} ?: throw ExecutionException(ZigDebugBundle.message("debug.base.compile.failed.no-exe"))
}
@Throws(ExecutionException::class)
override suspend fun preLaunch(listener: PreLaunchProcessListener) {
this.executableFile = withProgressText("Compiling executable") {
compileExe(listener)
}
}
}

View file

@ -0,0 +1,135 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2025 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.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
import com.falsepattern.zigbrains.shared.coroutine.runInterruptibleEDT
import com.falsepattern.zigbrains.shared.coroutine.withEDTContext
import com.intellij.execution.DefaultExecutionResult
import com.intellij.execution.ExecutionException
import com.intellij.execution.executors.DefaultDebugExecutor
import com.intellij.execution.filters.Filter
import com.intellij.execution.filters.TextConsoleBuilder
import com.intellij.execution.process.ProcessTerminatedListener
import com.intellij.execution.runners.ExecutionEnvironment
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
import com.intellij.xdebugger.XDebugSession
import com.intellij.xdebugger.XDebuggerManager
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
abstract class ZigDebugRunnerBase<ProfileState : ZigProfileState<*>> : ZigProgramRunner<ProfileState>(DefaultDebugExecutor.EXECUTOR_ID) {
@Throws(ExecutionException::class)
override suspend fun execute(
state: ProfileState,
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 = true, DebuggerDriverConfiguration::class.java) ?: continue
return executeWithDriver(state, toolchain, environment, driver) ?: continue
}
return null
}
@Throws(ExecutionException::class)
private suspend fun executeWithDriver(
state: ProfileState,
toolchain: ZigToolchain,
environment: ExecutionEnvironment,
debuggerDriver: DebuggerDriverConfiguration
): RunContentDescriptor? {
return reportProgress { reporter ->
val runParameters = getDebugParameters(state, debuggerDriver, toolchain)
val console = state.consoleBuilder.console
if (runParameters is PreLaunchAware) {
val listener = PreLaunchProcessListener(console)
try {
reporter.indeterminateStep {
runParameters.preLaunch(listener)
}
} 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)
}
}
if (listener.isBuildFailed) {
val executionResult = DefaultExecutionResult(console, listener.processHandler.unwrap())
return@reportProgress withEDTContext(ModalityState.any()) {
val runContentBuilder = RunContentBuilder(executionResult, environment)
runContentBuilder.showRunContent(null)
}
}
}
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 = state.consoleBuilder
val debugProcess = ZigLocalDebugProcess(runParameters, session, textConsoleBuilder)
ProcessTerminatedListener.attach(debugProcess.processHandler, project)
debugProcess.start()
return debugProcess
}
}).runContentDescriptor
}
}
}
@Throws(ExecutionException::class)
protected abstract fun getDebugParameters(
state: ProfileState,
debuggerDriver: DebuggerDriverConfiguration,
toolchain: ZigToolchain
): ZigDebugParametersBase<ProfileState>
private class SharedConsoleBuilder(private val console: ConsoleView) : TextConsoleBuilder() {
override fun getConsole(): ConsoleView {
return console
}
override fun addFilter(filter: Filter) {
}
override fun setViewer(isViewer: Boolean) {
}
}
}

View file

@ -0,0 +1,41 @@
/*
* 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.debugger.runner.binary
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.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: 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 {
return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, profileState.configuration.args.argsSplit())
}
}

View file

@ -0,0 +1,55 @@
/*
* 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.debugger.runner.binary
import com.falsepattern.zigbrains.debugger.execution.binary.ZigExecConfigBinary
import com.falsepattern.zigbrains.debugger.execution.binary.ZigProfileStateBinary
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.base.ZigToolchain
import com.falsepattern.zigbrains.project.toolchain.local.LocalZigToolchain
import com.intellij.execution.configurations.RunProfile
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
class ZigDebugRunnerBinary: ZigDebugRunnerBase<ZigProfileStateBinary>() {
override fun getDebugParameters(
state: ZigProfileStateBinary,
debuggerDriver: DebuggerDriverConfiguration,
toolchain: ZigToolchain
): ZigDebugParametersBase<ZigProfileStateBinary> {
return ZigDebugParametersBinary(debuggerDriver, LocalZigToolchain.ensureLocal(toolchain), state)
}
override fun castProfileState(state: ZigProfileState<*>): ZigProfileStateBinary? {
return state as? ZigProfileStateBinary
}
override fun canRun(executorId: String, profile: RunProfile): Boolean {
return this.executorId == executorId && profile is ZigExecConfigBinary
}
override fun getRunnerId(): String {
return "ZigDebugRunnerBinary"
}
}

View file

@ -0,0 +1,115 @@
/*
* 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.debugger.runner.build
import com.falsepattern.zigbrains.debugger.ZigDebugBundle
import com.falsepattern.zigbrains.debugger.runner.base.PreLaunchAware
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.base.ZigToolchain
import com.intellij.execution.ExecutionException
import com.intellij.openapi.util.SystemInfo
import com.intellij.platform.util.progress.withProgressText
import com.jetbrains.cidr.execution.Installer
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.PropertyKey
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.util.stream.Stream
import kotlin.io.path.isExecutable
import kotlin.io.path.isRegularFile
class ZigDebugParametersBuild(
driverConfiguration: DebuggerDriverConfiguration,
toolchain: ZigToolchain,
profileState: ZigProfileStateBuild
) : ZigDebugParametersBase<ZigProfileStateBuild>(driverConfiguration, toolchain, profileState), PreLaunchAware {
@Volatile
private lateinit var executableFile: File
override fun getInstaller(): Installer {
return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, profileState.configuration.exeArgs.argsSplit())
}
@Throws(ExecutionException::class)
override suspend fun preLaunch(listener: PreLaunchProcessListener) {
withProgressText("Building zig project") {
withContext(Dispatchers.IO) {
val commandLine = profileState.getCommandLine(toolchain, true)
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 {
//Attempt autodetect, should work for trivial cases, and make most users happy, while advanced
// users can use manual executable paths.
if (workingDir == null) {
fail("debug.build.compile.failed.no-workdir")
}
val expectedOutputDir = workingDir.resolve(Path.of("zig-out", "bin"))
if (!expectedOutputDir.toFile().exists()) {
fail("debug.build.compile.failed.autodetect")
}
withContext(Dispatchers.IO) {
Files.list(expectedOutputDir).use { getExe(it) }
}
}
if (!exe.toFile().exists())
fail("debug.build.compile.failed.no-file", exe)
else if (!exe.isExecutable())
fail("debug.build.compile.failed.non-exec-file", exe)
executableFile = exe.toFile()
}
}
}
}
@Throws(ExecutionException::class)
private fun getExe(files: Stream<Path>): Path {
var fileStream = files.filter { it.isRegularFile() }
if (SystemInfo.isWindows) {
fileStream = fileStream.filter { it.fileName.endsWith(".exe") }
} else {
fileStream = fileStream.filter { it.isExecutable() }
}
val executables = fileStream.toList()
return when(executables.size) {
0 -> fail("debug.base.compile.failed.no-exe")
1 -> executables[0]
else -> fail("debug.build.compile.failed.multiple-exe")
}
}
@Throws(ExecutionException::class)
private fun fail(@PropertyKey(resourceBundle = ZigDebugBundle.BUNDLE) messageKey: String, vararg params: Any): Nothing {
throw ExecutionException(ZigDebugBundle.message("debug.build.compile.failed.boilerplate", ZigDebugBundle.message(messageKey, params)))
}

View file

@ -0,0 +1,55 @@
/*
* 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.debugger.runner.build
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.execution.build.ZigExecConfigBuild
import com.falsepattern.zigbrains.project.execution.build.ZigProfileStateBuild
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
class ZigDebugRunnerBuild: ZigDebugRunnerBase<ZigProfileStateBuild>() {
override fun getDebugParameters(
state: ZigProfileStateBuild,
debuggerDriver: DebuggerDriverConfiguration,
toolchain: ZigToolchain
): ZigDebugParametersBase<ZigProfileStateBuild> {
return ZigDebugParametersBuild(debuggerDriver, LocalZigToolchain.ensureLocal(toolchain), state)
}
override fun castProfileState(state: ZigProfileState<*>): ZigProfileStateBuild? {
return state as? ZigProfileStateBuild
}
override fun canRun(executorId: String, profile: RunProfile): Boolean {
return this.executorId == executorId && profile is ZigExecConfigBuild
}
override fun getRunnerId(): String {
return "ZigDebugRunnerBuild"
}
}

View file

@ -0,0 +1,37 @@
/*
* 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.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.base.ZigToolchain
import com.jetbrains.cidr.execution.Installer
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
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())
}
}

View file

@ -0,0 +1,55 @@
/*
* 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.debugger.runner.run
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.execution.run.ZigExecConfigRun
import com.falsepattern.zigbrains.project.execution.run.ZigProfileStateRun
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
class ZigDebugRunnerRun: ZigDebugRunnerBase<ZigProfileStateRun>() {
override fun getDebugParameters(
state: ZigProfileStateRun,
debuggerDriver: DebuggerDriverConfiguration,
toolchain: ZigToolchain
): ZigDebugParametersBase<ZigProfileStateRun> {
return ZigDebugParametersRun(debuggerDriver, LocalZigToolchain.ensureLocal(toolchain), state)
}
override fun castProfileState(state: ZigProfileState<*>): ZigProfileStateRun? {
return state as? ZigProfileStateRun
}
override fun canRun(executorId: String, profile: RunProfile): Boolean {
return this.executorId == executorId && (profile is ZigExecConfigRun)
}
override fun getRunnerId(): String {
return "ZigDebugRunnerRun"
}
}

View file

@ -0,0 +1,37 @@
/*
* 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.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.base.ZigToolchain
import com.jetbrains.cidr.execution.Installer
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriverConfiguration
class ZigDebugParametersTest(driverConfiguration: DebuggerDriverConfiguration, toolchain: ZigToolchain, profileState: ZigProfileStateTest) :
ZigDebugParametersEmitBinaryBase<ZigProfileStateTest>(driverConfiguration, toolchain, profileState) {
override fun getInstaller(): Installer {
return ZigDebugEmitBinaryInstaller(profileState, toolchain, executableFile, listOf())
}
}

View file

@ -0,0 +1,55 @@
/*
* 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.debugger.runner.test
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.execution.test.ZigExecConfigTest
import com.falsepattern.zigbrains.project.execution.test.ZigProfileStateTest
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
class ZigDebugRunnerTest: ZigDebugRunnerBase<ZigProfileStateTest>() {
override fun getDebugParameters(
state: ZigProfileStateTest,
debuggerDriver: DebuggerDriverConfiguration,
toolchain: ZigToolchain
): ZigDebugParametersBase<ZigProfileStateTest> {
return ZigDebugParametersTest(debuggerDriver, LocalZigToolchain.ensureLocal(toolchain), state)
}
override fun castProfileState(state: ZigProfileState<*>): ZigProfileStateTest? {
return state as? ZigProfileStateTest
}
override fun canRun(executorId: String, profile: RunProfile): Boolean {
return this.executorId == executorId && profile is ZigExecConfigTest
}
override fun getRunnerId(): String {
return "ZigDebugRunnerTest"
}
}

View file

@ -0,0 +1,29 @@
/*
* 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.debugger.settings
enum class MSVCDownloadPermission {
AskMe,
Allow,
Deny
}

View file

@ -0,0 +1,54 @@
/*
* 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.debugger.settings
import com.intellij.openapi.Disposable
import com.intellij.openapi.options.ConfigurableUi
import com.intellij.openapi.util.Disposer
import com.intellij.ui.dsl.builder.panel
import javax.swing.JComponent
class ZigDebuggerGeneralSettingsConfigurableUi: ConfigurableUi<ZigDebuggerSettings>, Disposable {
private val components = listOf<ZigDebuggerUiComponent>(ZigDebuggerToolchainConfigurableUi()).onEach { Disposer.register(this, it) }
override fun reset(settings: ZigDebuggerSettings) {
components.forEach { it.reset(settings) }
}
override fun isModified(settings: ZigDebuggerSettings): Boolean {
return components.any { it.isModified(settings) }
}
override fun apply(settings: ZigDebuggerSettings) {
components.forEach { it.apply(settings) }
}
override fun getComponent(): JComponent {
return panel {
components.forEach { it.buildUi(this) }
}
}
override fun dispose() {
}
}

View file

@ -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.debugger.settings
import com.falsepattern.zigbrains.debugger.ZigDebugBundle
import com.falsepattern.zigbrains.debugger.toolchain.DebuggerKind
import com.intellij.openapi.options.Configurable
import com.intellij.openapi.options.SimpleConfigurable
import com.intellij.util.xmlb.XmlSerializerUtil
import com.intellij.xdebugger.settings.DebuggerSettingsCategory
import com.intellij.xdebugger.settings.XDebuggerSettings
import java.util.function.Supplier
class ZigDebuggerSettings: XDebuggerSettings<ZigDebuggerSettings>("Zig") {
var debuggerKind = DebuggerKind.default
var downloadAutomatically = false
var useClion = true
var msvcConsent = MSVCDownloadPermission.AskMe
override fun getState(): ZigDebuggerSettings {
return this
}
override fun loadState(p0: ZigDebuggerSettings) {
XmlSerializerUtil.copyBean(p0, this)
}
override fun createConfigurables(category: DebuggerSettingsCategory): Collection<Configurable> {
val configurable = when(category) {
DebuggerSettingsCategory.GENERAL -> createGeneralSettingsConfigurable()
else -> null
}
return configurable?.let { listOf(configurable) } ?: emptyList()
}
private fun createGeneralSettingsConfigurable(): Configurable {
return SimpleConfigurable.create(
GENERAL_SETTINGS_ID,
ZigDebugBundle.message("settings.debugger.title"),
ZigDebuggerGeneralSettingsConfigurableUi::class.java,
Supplier {
instance
}
)
}
companion object {
val instance: ZigDebuggerSettings get() = getInstance(ZigDebuggerSettings::class.java)
}
}
private const val GENERAL_SETTINGS_ID = "Debugger.Zig.General"

View file

@ -0,0 +1,154 @@
/*
* 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.debugger.settings
import com.falsepattern.zigbrains.debugger.ZigDebugBundle
import com.falsepattern.zigbrains.debugger.toolchain.DebuggerAvailability
import com.falsepattern.zigbrains.debugger.toolchain.DebuggerKind
import com.falsepattern.zigbrains.debugger.toolchain.ZigDebuggerToolchainService
import com.falsepattern.zigbrains.debugger.toolchain.zigDebuggerToolchainService
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
import com.intellij.openapi.util.SystemInfo
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.components.JBCheckBox
import com.intellij.ui.dsl.builder.DEFAULT_COMMENT_WIDTH
import com.intellij.ui.dsl.builder.Panel
import com.intellij.util.concurrency.annotations.RequiresEdt
import javax.swing.ComboBoxModel
import javax.swing.DefaultComboBoxModel
import javax.swing.JEditorPane
class ZigDebuggerToolchainConfigurableUi : ZigDebuggerUiComponent {
private val debuggerKindComboBox = ComboBox(
runModalOrBlocking({ ModalTaskOwner.guess() }, { "ZigDebuggerToolchainConfigurableUi" }) {
createDebuggerKindComboBoxModel()
}
)
private val downloadAutomaticallyCheckBox = JBCheckBox(
ZigDebugBundle.message("settings.debugger.toolchain.download.debugger.automatically.checkbox"),
ZigDebuggerSettings.instance.downloadAutomatically
)
private val useClion = JBCheckBox(
ZigDebugBundle.message("settings.debugger.toolchain.use.clion.toolchains"),
ZigDebuggerSettings.instance.useClion
)
private var comment: JEditorPane? = null
private val currentDebuggerKind get() = debuggerKindComboBox.item
override fun reset(settings: ZigDebuggerSettings) {
debuggerKindComboBox.item = settings.debuggerKind
downloadAutomaticallyCheckBox.isSelected = settings.downloadAutomatically
useClion.isSelected = settings.useClion
}
override fun isModified(settings: ZigDebuggerSettings): Boolean {
return settings.debuggerKind != debuggerKindComboBox.item ||
settings.downloadAutomatically != downloadAutomaticallyCheckBox.isSelected ||
settings.useClion != useClion.isSelected
}
override fun apply(settings: ZigDebuggerSettings) {
settings.debuggerKind = debuggerKindComboBox.item
settings.downloadAutomatically = downloadAutomaticallyCheckBox.isSelected
settings.useClion = useClion.isSelected
}
override fun buildUi(panel: Panel): Unit = with(panel) {
row(ZigDebugBundle.message("settings.debugger.toolchain.debugger.label")) {
comment = cell(debuggerKindComboBox)
.comment("", DEFAULT_COMMENT_WIDTH) {
zigCoroutineScope.launchWithEDT(ModalityState.defaultModalityState()) {
withModalProgress(ModalTaskOwner.component(debuggerKindComboBox), "Downloading debugger", TaskCancellation.cancellable()) {
downloadDebugger()
}
}
}
.applyToComponent {
whenItemSelected(null) {
zigCoroutineScope.launchWithEDT(ModalityState.defaultModalityState()) {
this@ZigDebuggerToolchainConfigurableUi.update()
}
}
}
.comment
}
row {
cell(downloadAutomaticallyCheckBox)
}
if (PluginManager.isPluginInstalled(PluginId.getId("com.intellij.modules.clion")) && !SystemInfo.isWindows) {
row {
cell(useClion)
}
}
zigCoroutineScope.launchWithEDT(ModalityState.defaultModalityState()) {
update()
}
}
override fun dispose() {
}
@RequiresEdt
private suspend fun downloadDebugger() {
val result = zigDebuggerToolchainService.downloadDebugger(null, currentDebuggerKind)
if (result is ZigDebuggerToolchainService.DownloadResult.Ok) {
update()
}
}
@RequiresEdt
private suspend fun update() {
val availability = zigDebuggerToolchainService.debuggerAvailability(currentDebuggerKind)
val text = when (availability) {
is DebuggerAvailability.NeedToDownload -> ZigDebugBundle.message("settings.debugger.toolchain.download.comment")
is DebuggerAvailability.NeedToUpdate -> ZigDebugBundle.message("settings.debugger.toolchain.update.comment")
else -> null
}
comment?.let {
it.text = text
it.isVisible = text != null
}
}
companion object {
private suspend fun createDebuggerKindComboBoxModel(): ComboBoxModel<DebuggerKind> {
val toolchainService = zigDebuggerToolchainService
val availableKinds = DebuggerKind.entries.filter { toolchainService.debuggerAvailability(it) !is DebuggerAvailability.Unavailable }
return DefaultComboBoxModel(availableKinds.toTypedArray()).also { it.selectedItem = ZigDebuggerSettings.instance.debuggerKind }
}
}
}

View file

@ -0,0 +1,39 @@
/*
* 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.debugger.settings
import com.intellij.openapi.Disposable
import com.intellij.openapi.options.ConfigurableUi
import com.intellij.ui.dsl.builder.Panel
import com.intellij.ui.dsl.builder.panel
import javax.swing.JComponent
interface ZigDebuggerUiComponent: ConfigurableUi<ZigDebuggerSettings>, Disposable {
fun buildUi(panel: Panel)
override fun getComponent(): JComponent {
return panel {
buildUi(this)
}
}
}

View file

@ -0,0 +1,37 @@
/*
* 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.debugger.toolchain
import java.nio.file.Path
sealed class DebuggerAvailability<out T> {
data object Unavailable: DebuggerAvailability<Nothing>()
data object NeedToDownload: DebuggerAvailability<Nothing>()
data object NeedToUpdate: DebuggerAvailability<Nothing>()
data object Bundled: DebuggerAvailability<Nothing>()
data class Binaries<T> (val binaries: T): DebuggerAvailability<T>()
}
data class LLDBBinaries(val frameworkFile: Path, val frontendFile: Path)
data class GDBBinaries(val gdbFile: Path)
data class MSVCBinaries(val msvcFile: Path)

View file

@ -0,0 +1,35 @@
/*
* 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.debugger.toolchain
import com.intellij.openapi.util.SystemInfo
enum class DebuggerKind {
LLDB,
GDB,
MSVC;
companion object {
val default get() = if (SystemInfo.isWindows) MSVC else LLDB
}
}

View file

@ -0,0 +1,126 @@
/*
* 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.debugger.toolchain
import com.falsepattern.zigbrains.debugger.ZigDebugBundle
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.intellij.notification.Notification
import com.intellij.notification.NotificationType
import com.intellij.openapi.progress.coroutineToIndicator
import com.intellij.openapi.ui.DialogBuilder
import com.intellij.platform.util.progress.withProgressText
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBPanel
import com.intellij.util.download.DownloadableFileService
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withTimeoutOrNull
import java.io.IOException
import java.util.*
import javax.swing.BoxLayout
private val mutex = Mutex()
private var cache: Properties? = null
suspend fun msvcMetadata(): Properties {
cache?.let { return it }
mutex.withLock {
cache?.let { return it }
val settings = ZigDebuggerSettings.instance
var permission = settings.msvcConsent
if (permission == MSVCDownloadPermission.AskMe) {
val allowDownload = withCurrentEDTModalityContext {
val dialog = DialogBuilder()
dialog.setTitle(ZigDebugBundle.message("msvc.consent.title"))
dialog.addCancelAction().setText(ZigDebugBundle.message("msvc.consent.deny"))
dialog.addOkAction().setText(ZigDebugBundle.message("msvc.consent.allow"))
val centerPanel = JBPanel<JBPanel<*>>()
centerPanel.setLayout(BoxLayout(centerPanel, BoxLayout.Y_AXIS))
val lines = ZigDebugBundle.message("msvc.consent.body").split('\n')
for (line in lines) {
centerPanel.add(JBLabel(line))
}
dialog.centerPanel(centerPanel)
dialog.showAndGet()
}
permission = if (allowDownload) MSVCDownloadPermission.Allow else MSVCDownloadPermission.Deny
settings.msvcConsent = permission
}
val data = if (permission == MSVCDownloadPermission.Allow) {
withTimeoutOrNull(3000L) {
downloadMSVCProps()
} ?: run {
Notification(
"zigbrains",
ZigDebugBundle.message("notification.title.debugger"),
ZigDebugBundle.message("notification.content.debugger.metadata.downloading.failed"),
NotificationType.ERROR
).notify(null)
fetchBuiltinMSVCProps()
}
} else {
fetchBuiltinMSVCProps()
}
cache = data
return data
}
}
private suspend fun downloadMSVCProps(): Properties {
return withProgressText("Downloading debugger metadata") {
val service = DownloadableFileService.getInstance()
val desc = service.createFileDescription("https://falsepattern.com/zigbrains/msvc.properties", "msvc.properties")
val downloader = service.createDownloader(listOf(desc), "Debugger metadata downloading")
val downloadDirectory = downloadPath().toFile()
val prop = Properties()
val downloadResults = coroutineToIndicator {
downloader.download(downloadDirectory)
}
for (result in downloadResults) {
if (result.second.defaultFileName == "msvc.properties") {
result.first.reader().use { prop.load(it) }
}
}
return@withProgressText prop
}
}
private fun fetchBuiltinMSVCProps(): Properties {
val prop = Properties()
try {
val resource = ZigDebuggerToolchainService::class.java.getResourceAsStream("/msvc.properties") ?: throw IOException("null")
resource.reader().use { prop.load(it) }
} catch (ex: IOException) {
ex.printStackTrace()
Notification(
"zigbrains",
ZigDebugBundle.message("notification.title.debugger"),
ZigDebugBundle.message("notification.content.debugger.metadata.fallback.parse.failed"),
NotificationType.ERROR
).notify(null)
}
return prop
}

View file

@ -0,0 +1,350 @@
/*
* 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.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
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.coroutineToIndicator
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.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.withContext
import java.io.IOException
import java.net.URL
import java.nio.file.Path
import java.util.*
import kotlin.io.path.name
@Service
class ZigDebuggerToolchainService {
suspend fun debuggerAvailability(kind: DebuggerKind): DebuggerAvailability<*> {
return when(kind) {
DebuggerKind.LLDB -> lldbAvailability()
DebuggerKind.GDB -> gdbAvailability()
DebuggerKind.MSVC -> msvcAvailability()
}
}
fun lldbAvailability(): DebuggerAvailability<LLDBBinaries> {
if (LLDBDriverConfiguration.hasBundledLLDB()) return DebuggerAvailability.Bundled
val (frameworkPath, frontendPath) = when {
SystemInfo.isMac -> "LLDB.framework" to "LLDBFrontend"
SystemInfo.isUnix -> "lib/liblldb.so" to "bin/LLDBFrontend"
// SystemInfo.isWindows -> "bin/liblldb.dll" to "bin/LLDBFrontend.exe"
else -> return DebuggerAvailability.Unavailable
}
val lldbPath = lldbPath()
val frameworkFile = lldbPath.resolve(frameworkPath)
val frontendFile = lldbPath.resolve(frontendPath)
if (!frameworkFile.toFile().exists() || !frontendFile.toFile().exists()) return DebuggerAvailability.NeedToDownload
val versions = loadDebuggerVersions(DebuggerKind.LLDB)
val (lldbFrameworkUrl, lldbFrontendUrl) = lldbUrls() ?: return DebuggerAvailability.Unavailable
val lldbFrameworkVersion = fileNameWithoutExtension(lldbFrameworkUrl.toString())
val lldbFrontendVersion = fileNameWithoutExtension(lldbFrontendUrl.toString())
if (versions[LLDB_FRAMEWORK_PROPERTY_NAME] != lldbFrameworkVersion ||
versions[LLDB_FRONTEND_PROPERTY_NAME] != lldbFrontendVersion) return DebuggerAvailability.NeedToUpdate
return DebuggerAvailability.Binaries(LLDBBinaries(frameworkFile, frontendFile))
}
fun gdbAvailability(): DebuggerAvailability<GDBBinaries> {
if (SystemInfo.isMac) return DebuggerAvailability.Unavailable
if (CidrDebuggerPathManager.getBundledGDBBinary().exists()) return DebuggerAvailability.Bundled
val gdbBinaryPath = when {
SystemInfo.isUnix -> "bin/gdb"
// SystemInfo.isWindows -> "bin/gdb.exe"
else -> return DebuggerAvailability.Unavailable
}
val gdbFile = gdbPath().resolve(gdbBinaryPath)
if (!gdbFile.toFile().exists()) return DebuggerAvailability.NeedToDownload
val versions = loadDebuggerVersions(DebuggerKind.GDB)
val gdbUrl = gdbUrl() ?: return DebuggerAvailability.Unavailable
val gdbVersion = fileNameWithoutExtension(gdbUrl.toString())
if (versions[GDB_PROPERTY_NAME] != gdbVersion) return DebuggerAvailability.NeedToUpdate
return DebuggerAvailability.Binaries(GDBBinaries(gdbFile))
}
suspend fun msvcAvailability(): DebuggerAvailability<MSVCBinaries> {
if (!SystemInfo.isWindows) return DebuggerAvailability.Unavailable
val msvcBinaryPath = "vsdbg.exe"
val msvcFile = msvcPath().resolve(msvcBinaryPath)
if (!msvcFile.toFile().exists()) return DebuggerAvailability.NeedToDownload
val msvcUrl = msvcUrl() ?: return DebuggerAvailability.Binaries(MSVCBinaries(msvcFile))
val versions = loadDebuggerVersions(DebuggerKind.MSVC)
if (versions[MSVC_PROPERTY_NAME] != msvcUrl.version) return DebuggerAvailability.NeedToUpdate
return DebuggerAvailability.Binaries(MSVCBinaries(msvcFile))
}
@RequiresEdt
private suspend fun doDownloadDebugger(debuggerKind: DebuggerKind): DownloadResult {
val baseDir = debuggerKind.basePath()
val downloadableBinaries = when(debuggerKind) {
DebuggerKind.LLDB -> {
val (lldbFrameworkUrl, lldbFrontendUrl) = lldbUrls()?.run { first.toString() to second.toString() } ?: return DownloadResult.NoUrls
listOf(
DownloadableDebuggerBinary(lldbFrameworkUrl, LLDB_FRAMEWORK_PROPERTY_NAME, fileNameWithoutExtension(lldbFrameworkUrl)),
DownloadableDebuggerBinary(lldbFrontendUrl, LLDB_FRONTEND_PROPERTY_NAME, fileNameWithoutExtension(lldbFrontendUrl))
)
}
DebuggerKind.GDB -> {
val gdbUrl = gdbUrl()?.run { toString() } ?: return DownloadResult.NoUrls
listOf(DownloadableDebuggerBinary(gdbUrl, GDB_PROPERTY_NAME, fileNameWithoutExtension(gdbUrl)))
}
DebuggerKind.MSVC -> {
val msvcUrl = msvcUrl() ?: return DownloadResult.NoUrls
val dialog = DialogBuilder()
dialog.setTitle(msvcUrl.dialogTitle)
dialog.addCancelAction().setText("Reject")
dialog.addOkAction().setText("Accept")
val centerPanel = JBPanel<JBPanel<*>>()
val hyperlink = HyperlinkLabel()
hyperlink.setTextWithHyperlink(msvcUrl.dialogBody)
hyperlink.setHyperlinkTarget(msvcUrl.dialogLink)
hyperlink.addHyperlinkListener(BrowserHyperlinkListener())
centerPanel.add(hyperlink)
dialog.centerPanel(centerPanel)
if (!dialog.showAndGet()) return DownloadResult.NoUrls
listOf(DownloadableDebuggerBinary(msvcUrl.url, MSVC_PROPERTY_NAME, msvcUrl.version, "extension/debugAdapters/vsdbg/bin"))
}
}
try {
withContext(Dispatchers.IO) {
downloadAndUnArchive(baseDir, downloadableBinaries)
}
return DownloadResult.Ok(baseDir)
} catch (e: IOException) {
//TODO logging
e.printStackTrace()
return DownloadResult.Failed(e.message)
}
}
@RequiresEdt
suspend fun downloadDebugger(project: Project? = null, debuggerKind: DebuggerKind): DownloadResult {
val result = doDownloadDebugger(debuggerKind)
when(result) {
is DownloadResult.Ok -> {
Notification(
"zigbrains",
ZigDebugBundle.message("notification.title.debugger"),
ZigDebugBundle.message("notification.content.debugger.successfully.downloaded"),
NotificationType.INFORMATION
).notify(project)
}
is DownloadResult.Failed -> {
Notification(
"zigbrains",
ZigDebugBundle.message("notification.title.debugger"),
ZigDebugBundle.message("notification.content.debugger.downloading.failed"),
NotificationType.ERROR
).notify(project)
}
else -> Unit
}
return result
}
@Throws(IOException::class)
@RequiresEdt
private suspend fun downloadAndUnArchive(baseDir: Path, binariesToDownload: List<DownloadableDebuggerBinary>) {
reportSequentialProgress { reporter ->
val service = DownloadableFileService.getInstance()
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 = 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>? {
val lldb = UrlProvider.lldb(OS.CURRENT, CpuArch.CURRENT) ?: return null
val lldbFrontend = UrlProvider.lldbFrontend(OS.CURRENT, CpuArch.CURRENT) ?: return null
return lldb to lldbFrontend
}
private fun gdbUrl(): URL? = UrlProvider.gdb(OS.CURRENT, CpuArch.CURRENT)
private suspend fun msvcUrl(): MSVCUrl? {
val dlKey = when(CpuArch.CURRENT) {
CpuArch.X86 -> "downloadX86"
CpuArch.X86_64 -> "downloadX86_64"
CpuArch.ARM64 -> "downloadARM64"
else -> return null
}
val props = msvcMetadata()
val version = props.getProperty("version") ?: return null
val url = props.getProperty(dlKey) ?: return null
return MSVCUrl(url, version, props.getProperty("dialogTitle")!!, props.getProperty("dialogBody")!!, props.getProperty("dialogLink")!!)
}
private data class MSVCUrl(
val url: String,
val version: String,
val dialogTitle: String,
val dialogBody: String,
val dialogLink: String
)
private fun loadDebuggerVersions(kind: DebuggerKind): Properties = loadVersions(kind.basePath())
private fun saveVersionsFile(basePath: Path, versions: Properties) {
val file = basePath.resolve(DEBUGGER_VERSIONS).toFile()
try {
versions.store(file.bufferedWriter(), "")
} catch (e: IOException) {
LOG.warn("Failed to save `${basePath.name}/${file.name}`", e)
}
}
private fun loadVersions(basePath: Path): Properties {
val versions = Properties()
val versionsFile = basePath.resolve(DEBUGGER_VERSIONS).toFile()
if (versionsFile.exists()) {
try {
versionsFile.bufferedReader().use { versions.load(it) }
} catch (e: IOException) {
LOG.warn("Failed to load `${basePath.name}/${versionsFile.name}`", e)
}
}
return versions
}
private fun DebuggerKind.basePath(): Path {
return when(this) {
DebuggerKind.LLDB -> lldbPath()
DebuggerKind.GDB -> gdbPath()
DebuggerKind.MSVC -> msvcPath()
}
}
companion object {
private val LOG = logger<ZigDebuggerToolchainService>()
private const val DEBUGGER_VERSIONS: String = "versions.properties"
private const val LLDB_FRONTEND_PROPERTY_NAME = "lldbFrontend"
private const val LLDB_FRAMEWORK_PROPERTY_NAME = "lldbFramework"
private const val GDB_PROPERTY_NAME = "gdb"
private const val MSVC_PROPERTY_NAME = "msvc"
fun downloadPath() = tempPluginDir
private fun lldbPath() = pluginDir.resolve("lldb")
private fun gdbPath() = pluginDir.resolve("gdb")
private fun msvcPath() = pluginDir.resolve("msvc")
private val pluginDir get() = PathManager.getSystemDir().resolve("zigbrains")
private val tempPluginDir get(): Path = PathManager.getTempPath().toNioPathOrNull()!!.resolve("zigbrains")
private fun fileNameWithoutExtension(url: String): String {
return url.substringAfterLast("/").removeSuffix(".zip").removeSuffix(".tar.gz")
}
private fun fileName(url: String): String {
return url.substringAfterLast("/")
}
}
sealed class DownloadResult {
class Ok(val baseDir: Path): DownloadResult()
data object NoUrls: DownloadResult()
class Failed(val message: String?): DownloadResult()
}
@JvmRecord
private data class DownloadableDebuggerBinary(val url: String, val propertyName: String, val version: String, val prefix: String? = null)
}
val zigDebuggerToolchainService get() = application.service<ZigDebuggerToolchainService>()

View file

@ -0,0 +1,53 @@
/*
* 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.debugger.win
import com.falsepattern.zigbrains.debugger.dap.DAPDebuggerDriverConfiguration
import com.intellij.execution.configurations.GeneralCommandLine
import com.jetbrains.cidr.ArchitectureType
import com.jetbrains.cidr.execution.debugger.backend.DebuggerDriver
import org.eclipse.lsp4j.debug.InitializeRequestArguments
import java.nio.file.Path
import kotlin.io.path.pathString
abstract class MSVCDriverConfiguration: DAPDebuggerDriverConfiguration() {
protected abstract val debuggerExecutable: Path
override fun createDriver(handler: DebuggerDriver.Handler, arch: ArchitectureType): DebuggerDriver {
return WinDAPDriver(handler).also { it.initialize(this) }
}
override fun createDriverCommandLine(driver: DebuggerDriver, arch: ArchitectureType): GeneralCommandLine {
val path = debuggerExecutable
val cli = GeneralCommandLine()
cli.withExePath(path.pathString)
cli.addParameters("--interpreter=vscode", "--extconfigdir=%USERPROFILE%\\.cppvsdbg\\extensions")
cli.withWorkingDirectory(path.parent)
return cli
}
override fun customizeInitializeArguments(initArgs: InitializeRequestArguments) {
initArgs.pathFormat = "path"
initArgs.adapterID = "cppvsdbg"
}
}

View file

@ -0,0 +1,129 @@
/*
* 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.debugger.win
import com.falsepattern.zigbrains.debugger.dap.DAPDriver
import com.falsepattern.zigbrains.shared.zigCoroutineScope
import com.intellij.util.system.CpuArch
import com.jetbrains.cidr.ArchitectureType
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.sync.Semaphore
import org.eclipse.lsp4j.debug.Capabilities
import org.eclipse.lsp4j.debug.OutputEventArguments
import org.eclipse.lsp4j.debug.services.IDebugProtocolServer
import org.eclipse.lsp4j.debug.util.ToStringBuilder
import org.eclipse.lsp4j.jsonrpc.MessageConsumer
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.security.MessageDigest
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.zip.Inflater
class WinDAPDriver(handler: Handler) : DAPDriver<IDebugProtocolServer, WinDAPDriver.WinDAPDebuggerClient>(handler) {
private val handshakeFinished = Semaphore(1, 1)
override fun createDebuggerClient(): WinDAPDebuggerClient {
return WinDAPDebuggerClient()
}
override fun getServerInterface(): Class<IDebugProtocolServer> {
return IDebugProtocolServer::class.java
}
override fun wrapMessageConsumer(mc: MessageConsumer): MessageConsumer {
return object: MessageConsumer {
private var verifyHandshake = true
override fun consume(message: Message) {
if (verifyHandshake && message is DebugResponseMessage && message.method == "handshake") {
verifyHandshake = false
message.setResponseId(1)
}
mc.consume(message)
}
}
}
override suspend fun postInitialize(capabilities: Capabilities) {
handshakeFinished.acquire()
}
inner class WinDAPDebuggerClient: DAPDriver<IDebugProtocolServer, WinDAPDebuggerClient>.DAPDebuggerClient() {
override fun output(args: OutputEventArguments) {
if ("telemetry" == args.category)
return
super.output(args)
}
@JsonRequest
fun handshake(handshake: HandshakeRequest): CompletableFuture<HandshakeResponse> {
return zigCoroutineScope.async(Dispatchers.IO) {
handshakeSuspend(handshake)
}.asCompletableFuture()
}
private fun handshakeSuspend(handshake: HandshakeRequest): HandshakeResponse {
val hasher = MessageDigest.getInstance("SHA-256")
hasher.update(handshake.value.encodeToByteArray())
val inflater = Inflater(true)
val coconut = DAPDebuggerClient::class.java.getResourceAsStream("/coconut.jpg")?.use(InputStream::readAllBytes) ?: throw RuntimeException("No coconut")
inflater.setInput(coconut, coconut.size - 80, 77)
inflater.finished()
val b = ByteArray(1)
while (inflater.inflate(b) > 0)
hasher.update(b)
val result = HandshakeResponse(String(coconut, coconut.size - 3, 3) + Base64.getEncoder().encodeToString(hasher.digest()))
handshakeFinished.release()
return result
}
}
override fun getArchitecture(): String {
return ArchitectureType.forVmCpuArch(CpuArch.CURRENT).id
}
data class HandshakeRequest(var value: String) {
constructor() : this("")
override fun toString(): String {
val b = ToStringBuilder(this)
b.add("value", value)
return b.toString()
}
}
data class HandshakeResponse(var signature: String) {
constructor() : this("")
override fun toString(): String {
val b = ToStringBuilder(this)
b.add("signature", this.signature)
return b.toString()
}
}
}

View file

@ -0,0 +1,8 @@
<idea-plugin package="com.falsepattern.zigbrains.cidr">
<depends>com.intellij.cidr.base</depends>
<extensions defaultExtensionNs="cidr.project">
<workspaceProvider
implementation="com.falsepattern.zigbrains.cidr.ZigWorkspaceProvider"
/>
</extensions>
</idea-plugin>

View file

@ -0,0 +1,10 @@
<idea-plugin package="com.falsepattern.zigbrains.clion">
<depends>com.intellij.clion</depends>
<extensions defaultExtensionNs="com.falsepattern.zigbrains">
<debuggerDriverProvider
id="CLionProvider"
implementation="com.falsepattern.zigbrains.clion.ZigClionDebuggerDriverConfigurationProvider"
order="before DefaultProvider"
/>
</extensions>
</idea-plugin>

View file

@ -0,0 +1,60 @@
<idea-plugin package="com.falsepattern.zigbrains.debugger">
<depends>com.intellij.modules.cidr.debugger</depends>
<extensions defaultExtensionNs="com.intellij">
<configurationType
implementation="com.falsepattern.zigbrains.debugger.execution.binary.ZigConfigTypeBinary"
/>
<programRunner
id="ZigDebugRunnerRun"
implementation="com.falsepattern.zigbrains.debugger.runner.run.ZigDebugRunnerRun"
/>
<programRunner
id="ZigDebugRunnerTest"
implementation="com.falsepattern.zigbrains.debugger.runner.test.ZigDebugRunnerTest"
/>
<programRunner
id="ZigDebugRunnerBuild"
implementation="com.falsepattern.zigbrains.debugger.runner.build.ZigDebugRunnerBuild"
/>
<programRunner
id="ZigDebugRunnerBinary"
implementation="com.falsepattern.zigbrains.debugger.runner.binary.ZigDebugRunnerBinary"
/>
<xdebugger.settings
implementation="com.falsepattern.zigbrains.debugger.settings.ZigDebuggerSettings"
/>
</extensions>
<extensions defaultExtensionNs="com.falsepattern.zigbrains">
<featureProvider
implementation="com.falsepattern.zigbrains.debugger.DebuggerFeatures"
/>
<debuggerDriverProvider
id="DefaultProvider"
implementation="com.falsepattern.zigbrains.debugger.ZigDefaultDebuggerDriverConfigurationProvider"
order="last"
/>
</extensions>
<extensions defaultExtensionNs="cidr.debugger">
<languageSupport
language="Zig"
implementationClass="com.falsepattern.zigbrains.debugger.ZigDebuggerLanguageSupport"
/>
<editorsExtension
language="Zig"
implementationClass="com.falsepattern.zigbrains.debugger.ZigDebuggerEditorsExtension"
/>
<lineBreakpointFileTypesProvider
implementation="com.falsepattern.zigbrains.debugger.ZigLineBreakpointFileTypesProvider"
/>
<localVariablesFilterHandler
implementation="com.falsepattern.zigbrains.debugger.ZigLocalVariablesFilterHandler"
/>
</extensions>
<extensionPoints>
</extensionPoints>
</idea-plugin>

View file

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

View file

@ -0,0 +1,36 @@
notification.title.debugger=Debugger
notification.content.debugger.successfully.downloaded=Debugger successfully downloaded
notification.content.debugger.downloading.failed=Debugger downloading failed
notification.content.debugger.metadata.downloading.failed=Debugger metadata downloading failed, switching to fallback
notification.content.debugger.metadata.fallback.parse.failed=Debugger fallback metadata parse failed
settings.debugger.toolchain.download.debugger.automatically.checkbox=Download and update the debugger automatically
settings.debugger.title=Zig
settings.debugger.toolchain.debugger.label=Debugger:
settings.debugger.toolchain.download.comment=Need to be <a>downloaded</a>
settings.debugger.toolchain.update.comment=Need to be <a>updated</a>
settings.debugger.toolchain.use.clion.toolchains=Use Clion toolchains instead
debugger.run.unavailable=Unable to Run Debugger
debugger.run.unavailable.reason.download=Debugger is not downloaded yet
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.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
debug.build.compile.failed.autodetect=Could not auto-detect default executable output directory "zig-out/bin"
debug.build.compile.failed.no-file=File `{0}` does not exist
debug.build.compile.failed.non-exec-file=File `{0}` is not executable
debug.build.compile.failed.generic=Failed to build project
exec.type.binary.label=Zig-compiled native executable
exec.option.label.binary.exe-path=Executable program path (not the zig compiler)
exec.option.label.binary.args=Command line arguments
exception.missing-exe-path=Missing executable path
configuration.binary.name=Native Application (Zig)
configuration.binary.suggested-name=Executable
configuration.binary.description=Binary executable compiled from zig code (useful for debugging on Windows)
msvc.consent.title=Network Request Consent
msvc.consent.deny=Use Bundled
msvc.consent.allow=Download
msvc.consent.body=Debugging on Windows requires downloading extra metadata from the internet.\nThe bundled fallback metadata will be used if the request is denied.

108
core/build.gradle.kts Normal file
View file

@ -0,0 +1,108 @@
import org.jetbrains.grammarkit.tasks.GenerateLexerTask
import org.jetbrains.grammarkit.tasks.GenerateParserTask
import org.jetbrains.intellij.platform.gradle.IntelliJPlatformType
plugins {
id("org.jetbrains.grammarkit")
kotlin("plugin.serialization")
}
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
}
}
//region grammars
run {
val grammarGenRoot = layout.buildDirectory.dir("generated/sources/grammarkit")
val rootPackagePath = "com/falsepattern/zigbrains"
val grammarSources = layout.projectDirectory.dir("src/main/grammar")
val parserDir = grammarGenRoot.map {it.dir("zig/parser")}
val lexerDir = grammarGenRoot.map {it.dir("zig/lexer")}
val lexerStringDir = grammarGenRoot.map {it.dir("zig/lexerstring")}
val zonParserDir = grammarGenRoot.map {it.dir("zon/parser")}
val zonLexerDir = grammarGenRoot.map {it.dir("zon/lexer")}
val grammarGenDirs = listOf(parserDir, lexerDir, lexerStringDir, zonParserDir, zonLexerDir)
sourceSets {
main {
java {
grammarGenDirs.forEach { srcDir(it) }
}
}
}
idea {
module {
grammarGenDirs.forEach {
val file = it.get().asFile
sourceDirs.add(file)
generatedSourceDirs.add(file)
}
sourceDirs.add(grammarSources.asFile)
}
}
tasks {
// region grammarkit
generateLexer {
purgeOldFiles = true
sourceFile = grammarSources.file("Zig.flex")
targetOutputDir = lexerDir.map { it.dir("$rootPackagePath/zig/lexer") }
}
register<GenerateLexerTask>("generateLexerString") {
purgeOldFiles = true
sourceFile = grammarSources.file("ZigString.flex")
targetOutputDir = lexerStringDir.map { it.dir("$rootPackagePath/zig/lexerstring") }
}
generateParser {
purgeOldFiles = true
sourceFile = grammarSources.file("Zig.bnf")
targetRootOutputDir = parserDir
pathToParser = "$rootPackagePath/zig/parser/ZigParser.java"
pathToPsiRoot = "$rootPackagePath/zig/psi"
}
register<GenerateLexerTask>("generateZonLexer") {
purgeOldFiles = true
sourceFile = grammarSources.file("Zon.flex")
targetOutputDir = zonLexerDir.map { it.dir("$rootPackagePath/zon/lexer") }
}
register<GenerateParserTask>("generateZonParser") {
purgeOldFiles = true
sourceFile = grammarSources.file("Zon.bnf")
targetRootOutputDir = zonParserDir
pathToParser = "$rootPackagePath/zon/parser/ZonParser.java"
pathToPsiRoot = "$rootPackagePath/zon/psi"
}
register<DefaultTask>("generateGrammars") {
group = "grammarkit"
dependsOn("generateLexer", "generateLexerString", "generateParser")
dependsOn("generateZonLexer", "generateZonParser")
}
compileJava {
dependsOn("generateGrammars")
}
compileKotlin {
dependsOn("generateGrammars")
}
}
}
//endregion grammars

View file

@ -1,17 +1,23 @@
/*
* Copyright 2023-2024 FalsePattern
* This file is part of ZigBrains.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* Copyright (C) 2023-2025 FalsePattern
* All Rights Reserved
*
* http://www.apache.org/licenses/LICENSE-2.0
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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/>.
*/
{
@ -26,9 +32,9 @@
psiImplPackage="com.falsepattern.zigbrains.zig.psi.impl"
elementTypeHolderClass="com.falsepattern.zigbrains.zig.psi.ZigTypes"
elementTypeClass="com.falsepattern.zigbrains.zig.psi.ZigElementType"
elementTypeClass="com.falsepattern.zigbrains.zig.parser.ZigElementType"
tokenTypeClass="com.falsepattern.zigbrains.zig.psi.ZigTokenType"
tokenTypeClass="com.falsepattern.zigbrains.zig.parser.ZigTokenType"
generateTokenAccessors = true
tokens=[
@ -159,6 +165,8 @@
STRING_LITERAL_SINGLE='quoted string literal'
STRING_LITERAL_MULTI='multiline string literal'
BAD_SQUOT='unterminated character literal'
BAD_DQUOT='unterminated string'
IDENTIFIER='identifier'
BUILTINIDENTIFIER='builtin identifier'
@ -172,11 +180,11 @@
Root ::= CONTAINER_DOC_COMMENT? ContainerMembers?
// *** Top level ***
ContainerMembers ::= ContainerDeclarations? (ContainerField COMMA)* (ContainerField | ContainerDeclarations)?
ContainerMembers ::= ContainerDeclaration* (ContainerField COMMA)* (ContainerField | ContainerDeclaration*)
ContainerDeclarations ::= (TestDecl | ComptimeDecl | DOC_COMMENT? KEYWORD_PUB? Decl)+
ContainerDeclaration ::= TestDecl | ComptimeDecl | DOC_COMMENT? KEYWORD_PUB? Decl
TestDecl ::= DOC_COMMENT? KEYWORD_TEST (STRING_LITERAL_SINGLE | IDENTIFIER)? Block {pin=2}
TestDecl ::= KEYWORD_TEST (STRING_LITERAL_SINGLE | IDENTIFIER)? Block {pin=1}
ComptimeDecl ::= KEYWORD_COMPTIME Block
@ -197,6 +205,7 @@ ContainerField ::= DOC_COMMENT? KEYWORD_COMPTIME? !KEYWORD_FN (IDENTIFIER COLON)
Statement
::= KEYWORD_COMPTIME ComptimeStatement
| KEYWORD_NOSUSPEND BlockExprStatement
| KEYWORD_SUSPEND BlockExprStatement
| KEYWORD_DEFER BlockExprStatement
| KEYWORD_ERRDEFER Payload? BlockExprStatement
| IfStatement
@ -407,14 +416,21 @@ WhilePrefix ::= KEYWORD_WHILE ZB_WhilePrefix_Operand PtrPayload? WhileContinueEx
private ZB_WhilePrefix_Operand ::= LPAREN Expr RPAREN {pin=1}
ForRange ::= Expr DOT2 Expr?
ForOperand ::= ForRange | Expr {recoverWhile="ZB_ForOperand_Recover"}
ForPrefix ::= KEYWORD_FOR LPAREN ZB_ForParams RPAREN ForPayload {pin=1}
private ZB_ForOperand_Recover ::= !(COMMA | RPAREN)
private ZB_ForParams ::= ForInput (COMMA ForInput)* COMMA? {recoverWhile="ZB_ForParams_Recover"}
ForPrefix ::= KEYWORD_FOR ZB_ForPrefix_Operands PtrIndexPayload {pin=1}
private ZB_ForParams_Recover ::= !(RPAREN)
private ZB_ForPrefix_Operands ::= LPAREN (ForOperand COMMA)* ForOperand RPAREN {pin=1}
ForInput ::= Expr (DOT2 Expr?)? {recoverWhile="ZB_ForInput_Recover"}
private ZB_ForInput_Recover ::= !(COMMA | RPAREN)
ForPayload ::= PIPE ZB_ForPayload_Item (COMMA ZB_ForPayload_Item)* PIPE {pin=1}
private ZB_ForPayload_Item ::= ASTERISK? IDENTIFIER {recoverWhile="ZB_ForPayload_Recover"}
private ZB_ForPayload_Recover ::= !(COMMA | PIPE)
// Payloads
Payload ::= PIPE IDENTIFIER PIPE

View file

@ -1,17 +1,23 @@
/*
* Copyright 2023-2024 FalsePattern
* This file is part of ZigBrains.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* Copyright (C) 2023-2025 FalsePattern
* All Rights Reserved
*
* http://www.apache.org/licenses/LICENSE-2.0
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.zig.lexer;
@ -28,9 +34,12 @@ import static com.falsepattern.zigbrains.zig.psi.ZigTypes.*;
%implements FlexLexer
%function advance
%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}
@ -46,55 +55,14 @@ oct_int={oct} {oct_}*
dec_int={dec} {dec_}*
hex_int={hex} {hex_}*
ox80_oxBF=[\200-\277]
oxF4=\364
ox80_ox8F=[\200-\217]
oxF1_oxF3=[\361-\363]
oxF0=\360
ox90_0xBF=[\220-\277]
oxEE_oxEF=[\356-\357]
oxED=\355
ox80_ox9F=[\200-\237]
oxE1_oxEC=[\341-\354]
oxE0=\340
oxA0_oxBF=[\240-\277]
oxC2_oxDF=[\302-\337]
// From https://lemire.me/blog/2018/05/09/how-quickly-can-you-check-that-a-string-is-valid-unicode-utf-8/
// First Byte Second Byte Third Byte Fourth Byte
// [0x00,0x7F]
// [0xC2,0xDF] [0x80,0xBF]
// 0xE0 [0xA0,0xBF] [0x80,0xBF]
// [0xE1,0xEC] [0x80,0xBF] [0x80,0xBF]
// 0xED [0x80,0x9F] [0x80,0xBF]
// [0xEE,0xEF] [0x80,0xBF] [0x80,0xBF]
// 0xF0 [0x90,0xBF] [0x80,0xBF] [0x80,0xBF]
// [0xF1,0xF3] [0x80,0xBF] [0x80,0xBF] [0x80,0xBF]
// 0xF4 [0x80,0x8F] [0x80,0xBF] [0x80,0xBF]
mb_utf8_literal= {oxF4} {ox80_ox8F} {ox80_oxBF} {ox80_oxBF}
| {oxF1_oxF3} {ox80_oxBF} {ox80_oxBF} {ox80_oxBF}
| {oxF0} {ox90_0xBF} {ox80_oxBF} {ox80_oxBF}
| {oxEE_oxEF} {ox80_oxBF} {ox80_oxBF}
| {oxED} {ox80_ox9F} {ox80_oxBF}
| {oxE1_oxEC} {ox80_oxBF} {ox80_oxBF}
| {oxE0} {oxA0_oxBF} {ox80_oxBF}
| {oxC2_oxDF} {ox80_oxBF}
ascii_char_not_nl_slash_squote=[\000-\011\013-\046\050-\133\135-\177]
char_escape= "\\x" {hex} {hex}
| "\\u{" {hex}+ "}"
| "\\" [nr\\t'\"]
char_char= {mb_utf8_literal}
| {char_escape}
| {ascii_char_not_nl_slash_squote}
char_char= \\ .
| [^\'\r\n\u0085\u2028\u2029]
string_char= \\ .
| [^\"\n]
| [^\"\r\n\u0085\u2028\u2029]
all_nl_wrap=[^\n]* [ \n]*
all_nl_nowrap=[^\n]* \n
nl_wrap={LF} (\s|{LF})*
all_no_nl=[^\r\n\u0085\u2028\u2029]+
FLOAT= "0x" {hex_int} "." {hex_int} ([pP] [-+]? {dec_int})?
@ -115,26 +83,38 @@ BUILTINIDENTIFIER="@"[A-Za-z_][A-Za-z0-9_]*
%state CHAR_LIT
%state ID_QUOT
%state UNT_QUOT
%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_nl_nowrap} { 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_nl_nowrap} { 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_nl_nowrap} { 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; }
@ -254,28 +234,49 @@ 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> [^] { yypushback(1); yybegin(UNT_QUOT); }
<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> "\"" { 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_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; }
<YYINITIAL> "\"" { yybegin(STR_LIT); }
<STR_LIT> {string_char}*"\"" { yybegin(YYINITIAL); return STRING_LITERAL_SINGLE; }
<STR_LIT> [^] { yypushback(1); yybegin(UNT_QUOT); }
<YYINITIAL> "\\\\" { yybegin(STR_MULT_LINE); }
<STR_MULT_LINE> {all_nl_wrap} "\\\\" { }
<STR_MULT_LINE> {all_nl_nowrap} { yybegin(YYINITIAL); return STRING_LITERAL_MULTI; }
//Identifiers
<YYINITIAL> {IDENTIFIER_PLAIN} { return IDENTIFIER; }
<YYINITIAL> "@\"" { yybegin(ID_QUOT); }
<ID_QUOT> {string_char}*"\"" { yybegin(YYINITIAL); return IDENTIFIER; }
<ID_QUOT> [^] { yypushback(1); yybegin(UNT_QUOT); }
<ID_QUOT> <<EOF>> { yybegin(YYINITIAL); return BAD_DQUOT; }
<ID_QUOT> [^] { yypushback(1); yybegin(UNT_DQUOT); }
<YYINITIAL> {BUILTINIDENTIFIER} { return BUILTINIDENTIFIER; }
<UNT_QUOT> [^\n]*{CRLF} { yybegin(YYINITIAL); return BAD_CHARACTER; }
//Error handling
<UNT_SQUOT> <<EOF>> { 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> {LF} { yybegin(YYINITIAL); return BAD_DQUOT; }
<UNT_DQUOT> {all_no_nl} { }
//Misc
<YYINITIAL> {WHITE_SPACE} { return WHITE_SPACE; }

View file

@ -0,0 +1,84 @@
/*
* 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.zig.lexerstring;
import com.intellij.lexer.FlexLexer;
import com.intellij.psi.tree.IElementType;
import static com.intellij.psi.TokenType.WHITE_SPACE;
import static com.intellij.psi.TokenType.BAD_CHARACTER;
import static com.falsepattern.zigbrains.zig.psi.ZigTypes.*;
import static com.intellij.psi.StringEscapesTokenTypes.*;
%%
%public
%class ZigLexerString
%implements FlexLexer
%function advance
%type IElementType
hex=[0-9a-fA-F]
char_escape_unicode= "\\x" {hex} {hex} | "\\u{" {hex}+ "}"
char_escape_unicode_invalid= "\\x" .? .? | "\\u" ("{" [^}]* "}"?)?
char_escape_single_valid= "\\" [nr\\t'\"]
char_escape_single_invalid= "\\" [^nr\\t'\"]
%state STR
%state CHAR
%state CHAR_END
%state CHAR_FINISH
%%
<YYINITIAL> {
"\"" { yybegin(STR); return STRING_LITERAL_SINGLE; }
"'" { yybegin(CHAR); return CHAR_LITERAL; }
[^] { return STRING_LITERAL_SINGLE; }
}
<STR> {
{char_escape_unicode} { return VALID_STRING_ESCAPE_TOKEN; }
{char_escape_unicode_invalid} { return INVALID_UNICODE_ESCAPE_TOKEN; }
{char_escape_single_valid} { return VALID_STRING_ESCAPE_TOKEN; }
{char_escape_single_invalid} { return INVALID_CHARACTER_ESCAPE_TOKEN; }
[^] { return STRING_LITERAL_SINGLE; }
}
<CHAR> {
{char_escape_unicode} { yybegin(CHAR_END); return VALID_STRING_ESCAPE_TOKEN; }
{char_escape_unicode_invalid} { yybegin(CHAR_END); return INVALID_UNICODE_ESCAPE_TOKEN; }
{char_escape_single_valid} { yybegin(CHAR_END); return VALID_STRING_ESCAPE_TOKEN; }
{char_escape_single_invalid} { yybegin(CHAR_END); return INVALID_CHARACTER_ESCAPE_TOKEN; }
[^] { yybegin(CHAR_END); return CHAR_LITERAL; }
}
<CHAR_END> {
"'" { yybegin(CHAR_FINISH); return CHAR_LITERAL; }
[^] { return BAD_CHARACTER; }
}
<CHAR_FINISH> {
[^] { return BAD_CHARACTER; }
}

View file

@ -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/>.
*/
{
parserClass="com.falsepattern.zigbrains.zon.parser.ZonParser"
extends="com.intellij.extapi.psi.ASTWrapperPsiElement"
psiClassPrefix="Zon"
psiImplClassSuffix="Impl"
psiPackage="com.falsepattern.zigbrains.zon.psi"
psiImplPackage="com.falsepattern.zigbrains.zon.psi.impl"
elementTypeHolderClass="com.falsepattern.zigbrains.zon.psi.ZonTypes"
elementTypeClass="com.falsepattern.zigbrains.zon.parser.ZonElementType"
tokenTypeClass="com.falsepattern.zigbrains.zon.parser.ZonTokenType"
tokens=[
LINE_COMMENT='comment'
DOT='.'
EQUAL='='
LBRACE='{'
RBRACE='}'
COMMA=','
KEYWORD_FALSE='false'
KEYWORD_TRUE='true'
KEYWORD_NULL='null'
NUM_NAN='nan'
NUM_INF='inf'
CHAR_LITERAL='char literal'
STRING_LITERAL_SINGLE='string literal'
STRING_LITERAL_MULTI='multiline string literal'
FLOAT='float'
INTEGER='integer'
IDENTIFIER='identifier'
BAD_SQUOT='unterminated quote'
BAD_DQUOT='unterminated double quote'
]
}
Root ::= Expr
Expr
::= CHAR_LITERAL
| StringLiteral
| DOT IDENTIFIER
| DOT InitList
| Bool
| Number
| KEYWORD_NULL
InitList
::= LBRACE ZB_InitList_Body RBRACE {pin=1}
private ZB_InitList_Body
::= FieldInit (COMMA ZB_InitList_FieldInit)* COMMA?
| Expr (COMMA ZB_InitList_Expr)* COMMA?
| ()
private ZB_InitList_FieldInit ::= FieldInit {recoverWhile="ZB_InitList_Recover"}
private ZB_InitList_Expr ::= Expr {recoverWhile="ZB_InitList_Recover"}
private ZB_InitList_Recover ::= !(COMMA | RBRACE)
FieldInit ::= DOT IDENTIFIER EQUAL Expr
Bool ::= KEYWORD_TRUE | KEYWORD_FALSE
Number ::= FLOAT | INTEGER | NUM_NAN | NUM_INF
StringLiteral ::= STRING_LITERAL_SINGLE | STRING_LITERAL_MULTI

View file

@ -0,0 +1,159 @@
/*
* 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.zon.lexer;
import com.intellij.lexer.FlexLexer;
import com.intellij.psi.tree.IElementType;
import static com.intellij.psi.TokenType.WHITE_SPACE;
import static com.intellij.psi.TokenType.BAD_CHARACTER;
import static com.falsepattern.zigbrains.zon.psi.ZonTypes.*;
%%
%class ZonFlexLexer
%implements FlexLexer
%function advance
%type IElementType
%unicode
WHITE_SPACE=\s+
// visual studio parity
LF=\r\n?|[\n\u0085\u2028\u2029]
bin=[01]
bin_="_"? {bin}
oct=[0-7]
oct_="_"? {oct}
hex=[0-9a-fA-F]
hex_="_"? {hex}
dec=[0-9]
dec_="_"? {dec}
bin_int={bin} {bin_}*
oct_int={oct} {oct_}*
dec_int={dec} {dec_}*
hex_int={hex} {hex_}*
char_char= \\ .
| [^\'\r\n\u0085\u2028\u2029]
string_char= \\ .
| [^\"\r\n\u0085\u2028\u2029]
nl_wrap={LF} (\s|{LF})*
all_no_nl=[^\r\n\u0085\u2028\u2029]+
FLOAT= "0x" {hex_int} "." {hex_int} ([pP] [-+]? {dec_int})?
| {dec_int} "." {dec_int} ([eE] [-+]? {dec_int})?
| "0x" {hex_int} [pP] [-+]? {dec_int}
| {dec_int} [eE] [-+]? {dec_int}
INTEGER= "0b" {bin_int}
| "0o" {oct_int}
| "0x" {hex_int}
| {dec_int}
IDENTIFIER_PLAIN=[A-Za-z_][A-Za-z0-9_]*
%state STR_LIT
%state STR_MULT_LINE
%state CHAR_LIT
%state ID_QUOT
%state UNT_SQUOT
%state UNT_DQUOT
%state CMT_LINE
%%
//Comments
<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
<YYINITIAL> "." { return DOT; }
<YYINITIAL> "=" { return EQUAL; }
<YYINITIAL> "{" { return LBRACE; }
<YYINITIAL> "}" { return RBRACE; }
<YYINITIAL> "," { return COMMA; }
//Keywords
<YYINITIAL> "false" { return KEYWORD_FALSE; }
<YYINITIAL> "true" { return KEYWORD_TRUE; }
<YYINITIAL> "null" { return KEYWORD_NULL; }
<YYINITIAL> "nan" { return NUM_NAN; }
<YYINITIAL> "inf" { return NUM_INF; }
//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> "\"" { 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_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; }
<ID_QUOT> <<EOF>> { yybegin(YYINITIAL); return BAD_DQUOT; }
<ID_QUOT> [^] { yypushback(1); yybegin(UNT_DQUOT); }
//Error handling
<UNT_SQUOT> <<EOF>> { 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> {LF} { yybegin(YYINITIAL); return BAD_DQUOT; }
<UNT_DQUOT> {all_no_nl} { }
//Misc
<YYINITIAL> {WHITE_SPACE} { return WHITE_SPACE; }
[^] { return BAD_CHARACTER; }

View file

@ -0,0 +1,34 @@
/*
* 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
import com.intellij.openapi.util.IconLoader
import org.jetbrains.annotations.NonNls
@NonNls
object Icons {
@JvmField
val Zig = IconLoader.getIcon("/icons/zig.svg", Icons::class.java)
@JvmField
val Zon = IconLoader.getIcon("/icons/zon.svg", Icons::class.java)
}

View file

@ -0,0 +1,107 @@
/*
* 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
import com.intellij.ide.BrowserUtil
import com.intellij.ide.plugins.PluginManager
import com.intellij.notification.Notification
import com.intellij.notification.NotificationAction
import com.intellij.notification.NotificationType
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.extensions.PluginId
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 java.lang.reflect.Constructor
import java.lang.reflect.Method
class ZBStartup: ProjectActivity {
var firstInit = true
override suspend fun execute(project: Project) {
if (firstInit) {
firstInit = false
if (!PluginManager.isPluginInstalled(PluginId.getId("com.intellij.modules.cidr.debugger")) &&
PluginManager.isPluginInstalled(PluginId.getId("com.intellij.modules.nativeDebug-plugin-capable"))) {
val notif = Notification(
"zigbrains",
ZigBrainsBundle.message("notification.title.native-debug"),
ZigBrainsBundle.message("notification.content.native-debug"),
NotificationType.INFORMATION
)
if (JBInternalPluginManagerConfigurable.successful) {
notif.addAction(object: NotificationAction(ZigBrainsBundle.message("notification.content.native-debug.market")) {
override fun actionPerformed(e: AnActionEvent, notification: Notification) {
val configurable = JBInternalPluginManagerConfigurable()
ShowSettingsUtil.getInstance().editConfigurable(null as Project?, configurable.instance) {
configurable.openMarketplaceTab("/vendor:\"JetBrains s.r.o.\" /tag:Debugging \"Native Debugging Support\"")
}
}
})
}
notif.addAction(object: NotificationAction(ZigBrainsBundle.message("notification.content.native-debug.browser")) {
override fun actionPerformed(e: AnActionEvent, notification: Notification) {
BrowserUtil.browse("https://plugins.jetbrains.com/plugin/12775-native-debugging-support")
}
})
notif.notify(null)
}
}
}
}
//JetBrains Internal API, but we need to access it, so access it reflectively (hopefully safe enough to pass verifier)
private class JBInternalPluginManagerConfigurable {
init {
if (!successful) {
throw IllegalStateException()
}
}
val instance = constructor.newInstance() as Configurable
fun openMarketplaceTab(option: String) {
openMarketplaceTab.invoke(instance, option)
}
companion object {
private lateinit var constructor: Constructor<*>
private lateinit var openMarketplaceTab: Method
val successful: Boolean
init {
lateinit var constructor: Constructor<*>
lateinit var openMarketplaceTab: Method
val successful = try {
val theClass = Class.forName("com_intellij_ide_plugins_PluginManagerConfigurable".replace('_', '.'))
constructor = theClass.getDeclaredConstructor().apply { isAccessible = true }
openMarketplaceTab = theClass.getDeclaredMethod("openMarketplaceTab", String::class.java).apply { isAccessible = true }
true
} catch (_: Throwable) { false }
if (successful) {
this.constructor = constructor
this.openMarketplaceTab = openMarketplaceTab
}
this.successful = successful
}
}
}

View file

@ -0,0 +1,50 @@
/*
* 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
import com.intellij.DynamicBundle
import org.jetbrains.annotations.Nls
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.PropertyKey
import java.util.function.Supplier
@NonNls
private const val BUNDLE = "zigbrains.Bundle"
internal object ZigBrainsBundle {
private val INSTANCE = DynamicBundle(ZigBrainsBundle::class.java, BUNDLE)
fun message(
key: @PropertyKey(resourceBundle = BUNDLE) String,
vararg params: Any
): @Nls String {
return INSTANCE.getMessage(key, *params)
}
fun lazyMessage(
key: @PropertyKey(resourceBundle = BUNDLE) String,
vararg params: Any
): Supplier<@Nls String> {
return INSTANCE.getLazyMessage(key, *params)
}
}

View file

@ -0,0 +1,26 @@
/*
* 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
@JvmRecord
data class DirenvOutput(val output: String, val error: Boolean)

View file

@ -0,0 +1,162 @@
/*
* 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
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.configurations.PathEnvironmentVariableUtil
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
@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()
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(
GROUP_DISPLAY_ID,
ZigBrainsBundle.message("notification.title.direnv-blocked"),
ZigBrainsBundle.message("notification.content.direnv-blocked"),
NotificationType.ERROR
))
return Env.empty
} else {
Notifications.Bus.notify(Notification(
GROUP_DISPLAY_ID,
ZigBrainsBundle.message("notification.title.direnv-error"),
ZigBrainsBundle.message("notification.content.direnv-error", runOutput.output),
NotificationType.ERROR
))
return Env.empty
}
}
return if (runOutput.output.isBlank()) {
Env.empty
} else {
Env(Json.decodeFromString<Map<String, String>>(runOutput.output))
}
}
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) {
mutex.withLock {
val process = cli.createProcess()
val exitCode = process.awaitExit()
process to exitCode
}
}
}
if (exitCode != 0) {
val stdErr = process.errorStream.bufferedReader().use { it.readText() }
return DirenvOutput(stdErr, true)
}
val stdOut = process.inputStream.bufferedReader().use { it.readText() }
return DirenvOutput(stdOut, false)
}
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)
}
}
}
sealed interface IDirenvService {
val isInstalled: Boolean
val isEnabled: DirenvState
suspend fun import(): Env
}
private val envFiles = listOf(".envrc", ".env")

View file

@ -0,0 +1,40 @@
/*
* 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
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
enum class DirenvState {
Auto,
Enabled,
Disabled;
fun isEnabled(project: Project?): Boolean {
return when(this) {
Enabled -> true
Disabled -> false
Auto -> project?.service<DirenvService>()?.hasDotEnv() == true
}
}
}

View file

@ -0,0 +1,62 @@
/*
* 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
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 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 findAllExecutablesOnPATH(exe: @NonNls String) = flow {
val exeName = if (SystemInfo.isWindows) "$exe.exe" else exe
val paths = path ?: return@flow
for (dir in paths) {
val path = dir.toNioPathOrNull()?.absolute() ?: continue
if (!path.toFile().exists() || !path.isDirectory())
continue
val exePath = path.resolve(exeName).absolute()
if (!exePath.isRegularFile() || !exePath.isExecutable())
continue
emit(exePath)
}
}.flowOn(Dispatchers.IO)
companion object {
val empty = Env(emptyMap())
}
}

View file

@ -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
}
}

View file

@ -0,0 +1,40 @@
/*
* 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.Icons
import com.intellij.ide.actions.CreateFileFromTemplateAction
import com.intellij.ide.actions.CreateFileFromTemplateDialog
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDirectory
class ZigNewFileAction: CreateFileFromTemplateAction() {
override fun buildDialog(project: Project, directory: PsiDirectory, builder: CreateFileFromTemplateDialog.Builder) {
builder.setTitle("Zig File")
.addKind("Empty file", Icons.Zig, "blank_zig_file")
}
override fun getActionName(directory: PsiDirectory?, newName: String, templateName: String?): String {
return "Zig File"
}
}

View file

@ -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
}
}

View file

@ -0,0 +1,33 @@
/*
* 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.console
import com.intellij.execution.filters.ConsoleFilterProvider
import com.intellij.execution.filters.Filter
import com.intellij.openapi.project.Project
class ZigConsoleFilterProvider: ConsoleFilterProvider {
override fun getDefaultFilters(project: Project): Array<Filter> {
return arrayOf(ZigSourceFileFilter(project))
}
}

View file

@ -0,0 +1,82 @@
/*
* 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.console
import com.intellij.execution.filters.Filter
import com.intellij.execution.filters.Filter.ResultItem
import com.intellij.execution.filters.OpenFileHyperlinkInfo
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.vfs.refreshAndFindVirtualFile
import com.intellij.openapi.vfs.toNioPathOrNull
import java.io.File
import java.nio.file.InvalidPathException
import java.nio.file.Path
import kotlin.math.max
class ZigSourceFileFilter(private val project: Project): Filter {
private val projectPath = runCatching { project.guessProjectDir()?.toNioPathOrNull()?.toFile() }.getOrNull()
override fun applyFilter(line: String, entireLength: Int): Filter.Result? {
val lineStart = entireLength - line.length
val results = ArrayList<ResultItem>()
val matcher = LEN_REGEX.findAll(line)
for (match in matcher) {
val start = match.range.first
val pair = findLongestParsablePathFromOffset(line, start)
val path = pair?.first ?: return null
val file = path.refreshAndFindVirtualFile() ?: return null
val lineNumber = max(match.groups[1]!!.value.toInt() - 1, 0)
val lineOffset = max(match.groups[2]!!.value.toInt() - 1, 0)
results.add(ResultItem(lineStart + pair.second, lineStart + match.range.last + 1, OpenFileHyperlinkInfo(project, file, lineNumber, lineOffset)))
}
return Filter.Result(results)
}
private fun findLongestParsablePathFromOffset(line: String, end: Int): Pair<Path, Int>? {
var longestStart = -1
var longest: File? = null
for (i in end - 1 downTo 0) {
try {
val pathStr = line.substring(i, end)
var file = File(pathStr)
if (!file.isFile) {
if (projectPath == null) {
continue
}
file = projectPath.resolve(pathStr)
if (!file.isFile) {
continue
}
}
longest = file
longestStart = i
} catch (ignored: InvalidPathException) {
}
}
longest ?: return null
return Pair(longest.toPath(), longestStart)
}
}
private val LEN_REGEX = Regex(":(\\d+):(\\d+)")

View file

@ -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.execution
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.ConfigurationTypeBase
import com.intellij.execution.configurations.runConfigurationType
inline fun <reified T: ConfigurationTypeBase> firstConfigFactory(): ConfigurationFactory {
return runConfigurationType<T>().configurationFactories.first()
}

View file

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

View file

@ -0,0 +1,384 @@
/*
* 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.ZigBrainsBundle
import com.falsepattern.zigbrains.project.execution.base.ZigConfigurable.ZigConfigModule
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.ui.ComboBox
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
import org.jdom.Element
import org.jetbrains.annotations.Nls
import java.io.Serializable
import java.nio.file.Path
import javax.swing.JComponent
import kotlin.io.path.pathString
class ZigConfigEditor<T : ZigExecConfig<T>>(private val state: ZigExecConfig<T>) : SettingsEditor<T>() {
private val configModules = ArrayList<ZigConfigModule<*>>()
override fun resetEditorFrom(s: T) {
outer@ for (cfg in s.getConfigurables()) {
for (module in configModules) {
if (module.tryReset(cfg))
continue@outer
}
}
}
override fun applyEditorTo(s: T) {
outer@ for (cfg in s.getConfigurables()) {
for (module in configModules) {
if (module.tryApply(cfg)) {
continue@outer
}
}
}
}
override fun createEditor(): JComponent {
configModules.clear()
configModules.addAll(state.getConfigurables().map { it.createEditor() })
return panel {
for (module in configModules) {
module.construct(this)
}
}
}
override fun disposeEditor() {
for (module in configModules) {
module.dispose()
}
configModules.clear()
}
}
interface ZigConfigurable<T : ZigConfigurable<T>> : Serializable, Cloneable {
fun readExternal(element: Element)
fun writeExternal(element: Element)
fun createEditor(): ZigConfigModule<T>
public override fun clone(): T
interface ZigConfigModule<T : ZigConfigurable<T>> : Disposable {
fun tryMatch(cfg: ZigConfigurable<*>): T?
fun apply(configurable: T): Boolean
fun reset(configurable: T)
fun tryApply(cfg: ZigConfigurable<*>): Boolean {
val x = tryMatch(cfg)
if (x != null) {
return apply(x)
}
return false
}
fun tryReset(cfg: ZigConfigurable<*>): Boolean {
val x = tryMatch(cfg)
if (x != null) {
reset(x)
return true
}
return false
}
fun construct(p: Panel)
}
}
abstract class PathConfigurable<T : PathConfigurable<T>> : ZigConfigurable<T> {
var path: Path? = null
override fun readExternal(element: Element) {
path = element.readString(serializedName)?.ifBlank { null }?.toNioPathOrNull() ?: return
}
override fun writeExternal(element: Element) {
element.writeString(serializedName, path?.pathString ?: "")
}
abstract val serializedName: String
abstract class PathConfigModule<T : PathConfigurable<T>> : ZigConfigModule<T> {
override fun apply(configurable: T): Boolean {
val str = stringValue
if (str.isBlank()) {
configurable.path = null
} else {
configurable.path = str.toNioPathOrNull() ?: return false
}
return true
}
override fun reset(configurable: T) {
stringValue = configurable.path?.pathString ?: ""
}
protected abstract var stringValue: String
}
}
class WorkDirectoryConfigurable(@Transient override val serializedName: String) : PathConfigurable<WorkDirectoryConfigurable>(), Cloneable {
override fun createEditor(): ZigConfigModule<WorkDirectoryConfigurable> {
return WorkDirectoryConfigModule(serializedName)
}
override fun clone(): WorkDirectoryConfigurable {
return super<Cloneable>.clone() as WorkDirectoryConfigurable
}
class WorkDirectoryConfigModule(private val serializedName: String) : PathConfigModule<WorkDirectoryConfigurable>() {
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
override fun tryMatch(cfg: ZigConfigurable<*>): WorkDirectoryConfigurable? {
return if (cfg is WorkDirectoryConfigurable && cfg.serializedName == serializedName) cfg else null
}
override fun construct(p: Panel): Unit = with(p) {
row(ZigBrainsBundle.message("exec.option.label.working-directory")) {
cell(field).resizableColumn().align(AlignX.FILL)
}
}
override fun dispose() {
field.dispose()
}
}
}
class FilePathConfigurable(
@Transient override val serializedName: String,
@Transient @Nls private val guiLabel: String
) : PathConfigurable<FilePathConfigurable>(), Cloneable {
override fun createEditor(): ZigConfigModule<FilePathConfigurable> {
return FilePathConfigModule(serializedName, guiLabel)
}
override fun clone(): FilePathConfigurable {
return super<Cloneable>.clone() as FilePathConfigurable
}
class FilePathConfigModule(private val serializedName: String, @Nls private val label: String) : PathConfigModule<FilePathConfigurable>() {
private val field = textFieldWithBrowseButton(
null,
FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor(),
)
override var stringValue by field::text
override fun tryMatch(cfg: ZigConfigurable<*>): FilePathConfigurable? {
return if (cfg is FilePathConfigurable && cfg.serializedName == serializedName) cfg else null
}
override fun construct(p: Panel): Unit = with(p) {
row(label) {
cell(field).resizableColumn().align(AlignX.FILL)
}
}
override fun dispose() {
Disposer.dispose(field)
}
}
}
open class CheckboxConfigurable(
@Transient private val serializedName: String,
@Transient @Nls private val label: String,
var value: Boolean
) : ZigConfigurable<CheckboxConfigurable>, Cloneable {
override fun readExternal(element: Element) {
value = element.readBoolean(serializedName) ?: return
}
override fun writeExternal(element: Element) {
element.writeBoolean(serializedName, value)
}
override fun createEditor(): ZigConfigModule<CheckboxConfigurable> {
return CheckboxConfigModule(serializedName, label)
}
override fun clone(): CheckboxConfigurable {
return super<Cloneable>.clone() as CheckboxConfigurable
}
class CheckboxConfigModule(
private val serializedName: String,
@Nls private val label: String
) : ZigConfigModule<CheckboxConfigurable> {
private val checkBox = JBCheckBox()
override fun tryMatch(cfg: ZigConfigurable<*>): CheckboxConfigurable? {
return if (cfg is CheckboxConfigurable && cfg.serializedName == serializedName) cfg else null
}
override fun apply(configurable: CheckboxConfigurable): Boolean {
configurable.value = checkBox.isSelected
return true
}
override fun reset(configurable: CheckboxConfigurable) {
checkBox.isSelected = configurable.value
}
override fun construct(p: Panel): Unit = with(p) {
row(label) {
cell(checkBox)
}
}
override fun dispose() {}
}
}
class OptimizationConfigurable(
@Transient private val serializedName: String,
var level: OptimizationLevel = OptimizationLevel.Debug,
var forced: Boolean = false
) : ZigConfigurable<OptimizationConfigurable>, Cloneable {
override fun readExternal(element: Element) {
element.readChild(serializedName)?.apply {
readEnum<OptimizationLevel>("level")?.let { level = it }
readBoolean("forced")?.let { forced = it }
}
}
override fun writeExternal(element: Element) {
element.writeChild(serializedName).apply {
writeEnum("level", level)
writeBoolean("forced", forced)
}
}
override fun createEditor(): ZigConfigModule<OptimizationConfigurable> {
return OptimizationConfigModule(serializedName)
}
override fun clone(): OptimizationConfigurable {
return super<Cloneable>.clone() as OptimizationConfigurable
}
class OptimizationConfigModule(private val serializedName: String) : ZigConfigModule<OptimizationConfigurable> {
private val levels = ComboBox(OptimizationLevel.entries.toTypedArray())
private val forced = JBCheckBox(ZigBrainsBundle.message("exec.option.label.optimization.force"))
override fun tryMatch(cfg: ZigConfigurable<*>): OptimizationConfigurable? {
return if (cfg is OptimizationConfigurable && cfg.serializedName == serializedName) cfg else null
}
override fun apply(configurable: OptimizationConfigurable): Boolean {
configurable.level = levels.item
configurable.forced = forced.isSelected
return true
}
override fun reset(configurable: OptimizationConfigurable) {
levels.item = configurable.level
forced.isSelected = configurable.forced
}
override fun construct(p: Panel): Unit = with(p) {
row(ZigBrainsBundle.message("exec.option.label.optimization")) {
cell(levels)
cell(forced)
}
}
override fun dispose() {
}
}
}
class ArgsConfigurable(
@Transient private val serializedName: String,
@Transient @Nls private val guiName: String
) : ZigConfigurable<ArgsConfigurable>, Cloneable {
var args: String = ""
override fun readExternal(element: Element) {
args = element.readString(serializedName) ?: element.readStrings(serializedName)?.joinToString(separator = " ") { if (it.contains(' ')) "\"$it\"" else it } ?: ""
}
fun argsSplit(): List<String> {
return translateCommandline(args)
}
override fun writeExternal(element: Element) {
element.writeString(serializedName, args)
}
override fun createEditor(): ZigConfigModule<ArgsConfigurable> {
return ArgsConfigModule(serializedName, guiName)
}
override fun clone(): ArgsConfigurable {
return super<Cloneable>.clone() as ArgsConfigurable
}
class ArgsConfigModule(
private val serializedName: String,
@Nls private val guiName: String
) : ZigConfigModule<ArgsConfigurable> {
private val argsField = JBTextField()
override fun tryMatch(cfg: ZigConfigurable<*>): ArgsConfigurable? {
return if (cfg is ArgsConfigurable && cfg.serializedName == serializedName) cfg else null
}
override fun apply(configurable: ArgsConfigurable): Boolean {
configurable.args = argsField.text ?: ""
return true
}
override fun reset(configurable: ArgsConfigurable) {
argsField.text = configurable.args
}
override fun construct(p: Panel): Unit = with(p) {
row(guiName) {
cell(argsField).resizableColumn().align(AlignX.FILL)
}
}
override fun dispose() {
}
}
}

View file

@ -0,0 +1,30 @@
/*
* 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
enum class OptimizationLevel {
Debug,
ReleaseSafe,
ReleaseFast,
ReleaseSmall
}

View file

@ -0,0 +1,84 @@
/*
* 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.ZigFile
import com.intellij.execution.actions.ConfigurationContext
import com.intellij.execution.actions.ConfigurationFromContext
import com.intellij.execution.actions.LazyRunConfigurationProducer
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.openapi.util.Ref
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.toNioPathOrNull
import com.intellij.psi.PsiElement
import java.nio.file.Path
abstract class ZigConfigProducer<T: ZigExecConfig<T>>: LazyRunConfigurationProducer<T>() {
abstract override fun getConfigurationFactory(): ConfigurationFactory
override fun setupConfigurationFromContext(configuration: T, context: ConfigurationContext, sourceElement: Ref<PsiElement>): Boolean {
val element = context.location?.psiElement ?: return false
val psiFile = element.containingFile as? ZigFile ?: return false
val theFile = psiFile.virtualFile ?: return false
val filePath = theFile.toNioPathOrNull() ?: return false
return setupConfigurationFromContext(configuration, element, psiFile, filePath, theFile)
}
override fun isConfigurationFromContext(configuration: T, context: ConfigurationContext): Boolean {
val element = context.location?.psiElement ?: return false
val psiFile = element.containingFile as? ZigFile ?: return false
val theFile = psiFile.virtualFile ?: return false
val filePath = theFile.toNioPathOrNull() ?: return false
return isConfigurationFromContext(configuration, element, psiFile, filePath, theFile)
}
/*
TODO implement these
@Override
protected boolean setupConfigurationFromContext(@NotNull ZigExecConfigRun configuration, PsiElement element, String filePath, VirtualFile theFile) {
if (PsiUtil.getElementType(element) == ZigTypes.KEYWORD_TEST) {
configuration.command = "test " + filePath;
configuration.setName("Test " + theFile.getPresentableName());
} else if ("build.zig".equals(theFile.getName())) {
configuration.command = "build";
configuration.setName("Build");
} else {
configuration.extraArgs = filePath;
configuration.setName(theFile.getPresentableName());
}
return true;
}
@Override
protected boolean isConfigurationFromContext(@NotNull ZigExecConfigRun configuration, String filePath, VirtualFile vFile, PsiElement element) {
if (!configuration.command.contains(filePath)) {
return configuration.command.startsWith("build") && vFile.getName().equals("build.zig");
}
return (PsiUtil.getElementType(element) == ZigTypes.KEYWORD_TEST) == configuration.command.startsWith("test ");
}
*/
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
}

View file

@ -0,0 +1,81 @@
/*
* This file is part of ZigBrains.
*
* Copyright (C) 2023-2025 FalsePattern
* All Rights Reserved
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* ZigBrains is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, only version 3 of the License.
*
* ZigBrains is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with ZigBrains. If not, see <https://www.gnu.org/licenses/>.
*/
package com.falsepattern.zigbrains.project.execution.base
import com.falsepattern.zigbrains.direnv.DirenvService
import com.intellij.execution.ExecutionException
import com.intellij.execution.Executor
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.configurations.LocatableConfigurationBase
import com.intellij.execution.configurations.RunConfiguration
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.openapi.options.SettingsEditor
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.guessProjectDir
import com.intellij.openapi.util.NlsActions.ActionText
import com.intellij.openapi.vfs.toNioPathOrNull
import org.jdom.Element
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
abstract val suggestedName: @ActionText String
@Throws(ExecutionException::class)
abstract suspend fun buildCommandLineArgs(debug: Boolean): List<String>
abstract override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState<T>
override fun getConfigurationEditor(): SettingsEditor<out RunConfiguration> {
return ZigConfigEditor(this)
}
override fun readExternal(element: Element) {
super.readExternal(element)
getConfigurables().forEach { it.readExternal(element) }
}
override fun writeExternal(element: Element) {
super.writeExternal(element)
getConfigurables().forEach { it.writeExternal(element) }
}
suspend fun patchCommandLine(commandLine: GeneralCommandLine): GeneralCommandLine {
val direnv = DirenvService.getInstance(project)
if (direnv.isEnabled.isEnabled(project)) {
commandLine.withEnvironment(direnv.import().env)
}
return commandLine
}
override fun clone(): T {
val myClone = super.clone() as ZigExecConfig<*>
myClone.workingDirectory = workingDirectory.clone()
@Suppress("UNCHECKED_CAST")
return myClone as T
}
open fun getConfigurables(): List<ZigConfigurable<*>> = listOf(workingDirectory)
}

View file

@ -0,0 +1,74 @@
/*
* 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.ZigBrainsBundle
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.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.runners.ExecutionEnvironment
import com.intellij.platform.ide.progress.ModalTaskOwner
import kotlin.io.path.pathString
abstract class ZigProfileState<T: ZigExecConfig<T>> (
environment: ExecutionEnvironment,
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"}) {
startProcessSuspend()
}
}
@Throws(ExecutionException::class)
suspend fun startProcessSuspend(): ProcessHandler {
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: ZigToolchain, debug: Boolean): GeneralCommandLine {
val workingDir = configuration.workingDirectory
val zigExePath = toolchain.zig.path()
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)
}
}

View file

@ -0,0 +1,65 @@
/*
* 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.ZigTypes
import com.intellij.execution.lineMarker.ExecutorAction
import com.intellij.execution.lineMarker.RunLineMarkerContributor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.util.elementType
import javax.swing.Icon
abstract class ZigTopLevelLineMarker: RunLineMarkerContributor() {
private fun getParentIfTopLevel(element: PsiElement): PsiElement? {
var parent = getDeclaration(element)
var nestingLevel = 0
while (parent != null && parent !is PsiFile) {
if (parent.elementType == ZigTypes.CONTAINER_DECLARATION) {
if (nestingLevel != 0)
return null
nestingLevel++
}
parent = parent.parent
}
if (nestingLevel != 1)
return null
return parent
}
fun elementMatches(element: PsiElement): Boolean {
return getParentIfTopLevel(element) != null
}
override fun getInfo(element: PsiElement): Info? {
if (!elementMatches(element))
return null
val actions = ExecutorAction.getActions(0)
return Info(getIcon(element), actions, null)
}
abstract fun getDeclaration(element: PsiElement): PsiElement?
abstract fun getIcon(element: PsiElement): Icon
}

View file

@ -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"
}

View file

@ -0,0 +1,83 @@
/*
* 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.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>() {
override fun getConfigurationFactory(): ConfigurationFactory {
return firstConfigFactory<ZigConfigTypeBuild>()
}
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
}
}
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
}
}

View file

@ -0,0 +1,53 @@
/*
* 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.build
import com.falsepattern.zigbrains.Icons
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.ConfigurationTypeBase
import com.intellij.execution.configurations.RunConfiguration
import com.intellij.openapi.project.Project
class ZigConfigTypeBuild : ConfigurationTypeBase(
IDENTIFIER,
ZigBrainsBundle.message("configuration.build.name"),
ZigBrainsBundle.message("configuration.build.description"),
Icons.Zig
) {
init {
addFactory(ConfigFactoryRun(this))
}
class ConfigFactoryRun(type: ZigConfigTypeBuild): ConfigurationFactory(type) {
override fun createTemplateConfiguration(project: Project): RunConfiguration {
return ZigExecConfigBuild(project, this)
}
override fun getId(): String {
return IDENTIFIER
}
}
}
private const val IDENTIFIER = "ZIGBRAINS_BUILD"

View file

@ -0,0 +1,82 @@
/*
* 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.build
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.execution.base.*
import com.falsepattern.zigbrains.shared.ZBFeatures
import com.intellij.execution.ExecutionException
import com.intellij.execution.Executor
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.openapi.project.Project
class ZigExecConfigBuild(project: Project, factory: ConfigurationFactory): ZigExecConfig<ZigExecConfigBuild>(project, factory, ZigBrainsBundle.message("exec.type.build.label")) {
var buildSteps = ArgsConfigurable("buildSteps", ZigBrainsBundle.message("exec.option.label.build.steps"))
private set
var extraArgs = ArgsConfigurable("compilerArgs", ZigBrainsBundle.message("exec.option.label.build.args"))
private set
var debugBuildSteps = ArgsConfigurable("debugBuildSteps", ZigBrainsBundle.message("exec.option.label.build.steps-debug"))
private set
var debugExtraArgs = ArgsConfigurable("debugCompilerArgs", ZigBrainsBundle.message("exec.option.label.build.args-debug"))
private set
var exePath = FilePathConfigurable("exePath", ZigBrainsBundle.message("exec.option.label.build.exe-path-debug"))
private set
var exeArgs = ArgsConfigurable("exeArgs", ZigBrainsBundle.message("exec.option.label.build.exe-args-debug"))
private set
@Throws(ExecutionException::class)
override suspend fun buildCommandLineArgs(debug: Boolean): List<String> {
val result = ArrayList<String>()
result.add("build")
val steps = if (debug) debugBuildSteps.argsSplit() else buildSteps.argsSplit()
result.addAll(steps)
result.addAll(if (debug) debugExtraArgs.argsSplit() else extraArgs.argsSplit())
return result
}
override val suggestedName: String
get() = ZigBrainsBundle.message("configuration.build.suggested-name")
override fun clone(): ZigExecConfigBuild {
val clone = super.clone()
clone.buildSteps = buildSteps.clone()
clone.exeArgs = exeArgs.clone()
clone.exePath = exePath.clone()
clone.exeArgs = exeArgs.clone()
return clone
}
override fun getConfigurables(): List<ZigConfigurable<*>> {
val baseCfg = super.getConfigurables() + listOf(buildSteps, extraArgs)
return if (ZBFeatures.debug()) {
baseCfg + listOf(debugBuildSteps, debugExtraArgs, exePath, exeArgs)
} else {
baseCfg
}
}
override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState<ZigExecConfigBuild> {
return ZigProfileStateBuild(environment, this)
}
}

View file

@ -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.build
import com.falsepattern.zigbrains.project.execution.base.ZigTopLevelLineMarker
import com.falsepattern.zigbrains.zig.psi.ZigTypes
import com.intellij.icons.AllIcons.RunConfigurations.TestState
import com.intellij.psi.PsiElement
import com.intellij.psi.util.elementType
import javax.swing.Icon
class ZigLineMarkerBuild: ZigTopLevelLineMarker() {
override fun getDeclaration(element: PsiElement): PsiElement? {
if (element.elementType != ZigTypes.IDENTIFIER)
return null
if (!element.textMatches("build"))
return null
val parent = element.parent ?: return null
if (parent.elementType != ZigTypes.FN_PROTO)
return null
val file = element.containingFile ?: return null
val fileName = file.virtualFile.name
if (fileName != "build.zig")
return null
return parent.parent
}
override fun getIcon(element: PsiElement): Icon {
return TestState.Run
}
}

View file

@ -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.project.execution.build
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
import com.intellij.execution.runners.ExecutionEnvironment
class ZigProfileStateBuild(environment: ExecutionEnvironment, configuration: ZigExecConfigBuild) : ZigProfileState<ZigExecConfigBuild>(environment, configuration)

View file

@ -0,0 +1,60 @@
/*
* 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.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
import com.intellij.psi.PsiElement
import java.nio.file.Path
class ZigConfigProducerRun: ZigConfigProducer<ZigExecConfigRun>() {
override fun getConfigurationFactory(): ConfigurationFactory {
return firstConfigFactory<ZigConfigTypeRun>()
}
override fun setupConfigurationFromContext(configuration: ZigExecConfigRun, element: PsiElement, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean {
if (!psiFile.hasMainFunction()) {
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, psiFile: ZigFile, filePath: Path, theFile: VirtualFile): Boolean {
return filePath == configuration.filePath.path
}
override fun shouldReplace(self: ConfigurationFromContext, other: ConfigurationFromContext): Boolean {
return self.configurationType is ZigConfigTypeRun
}
}

View file

@ -0,0 +1,53 @@
/*
* 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.run
import com.falsepattern.zigbrains.Icons
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.ConfigurationTypeBase
import com.intellij.execution.configurations.RunConfiguration
import com.intellij.openapi.project.Project
class ZigConfigTypeRun : ConfigurationTypeBase(
IDENTIFIER,
ZigBrainsBundle.message("configuration.run.name"),
ZigBrainsBundle.message("configuration.run.description"),
Icons.Zig
) {
init {
addFactory(ConfigFactoryRun(this))
}
class ConfigFactoryRun(type: ZigConfigTypeRun): ConfigurationFactory(type) {
override fun createTemplateConfiguration(project: Project): RunConfiguration {
return ZigExecConfigRun(project, this)
}
override fun getId(): String {
return IDENTIFIER
}
}
}
private const val IDENTIFIER = "ZIGBRAINS_RUN"

View file

@ -0,0 +1,78 @@
/*
* 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.run
import com.falsepattern.zigbrains.ZigBrainsBundle
import com.falsepattern.zigbrains.project.execution.base.*
import com.intellij.execution.ExecutionException
import com.intellij.execution.Executor
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.runners.ExecutionEnvironment
import com.intellij.openapi.project.Project
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 optimization = OptimizationConfigurable("optimization")
private set
var compilerArgs = ArgsConfigurable("compilerArgs", ZigBrainsBundle.message("exec.option.label.compiler-args"))
private set
var exeArgs = ArgsConfigurable("exeArgs", ZigBrainsBundle.message("exec.option.label.exe-args"))
private set
override suspend fun buildCommandLineArgs(debug: Boolean): List<String> {
val result = ArrayList<String>()
result.add(if (debug) "build-exe" else "run")
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))
}
result.addAll(compilerArgs.argsSplit())
if (!debug) {
result.add("--")
result.addAll(exeArgs.argsSplit())
}
return result
}
override val suggestedName: String
get() = ZigBrainsBundle.message("configuration.run.suggested-name")
override fun clone(): ZigExecConfigRun {
val clone = super.clone()
clone.filePath = filePath.clone()
clone.compilerArgs = compilerArgs.clone()
clone.optimization = optimization.clone()
clone.exeArgs = exeArgs.clone()
return clone
}
override fun getConfigurables(): List<ZigConfigurable<*>> {
return super.getConfigurables() + listOf(filePath, optimization, compilerArgs, exeArgs)
}
override fun getState(executor: Executor, environment: ExecutionEnvironment): ZigProfileState<ZigExecConfigRun> {
return ZigProfileStateRun(environment, this)
}
}

View file

@ -0,0 +1,50 @@
/*
* 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.run
import com.falsepattern.zigbrains.project.execution.base.ZigTopLevelLineMarker
import com.falsepattern.zigbrains.zig.psi.ZigTypes
import com.intellij.icons.AllIcons.RunConfigurations.TestState
import com.intellij.psi.PsiElement
import com.intellij.psi.util.elementType
import javax.swing.Icon
class ZigLineMarkerRun: ZigTopLevelLineMarker() {
override fun getDeclaration(element: PsiElement): PsiElement? {
if (element.elementType != ZigTypes.IDENTIFIER)
return null
if (!element.textMatches("main"))
return null
val parent = element.parent
if (parent.elementType != ZigTypes.FN_PROTO)
return null
return parent.parent
}
override fun getIcon(element: PsiElement): Icon {
return TestState.Run
}
}

View file

@ -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.project.execution.run
import com.falsepattern.zigbrains.project.execution.base.ZigProfileState
import com.intellij.execution.runners.ExecutionEnvironment
class ZigProfileStateRun(environment: ExecutionEnvironment, configuration: ZigExecConfigRun) : ZigProfileState<ZigExecConfigRun>(environment, configuration)

Some files were not shown because too many files have changed in this diff Show more