aboutsummaryrefslogtreecommitdiff
path: root/Zig.novaextension
diff options
context:
space:
mode:
Diffstat (limited to 'Zig.novaextension')
-rw-r--r--Zig.novaextension/CHANGELOG.md24
-rw-r--r--Zig.novaextension/Images/filetype-script-zig/filetype-script-zig.pngbin0 -> 2890 bytes
-rw-r--r--Zig.novaextension/Images/filetype-script-zig/filetype-script-zig@2x.pngbin0 -> 2983 bytes
-rw-r--r--Zig.novaextension/Images/zig-build/zig-build.pngbin0 -> 1957 bytes
-rw-r--r--Zig.novaextension/Images/zig-build/zig-build@2x.pngbin0 -> 4955 bytes
-rw-r--r--Zig.novaextension/Images/zig-hex/zig-hex.pngbin0 -> 3070 bytes
-rw-r--r--Zig.novaextension/Images/zig-hex/zig-hex@2x.pngbin0 -> 3023 bytes
-rw-r--r--Zig.novaextension/Images/zig-mark/metadata.json3
-rw-r--r--Zig.novaextension/Images/zig-mark/zig-mark.pngbin0 -> 1941 bytes
-rw-r--r--Zig.novaextension/Images/zig-mark/zig-mark@2x.pngbin0 -> 1199 bytes
-rw-r--r--Zig.novaextension/Images/zig-script/zig-script.pngbin0 -> 1521 bytes
-rw-r--r--Zig.novaextension/Images/zig-script/zig-script@2x.pngbin0 -> 1506 bytes
-rw-r--r--Zig.novaextension/Images/zig-task/zig-task.pngbin0 -> 1773 bytes
-rw-r--r--Zig.novaextension/Images/zig-task/zig-task@2x.pngbin0 -> 4339 bytes
-rw-r--r--Zig.novaextension/LICENSE29
-rw-r--r--Zig.novaextension/MANUAL.md656
-rw-r--r--Zig.novaextension/NOTICES.md66
-rw-r--r--Zig.novaextension/Queries/arguments.scm3
-rw-r--r--Zig.novaextension/Queries/folds.scm22
-rw-r--r--Zig.novaextension/Queries/highlights.scm123
-rw-r--r--Zig.novaextension/Queries/symbols.scm74
-rw-r--r--Zig.novaextension/README.md118
-rw-r--r--Zig.novaextension/Resources/Zig@32px.afbin0 -> 64503 bytes
-rwxr-xr-xZig.novaextension/Scripts/build-parser.sh53
-rw-r--r--Zig.novaextension/Scripts/lldb-dap-proxy.pl224
-rw-r--r--Zig.novaextension/Scripts/main.js1583
-rw-r--r--Zig.novaextension/Scripts/normalize-zig-issues.pl93
-rwxr-xr-xZig.novaextension/Scripts/update-parser.sh64
-rw-r--r--Zig.novaextension/Syntaxes/Zig.xml55
-rwxr-xr-xZig.novaextension/Syntaxes/libtree-sitter-zig.dylibbin0 -> 1495312 bytes
-rw-r--r--Zig.novaextension/de.lproj/strings.json149
-rw-r--r--Zig.novaextension/en.lproj/strings.json144
-rw-r--r--Zig.novaextension/extension.json634
-rw-r--r--Zig.novaextension/extension.pngbin0 -> 5825 bytes
-rw-r--r--Zig.novaextension/extension@2x.pngbin0 -> 12092 bytes
35 files changed, 4117 insertions, 0 deletions
diff --git a/Zig.novaextension/CHANGELOG.md b/Zig.novaextension/CHANGELOG.md
new file mode 100644
index 0000000..3786150
--- /dev/null
+++ b/Zig.novaextension/CHANGELOG.md
@@ -0,0 +1,24 @@
+# Changelog
+
+## Unreleased
+
+- Tasks now expose first-class config for `-Doptimize`, `-Dtarget`, and custom `-D<key>=<value>` user options on every template that takes them.
+- Added **Zig Test** template (`zig build test` with `--test-filter` and `--summary`).
+- Added **Zig Watch** template (`zig build --watch`, with `--debounce` and `-fincremental` controls). Note: Nova issue matchers only fire on the first build cycle.
+- **Zig Package** gained a Console setting (Internal / External Terminal). The standalone *Zig Package (macOS Terminal)* template has been removed.
+- Dynamic per-step tasks: each step from `zig build --list-steps` appears as `Zig Build: <step>` in the task list. Cached and refreshed on `build.zig` / `build.zig.zon` changes.
+- **Zig Debug** auto-detects `programPath` from `build.zig.zon`'s `.name` and the `zig-out/bin/<name>` location when the field is left blank.
+- The Run Step default flips from `run` to empty — `zig build` then runs its own default install step. Clear and re-set the field on existing tasks to use the new behavior.
+- Clean now refuses to run when the working directory is `/`, `$HOME`, or outside the workspace, and runs `zig build uninstall` first when the project exposes that step.
+- "Current Zig File" Clean walks up to the nearest `build.zig` instead of cleaning the file's directory.
+- Issue assistant no longer registers for the non-existent `zig-package` syntax.
+## 0.1.8 — 2026-05-07
+
+- Added a setting to write an LLDB proxy log file for debugging the debug adapter.
+- Improved ZLS error logging in the Extension Console.
+- Updated German localisation.
+- Clarified third-party licences for bundled assets and grammar.
+
+## 0.1.7 — 2026-05-07
+
+- Initial release: syntax highlighting, ZLS integration, task templates, and LLDB debugging.
diff --git a/Zig.novaextension/Images/filetype-script-zig/filetype-script-zig.png b/Zig.novaextension/Images/filetype-script-zig/filetype-script-zig.png
new file mode 100644
index 0000000..19ae2b8
--- /dev/null
+++ b/Zig.novaextension/Images/filetype-script-zig/filetype-script-zig.png
Binary files differ
diff --git a/Zig.novaextension/Images/filetype-script-zig/filetype-script-zig@2x.png b/Zig.novaextension/Images/filetype-script-zig/filetype-script-zig@2x.png
new file mode 100644
index 0000000..44f033f
--- /dev/null
+++ b/Zig.novaextension/Images/filetype-script-zig/filetype-script-zig@2x.png
Binary files differ
diff --git a/Zig.novaextension/Images/zig-build/zig-build.png b/Zig.novaextension/Images/zig-build/zig-build.png
new file mode 100644
index 0000000..f0b86c8
--- /dev/null
+++ b/Zig.novaextension/Images/zig-build/zig-build.png
Binary files differ
diff --git a/Zig.novaextension/Images/zig-build/zig-build@2x.png b/Zig.novaextension/Images/zig-build/zig-build@2x.png
new file mode 100644
index 0000000..437c1f9
--- /dev/null
+++ b/Zig.novaextension/Images/zig-build/zig-build@2x.png
Binary files differ
diff --git a/Zig.novaextension/Images/zig-hex/zig-hex.png b/Zig.novaextension/Images/zig-hex/zig-hex.png
new file mode 100644
index 0000000..da18a18
--- /dev/null
+++ b/Zig.novaextension/Images/zig-hex/zig-hex.png
Binary files differ
diff --git a/Zig.novaextension/Images/zig-hex/zig-hex@2x.png b/Zig.novaextension/Images/zig-hex/zig-hex@2x.png
new file mode 100644
index 0000000..911219e
--- /dev/null
+++ b/Zig.novaextension/Images/zig-hex/zig-hex@2x.png
Binary files differ
diff --git a/Zig.novaextension/Images/zig-mark/metadata.json b/Zig.novaextension/Images/zig-mark/metadata.json
new file mode 100644
index 0000000..8d164cd
--- /dev/null
+++ b/Zig.novaextension/Images/zig-mark/metadata.json
@@ -0,0 +1,3 @@
+{
+ "template": true
+}
diff --git a/Zig.novaextension/Images/zig-mark/zig-mark.png b/Zig.novaextension/Images/zig-mark/zig-mark.png
new file mode 100644
index 0000000..903a55a
--- /dev/null
+++ b/Zig.novaextension/Images/zig-mark/zig-mark.png
Binary files differ
diff --git a/Zig.novaextension/Images/zig-mark/zig-mark@2x.png b/Zig.novaextension/Images/zig-mark/zig-mark@2x.png
new file mode 100644
index 0000000..e7a8454
--- /dev/null
+++ b/Zig.novaextension/Images/zig-mark/zig-mark@2x.png
Binary files differ
diff --git a/Zig.novaextension/Images/zig-script/zig-script.png b/Zig.novaextension/Images/zig-script/zig-script.png
new file mode 100644
index 0000000..bec1c9f
--- /dev/null
+++ b/Zig.novaextension/Images/zig-script/zig-script.png
Binary files differ
diff --git a/Zig.novaextension/Images/zig-script/zig-script@2x.png b/Zig.novaextension/Images/zig-script/zig-script@2x.png
new file mode 100644
index 0000000..b436080
--- /dev/null
+++ b/Zig.novaextension/Images/zig-script/zig-script@2x.png
Binary files differ
diff --git a/Zig.novaextension/Images/zig-task/zig-task.png b/Zig.novaextension/Images/zig-task/zig-task.png
new file mode 100644
index 0000000..adbed18
--- /dev/null
+++ b/Zig.novaextension/Images/zig-task/zig-task.png
Binary files differ
diff --git a/Zig.novaextension/Images/zig-task/zig-task@2x.png b/Zig.novaextension/Images/zig-task/zig-task@2x.png
new file mode 100644
index 0000000..21f056e
--- /dev/null
+++ b/Zig.novaextension/Images/zig-task/zig-task@2x.png
Binary files differ
diff --git a/Zig.novaextension/LICENSE b/Zig.novaextension/LICENSE
new file mode 100644
index 0000000..052ff1c
--- /dev/null
+++ b/Zig.novaextension/LICENSE
@@ -0,0 +1,29 @@
+BSD 2-Clause License
+
+Copyright 2026 David Czihak
+
+This license applies to the source code of this extension. Bundled image
+and icon assets, and the vendored Tree-sitter Zig grammar, are covered
+by separate licenses; see NOTICES.md for details.
+
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and / or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Zig.novaextension/MANUAL.md b/Zig.novaextension/MANUAL.md
new file mode 100644
index 0000000..27a555e
--- /dev/null
+++ b/Zig.novaextension/MANUAL.md
@@ -0,0 +1,656 @@
+# Zig for Nova — User Manual
+
+This manual covers the task system. For installation, language-server, and
+debugging prerequisites see [README.md](README.md).
+
+---
+
+## Table of Contents
+
+- [Overview](#overview)
+- [Creating a task](#creating-a-task)
+- [Common options](#common-options)
+ - [Working Directory](#working-directory)
+ - [Optimize](#optimize)
+ - [Target](#target)
+ - [User Options](#user-options)
+ - [Build Arguments](#build-arguments)
+ - [Program Arguments](#program-arguments)
+- [Task templates](#task-templates)
+ - [Zig Package](#zig-package)
+ - [Zig Debug](#zig-debug)
+ - [Zig Test](#zig-test)
+ - [Zig Watch](#zig-watch)
+- [Automatic tasks](#automatic-tasks)
+ - [Current Zig File](#current-zig-file)
+ - [Discovered build steps](#discovered-build-steps)
+- [Recipes](#recipes)
+ - [Run a single executable](#recipe-run-executable)
+ - [Run with a custom step name](#recipe-custom-step)
+ - [Cross-compile to a different target](#recipe-cross-compile)
+ - [Pass custom -D flags](#recipe-d-flags)
+ - [Run specific tests](#recipe-tests)
+ - [Ziglings-style projects](#recipe-ziglings)
+ - [Debug a binary that isn't in zig-out/bin](#recipe-debug)
+- [Clean action](#clean-action)
+- [Inline issue reporting](#inline-issues)
+- [Settings reference](#settings)
+
+---
+
+<a id="overview"></a>
+## Overview
+
+The extension integrates with `zig build` at three levels:
+
+| Kind | How it appears | Configurable | Persistent |
+|---|---|---|---|
+| **Task template** | Added by you in Project Settings | Yes — full config UI | Yes — saved in `.nova/` |
+| **Current Zig File** | Always present automatically | No | No |
+| **Discovered step** | One per step in `zig build --list-steps` | No | No |
+
+Task templates are the right tool when you need to control optimize mode,
+target, test filters, or program arguments. Automatic tasks are zero-config
+quick-launchers.
+
+---
+
+<a id="creating-a-task"></a>
+## Creating a task
+
+1. Open **Project Settings** (`⌘,` with a project open, or **Project →
+ Project Settings…**).
+2. Select the **Tasks** tab.
+3. Click **+** and choose a template from the Zig section.
+4. Fill in the options and close the panel — Nova saves the task automatically.
+
+Each task exposes up to three action slots that map to Nova's toolbar buttons:
+
+| Slot | Toolbar button | What it does |
+|---|---|---|
+| **Build** | ⌘B | Compiles without running |
+| **Run** | ▶ | Builds (if needed) and runs |
+| **Clean** | Broom icon | Removes build artifacts |
+
+---
+
+<a id="common-options"></a>
+## Common options
+
+Several options appear in more than one template. They are documented once
+here and referenced from each template section.
+
+<a id="working-directory"></a>
+### Working Directory
+
+| Field | Default |
+|---|---|
+| Working Directory | Workspace root |
+
+The directory from which `zig build` is invoked. Relative paths in other
+options are resolved against this directory. Leave blank to use the workspace
+root.
+
+Change this for monorepos or sub-package layouts where `build.zig` lives
+below the workspace root.
+
+<a id="optimize"></a>
+### Optimize
+
+| Value | Flag emitted | When to use |
+|---|---|---|
+| **Project Default** | *(nothing)* | Let `build.zig` decide (most projects default to `Debug`) |
+| **Debug** | `-Doptimize=Debug` | Development, debugging |
+| **ReleaseSafe** | `-Doptimize=ReleaseSafe` | Production with safety checks |
+| **ReleaseFast** | `-Doptimize=ReleaseFast` | Maximum performance |
+| **ReleaseSmall** | `-Doptimize=ReleaseSmall` | Smallest binary size |
+
+The flag is inserted immediately after `zig build`:
+```
+zig build -Doptimize=ReleaseFast …
+```
+
+<a id="target"></a>
+### Target
+
+| Field | Default |
+|---|---|
+| Target | *(host)* |
+
+A Zig target triple such as `aarch64-macos`, `x86_64-linux-gnu`, or
+`wasm32-wasi`. Passed as `-Dtarget=<value>`. Leave blank to compile for the
+machine you are running on.
+
+```
+zig build -Dtarget=aarch64-macos …
+```
+
+See `zig targets` for the full list of supported triples.
+
+<a id="user-options"></a>
+### User Options
+
+A list of custom `-D` flags defined by the project's `build.zig`. Each entry
+can be either:
+
+- `key` — emits `-Dkey` (boolean `true`)
+- `key=value` — emits `-Dkey=value`
+
+Example entries and the flags they produce:
+
+| Entry | Flag |
+|---|---|
+| `verbose` | `-Dverbose` |
+| `n=42` | `-Dn=42` |
+| `healed-path=patches/healed` | `-Dhealed-path=patches/healed` |
+
+Entries that do not match the pattern `[A-Za-z_][A-Za-z0-9_-]*(=.*)?` are
+silently skipped with a warning in the Extension Console.
+
+<a id="build-arguments"></a>
+### Build Arguments
+
+Free-form arguments appended after the `-D` flags but before the step name.
+Use this for `zig build` flags that are not exposed as dedicated fields, such
+as `--summary all`, `--verbose`, or `-j4`.
+
+```
+zig build [-Doptimize=…] [-Dtarget=…] [-D…] <Build Arguments> [step] …
+```
+
+<a id="program-arguments"></a>
+### Program Arguments
+
+Arguments passed to the compiled program after `--`:
+
+```
+zig build … run -- <Program Arguments>
+```
+
+For the **Zig Debug** template these are forwarded to the debugger's launch
+request instead.
+
+---
+
+<a id="task-templates"></a>
+## Task templates
+
+<a id="zig-package"></a>
+### Zig Package
+
+> **Build → Run → Clean** a Zig package from a single task configuration.
+
+**When to use:** everyday development — building, running, and cleaning a
+package whose output is an executable.
+
+#### Actions
+
+| Action | Command |
+|---|---|
+| Build | `zig build [flags…]` |
+| Run | `zig build [flags…] [step] [-- program-args]` |
+| Clean | removes `.zig-cache`, `zig-cache`, `zig-out` |
+
+The Build action compiles without running (equivalent to `zig build` with no
+run step). The Run action builds *and* runs in a single `zig build` invocation
+— Nova does not chain them; `zig build` handles both internally.
+
+#### Options
+
+| Option | Type | Default | Description |
+|---|---|---|---|
+| Working Directory | path | workspace root | See [Working Directory](#working-directory) |
+| Run Step | string | *(blank)* | Step name for the Run action. See below. |
+| Optimize | enum | Project Default | See [Optimize](#optimize) |
+| Target | string | host | See [Target](#target) |
+| User Options | string list | — | See [User Options](#user-options) |
+| Build Arguments | string list | — | See [Build Arguments](#build-arguments) |
+| Program Arguments | string list | — | See [Program Arguments](#program-arguments) |
+| Console | enum | Internal Console | Where to run the program |
+
+#### Run Step
+
+The step name appended to `zig build` for the Run action. Common values:
+
+| Value | Command produced | When |
+|---|---|---|
+| *(blank)* | `zig build` | Default install step — artifacts land in `zig-out/` |
+| `run` | `zig build run` | Project exposes a `run` step |
+| `serve` | `zig build serve` | Any custom step name |
+
+Leave blank if the project uses `-D` flags instead of a step name (Ziglings
+style — see [Recipes](#recipes)).
+
+#### Console
+
+| Value | Behaviour |
+|---|---|
+| **Internal Console** | Output appears in Nova's built-in console panel |
+| **External Terminal** | Opens macOS Terminal.app in a new window or tab |
+
+Use External Terminal for interactive programs that read from stdin.
+
+---
+
+<a id="zig-debug"></a>
+### Zig Debug
+
+> **Build → Debug** a Zig executable under lldb-dap.
+
+**When to use:** stepping through code, setting breakpoints, inspecting
+variables.
+
+The Run action always triggers a Build first (`buildBeforeRunning`), so the
+debugger always launches the freshest binary.
+
+Requires `lldb-dap`, discovered automatically via `xcrun` or `PATH`. Install
+the Xcode Command Line Tools if it is missing: `xcode-select --install`.
+
+#### Actions
+
+| Action | Command |
+|---|---|
+| Build | `zig build -Doptimize=<mode> [flags…]` |
+| Run | launches lldb-dap (build runs first automatically) |
+| Clean | removes `.zig-cache`, `zig-cache`, `zig-out` |
+
+#### Options
+
+| Option | Type | Default | Description |
+|---|---|---|---|
+| Working Directory | path | workspace root | See [Working Directory](#working-directory) |
+| Program | path | *(auto-detected)* | Path to the executable to debug. See below. |
+| Optimize | enum | **Debug** | See [Optimize](#optimize) |
+| Target | string | host | See [Target](#target) |
+| User Options | string list | — | See [User Options](#user-options) |
+| Build Arguments | string list | — | See [Build Arguments](#build-arguments) |
+| Program Arguments | string list | — | Forwarded to the debugged process |
+| Console | enum | Internal Console | Where the debugged process runs |
+| Stop On Entry | boolean | off | Pause at the very first instruction |
+
+#### Program path auto-detection
+
+When the **Program** field is blank, the extension reads `build.zig.zon` and
+extracts the package `.name`. It then probes `zig-out/bin/<name>` relative to
+the working directory. If the file exists it is used automatically; otherwise
+the task shows a warning asking you to fill in the field manually.
+
+This works for the common case of a single executable whose name matches the
+package name. For other layouts — multiple executables, custom install
+prefixes, library packages — set the path explicitly.
+
+#### Console (debug)
+
+| Value | Where the debugged process runs |
+|---|---|
+| **Internal Console** | Nova's built-in console (default) |
+| **Integrated Terminal** | Nova's integrated terminal |
+| **External Terminal** | macOS Terminal.app |
+
+---
+
+<a id="zig-test"></a>
+### Zig Test
+
+> **Build → Run → Clean** tests via `zig build test`.
+
+**When to use:** running the project's test suite, optionally filtered to a
+subset of tests.
+
+#### Actions
+
+| Action | Command |
+|---|---|
+| Build | `zig build test [flags…]` |
+| Run | `zig build test [flags…] [--summary=…] [--test-filter …] [-- runner-args]` |
+| Clean | removes `.zig-cache`, `zig-cache`, `zig-out` |
+
+Both Build and Run invoke `zig build test`. The distinction is that the Run
+action additionally applies the Test Filter and Summary fields.
+
+Test failures surface as inline issues in the editor via the same
+`file:line:col: error:` pattern used for compiler errors.
+
+#### Options
+
+| Option | Type | Default | Description |
+|---|---|---|---|
+| Working Directory | path | workspace root | See [Working Directory](#working-directory) |
+| Test Filter | string | — | Substring filter on test names. See below. |
+| Summary | enum | Default | Verbosity of `--summary`. See below. |
+| Optimize | enum | Project Default | See [Optimize](#optimize) |
+| Target | string | host | See [Target](#target) |
+| User Options | string list | — | See [User Options](#user-options) |
+| Build Arguments | string list | — | Appended after `zig build test` |
+| Program Arguments | string list | — | Passed after `--` to the test runner |
+
+#### Test Filter
+
+A substring of the test name. Only tests whose names contain this string are
+run. Maps directly to `zig build`'s `--test-filter` flag.
+
+```
+zig build test --test-filter "parser"
+```
+
+Leave blank to run all tests.
+
+#### Summary
+
+Controls how much output `zig build` prints about the test run.
+
+| Value | Flag | Output |
+|---|---|---|
+| **Default** | *(nothing)* | Zig's built-in default |
+| **All** | `--summary=all` | Every step result |
+| **Failures only** | `--summary=failures` | Only failed steps |
+| **None** | `--summary=none` | Silent on success |
+
+---
+
+<a id="zig-watch"></a>
+### Zig Watch
+
+> **Build** with `zig build --watch`, rebuilding automatically on file changes.
+
+**When to use:** tight edit–compile loops where you want continuous feedback
+without manually re-triggering the Build action.
+
+> **⚠ Known limitation:** Nova's issue matchers fire only on the first build
+> cycle. Errors found in subsequent automatic rebuilds will not appear as
+> inline annotations in the editor. Re-run the task (stop and start again) to
+> refresh the issue overlay.
+
+#### Actions
+
+| Action | Command |
+|---|---|
+| Build | `zig build [step] --watch [--debounce N] [-fincremental] [flags…]` |
+| Clean | removes `.zig-cache`, `zig-cache`, `zig-out` |
+
+There is no Run action — `zig build --watch` manages the build loop itself.
+
+#### Options
+
+| Option | Type | Default | Description |
+|---|---|---|---|
+| Working Directory | path | workspace root | See [Working Directory](#working-directory) |
+| Step | string | *(blank)* | Step to watch. Leave blank for Zig's default install step. |
+| Debounce (ms) | number | *(Zig default)* | Milliseconds to wait after a change before rebuilding. |
+| Incremental | enum | Default | Force incremental compilation on or off. |
+| Optimize | enum | Project Default | See [Optimize](#optimize) |
+| Target | string | host | See [Target](#target) |
+| User Options | string list | — | See [User Options](#user-options) |
+| Build Arguments | string list | — | See [Build Arguments](#build-arguments) |
+
+#### Debounce
+
+Passed as `--debounce <N>` to `zig build`. Controls how long Zig waits after
+detecting a file change before starting a rebuild. Useful on projects with
+slow file-system events or many simultaneous saves. Leave blank to use Zig's
+built-in default.
+
+#### Incremental
+
+| Value | Flag | Effect |
+|---|---|---|
+| **Default** | *(nothing)* | Zig decides |
+| **On** | `-fincremental` | Force incremental compilation on |
+| **Off** | `-fno-incremental` | Force incremental compilation off |
+
+---
+
+<a id="automatic-tasks"></a>
+## Automatic tasks
+
+Automatic tasks appear in the task list without any configuration. They cannot
+be edited and are not saved in project settings.
+
+<a id="current-zig-file"></a>
+### Current Zig File
+
+Always present. Targets whichever `.zig` file is currently focused in the
+editor.
+
+| Action | Command |
+|---|---|
+| Run | `zig run <active-file>` (in the file's directory) |
+| Clean | removes `.zig-cache`, `zig-cache`, `zig-out` from the nearest ancestor directory that contains a `build.zig` |
+
+The Clean action walks up the directory tree from the active file until it
+finds a `build.zig`, then cleans that project root. If no `build.zig` is
+found above the file, it falls back to the workspace root.
+
+**Note:** there is no Build action — `zig run` compiles and runs in one step.
+
+---
+
+<a id="discovered-build-steps"></a>
+### Discovered build steps
+
+When the workspace contains a `build.zig`, the extension runs
+`zig build --list-steps` in the background and creates one task per step:
+
+```
+Zig Build: install
+Zig Build: run
+Zig Build: test
+Zig Build: bench
+…
+```
+
+Each task has a single **Run** action that invokes `zig build <step>` in the
+workspace root with no extra flags.
+
+#### How discovery works
+
+1. On workspace open (or task-list refresh), the extension stats `build.zig`
+ and `build.zig.zon` to capture their modification times.
+2. It spawns `zig build --list-steps` asynchronously — the workspace is usable
+ immediately; discovered tasks appear once the command finishes.
+3. The result is cached. The cache is considered fresh if:
+ - Both `build.zig` and `build.zig.zon` have the same mtimes as when last
+ fetched, **and**
+ - The cache is less than 5 minutes old.
+4. When `build.zig` or `build.zig.zon` changes on disk, the cache is
+ invalidated and `--list-steps` is re-run automatically.
+
+#### What steps appear
+
+`zig build --list-steps` executes `build.zig` with no `-D` options, so only
+the steps registered unconditionally by the build script are visible. Steps
+that are registered only under a `-D` branch (such as Ziglings' `zigling` and
+`random` steps) do not appear unless that branch is taken — and it won't be,
+because the discovery command passes no flags.
+
+Steps whose names are not valid identifiers (`[A-Za-z_][A-Za-z0-9_-]*`) are
+silently filtered out.
+
+#### Disabling discovery
+
+Some projects run expensive logic at the top of `build()` that you may not
+want triggered on every workspace open. Discovery can be turned off:
+
+- **Globally:** Nova Preferences → Extensions → Zig → Tasks → uncheck
+ **Discover Build Steps**.
+- **Per workspace:** Project Settings → Zig → Tasks → uncheck **Discover Build
+ Steps** (overrides the global setting for this project only).
+
+When disabled, the `Zig Build: <step>` tasks disappear; the
+[Current Zig File](#current-zig-file) task and all configured templates are
+unaffected.
+
+---
+
+<a id="recipes"></a>
+## Recipes
+
+<a id="recipe-run-executable"></a>
+### Run a single executable
+
+Create a **Zig Package** task.
+
+- **Run Step:** `run` (or the name your `build.zig` uses for the run step)
+- Leave everything else at defaults.
+
+The Run (▶) button compiles and launches the program in Nova's console.
+
+<a id="recipe-custom-step"></a>
+### Run with a custom step name
+
+Same as above but change **Run Step** to whatever `b.step(…)` name your
+`build.zig` declares:
+
+```zig
+const serve_step = b.step("serve", "Start the HTTP server");
+```
+
+→ set **Run Step** to `serve`.
+
+<a id="recipe-cross-compile"></a>
+### Cross-compile to a different target
+
+Create a **Zig Package** task and set:
+
+- **Target:** `aarch64-linux-musl`
+- **Optimize:** `ReleaseSmall`
+
+The command produced will be:
+```
+zig build -Doptimize=ReleaseSmall -Dtarget=aarch64-linux-musl run
+```
+
+<a id="recipe-d-flags"></a>
+### Pass custom -D flags
+
+Your `build.zig` declares:
+
+```zig
+const verbose = b.option(bool, "verbose", "Enable verbose output") orelse false;
+const port = b.option(u16, "port", "Listening port") orelse 8080;
+```
+
+Add two entries to **User Options**:
+
+| Entry | Flag produced |
+|---|---|
+| `verbose` | `-Dverbose` |
+| `port=9000` | `-Dport=9000` |
+
+<a id="recipe-tests"></a>
+### Run specific tests
+
+Create a **Zig Test** task and set:
+
+- **Test Filter:** `parser` (runs any test whose name contains "parser")
+- **Summary:** Failures only (quieter output when most tests pass)
+
+<a id="recipe-ziglings"></a>
+### Ziglings-style projects
+
+Ziglings selects exercises with `-Dn=<number>` rather than a named step.
+
+For "run all exercises" use a **Zig Package** task with:
+- **Run Step:** *(blank)* — runs `zig build`, which hits the default `ziglings`
+ step
+
+For a single exercise use:
+- **Run Step:** *(blank)*
+- **User Options:** `n=42`
+
+This produces `zig build -Dn=42`.
+
+Alternatively, if you have step discovery enabled, **Zig Build: ziglings**
+appears automatically and covers the "run all" case with one click.
+
+<a id="recipe-debug"></a>
+### Debug a binary that isn't in zig-out/bin
+
+Create a **Zig Debug** task and set **Program** explicitly:
+
+- `zig-out/bin/my-tool` (relative to Working Directory)
+- or an absolute path
+
+When the field is left blank the extension checks `build.zig.zon` for the
+package name and probes `zig-out/bin/<name>`. If your binary name doesn't
+match the package name, set the path manually.
+
+---
+
+<a id="clean-action"></a>
+## Clean action
+
+Every template task includes a Clean action. It:
+
+1. Checks that the working directory is a real absolute path inside the current
+ workspace. Clean refuses to run if the path resolves to `/`, `$HOME`, or
+ anywhere outside the workspace root, and shows a warning instead.
+2. If the project's `build.zig` exposes an `uninstall` step (already in the
+ step discovery cache), runs `zig build uninstall` first.
+3. Removes `.zig-cache`, `zig-cache`, and `zig-out` with `rm -rf`.
+
+The [Current Zig File](#current-zig-file) automatic task's Clean action
+applies the same logic but targets the nearest ancestor directory that contains
+a `build.zig`, rather than a configured working directory.
+
+---
+
+<a id="inline-issues"></a>
+## Inline issue reporting
+
+Compiler and test errors are shown as inline annotations in the editor using
+the pattern:
+
+```
+<file>:<line>:<column>: error: <message>
+<file>:<line>:<column>: warning: <message>
+```
+
+This covers output from `zig build`, `zig build test`, `zig run`, `zig test`,
+and `zig fmt` (non-`--check` mode).
+
+`zig fmt --check` only prints filenames, not line numbers, so it does not
+produce inline annotations.
+
+For the **Zig Watch** template, annotations are populated from the first build
+cycle only. Subsequent automatic rebuilds do not update them — stop and restart
+the task to refresh.
+
+---
+
+<a id="settings"></a>
+## Settings reference
+
+Settings live in two places:
+
+- **Nova Preferences → Extensions → Zig** — global defaults for all projects.
+- **Project Settings → Zig** — workspace-specific overrides. These take
+ precedence over the global settings for the open project.
+
+### Tooling
+
+| Setting | Description |
+|---|---|
+| Zig Executable | Absolute path to `zig`. Leave blank to discover from `PATH`. |
+| ZLS Executable | Absolute path to `zls`. Leave blank to discover from `PATH`. |
+| LLDB DAP Executable | Absolute path to `lldb-dap`. Leave blank to discover via `xcrun` or `PATH`. |
+
+### Language Server
+
+| Setting | Default | Description |
+|---|---|---|
+| Enable ZLS | on | Enable diagnostics, completion, hover, go-to-definition, and formatting via ZLS. |
+| Build On Save | off | Allow ZLS to run its build/check runner when you save a file. |
+| Debug Server Messages | off | Log ZLS protocol traffic to the Extension Console (for troubleshooting). |
+
+### Debug Adapter
+
+| Setting | Default | Description |
+|---|---|---|
+| Enable Proxy Log | off | Write lldb-dap proxy traffic to a log file. For extension development only. |
+
+### Tasks
+
+| Setting | Default | Description |
+|---|---|---|
+| Discover Build Steps | on | Run `zig build --list-steps` and surface each step as a task. Disable for projects where executing `build.zig` on activation is undesirable. |
diff --git a/Zig.novaextension/NOTICES.md b/Zig.novaextension/NOTICES.md
new file mode 100644
index 0000000..3ee7700
--- /dev/null
+++ b/Zig.novaextension/NOTICES.md
@@ -0,0 +1,66 @@
+# Third-Party Notices
+
+This Nova extension bundles material under licenses different from the
+BSD 2-Clause license that covers its own source code (see `LICENSE`).
+Each section below identifies the affected files and the license that
+governs them.
+
+---
+
+## Zig Software Foundation logo — CC BY-SA 4.0
+
+The following image and icon assets are derivatives of the Zig
+programming language logo, which is the work of the
+[Zig Software Foundation](https://ziglang.org) and is licensed under
+[Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0)](https://creativecommons.org/licenses/by-sa/4.0/):
+
+- `extension.png`
+- `extension@2x.png`
+- `Images/zig-debug/zig-debug.png`
+- `Images/zig-debug/zig-debug@2x.png`
+- `Resources/Zig@32px.af`
+
+These assets remain licensed under CC BY-SA 4.0; redistribution must
+retain that license and provide attribution to the Zig Software
+Foundation. They are *not* covered by the BSD 2-Clause license that
+applies to the rest of this repository.
+
+The `@2x` variants and the `.af` icon were rasterised and pixel-aligned
+from the original logo; no other changes were made.
+
+---
+
+## Vendored tree-sitter-zig grammar — MIT
+
+The compiled parser shipped at `Syntaxes/libtree-sitter-zig.dylib` is
+built from the Tree-sitter grammar at
+[github.com/tree-sitter-grammars/tree-sitter-zig](https://github.com/tree-sitter-grammars/tree-sitter-zig).
+The pinned upstream commit is recorded in
+`vendor/tree-sitter-zig/VENDORING.md`.
+
+The grammar is distributed under the MIT License:
+
+> The MIT License (MIT)
+>
+> Copyright (c) 2024 Amaan Qureshi <amaanq12@gmail.com>
+>
+> Permission is hereby granted, free of charge, to any person obtaining a copy
+> of this software and associated documentation files (the "Software"), to deal
+> in the Software without restriction, including without limitation the rights
+> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+> copies of the Software, and to permit persons to whom the Software is
+> furnished to do so, subject to the following conditions:
+>
+> The above copyright notice and this permission notice shall be included in all
+> copies or substantial portions of the Software.
+>
+> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+> SOFTWARE.
+
+The unmodified upstream copy of this license is preserved at
+`vendor/tree-sitter-zig/LICENSE`.
diff --git a/Zig.novaextension/Queries/arguments.scm b/Zig.novaextension/Queries/arguments.scm
new file mode 100644
index 0000000..7824dba
--- /dev/null
+++ b/Zig.novaextension/Queries/arguments.scm
@@ -0,0 +1,3 @@
+(parameter
+ name: (identifier) @name
+ type: (_) @type)
diff --git a/Zig.novaextension/Queries/folds.scm b/Zig.novaextension/Queries/folds.scm
new file mode 100644
index 0000000..2275e5f
--- /dev/null
+++ b/Zig.novaextension/Queries/folds.scm
@@ -0,0 +1,22 @@
+[
+ (block)
+ (switch_expression)
+ (initializer_list)
+ (asm_expression)
+ (multiline_string)
+ (if_statement)
+ (while_statement)
+ (for_statement)
+ (if_expression)
+ (else_clause)
+ (for_expression)
+ (while_expression)
+ (if_type_expression)
+ (function_signature)
+ (parameters)
+ (struct_declaration)
+ (opaque_declaration)
+ (enum_declaration)
+ (union_declaration)
+ (error_set_declaration)
+] @subtree
diff --git a/Zig.novaextension/Queries/highlights.scm b/Zig.novaextension/Queries/highlights.scm
new file mode 100644
index 0000000..b366bc7
--- /dev/null
+++ b/Zig.novaextension/Queries/highlights.scm
@@ -0,0 +1,123 @@
+(comment) @comment
+
+[
+ (string)
+ (multiline_string)
+ (character)
+] @string
+
+[
+ (integer)
+ (float)
+] @value.number
+
+[
+ "true"
+ "false"
+] @value.boolean
+
+[
+ "null"
+ "undefined"
+ "unreachable"
+] @value.null
+
+(builtin_type) @identifier.type
+(builtin_identifier) @identifier.core
+
+((builtin_identifier) @processing
+ (#match? @processing "^@(import|cImport)$"))
+
+((identifier) @identifier.core
+ (#eq? @identifier.core "_"))
+
+(parameter
+ name: (identifier) @identifier.argument)
+
+(payload
+ (identifier) @identifier.argument)
+
+(field_expression
+ member: (identifier) @identifier.property)
+
+(field_initializer
+ (identifier) @identifier.property)
+
+(function_declaration
+ name: (identifier) @identifier.function)
+
+(call_expression
+ function: (identifier) @identifier.function)
+
+(call_expression
+ function: (field_expression
+ member: (identifier) @identifier.function))
+
+((identifier) @identifier.type
+ (#match? @identifier.type "^[A-Z][A-Za-z0-9_]*$"))
+
+((identifier) @identifier.constant
+ (#match? @identifier.constant "^[A-Z][A-Z0-9_]+$"))
+
+[
+ "asm"
+ "const"
+ "defer"
+ "errdefer"
+ "error"
+ "return"
+ "test"
+ "var"
+] @keyword
+
+[
+ "struct"
+ "union"
+ "enum"
+ "opaque"
+ "fn"
+] @keyword.construct
+
+[
+ "if"
+ "else"
+ "switch"
+ "for"
+ "while"
+ "try"
+ "catch"
+ "break"
+ "continue"
+] @keyword.condition
+
+[
+ "usingnamespace"
+ "export"
+ "comptime"
+ "inline"
+ "noinline"
+ "extern"
+ "pub"
+ "packed"
+ "threadlocal"
+ "volatile"
+ "allowzero"
+ "noalias"
+ "addrspace"
+ "align"
+ "callconv"
+ "linksection"
+ "async"
+ "await"
+ "suspend"
+ "nosuspend"
+ "resume"
+] @keyword.modifier
+
+[
+ "and"
+ "or"
+ "orelse"
+] @keyword.operator
+
+(ERROR) @invalid
diff --git a/Zig.novaextension/Queries/symbols.scm b/Zig.novaextension/Queries/symbols.scm
new file mode 100644
index 0000000..5f903cb
--- /dev/null
+++ b/Zig.novaextension/Queries/symbols.scm
@@ -0,0 +1,74 @@
+(
+ (function_declaration
+ name: (identifier) @name
+ (parameters) @arguments.target) @subtree
+ (#set! role function-or-method)
+ (#set! arguments.query "arguments.scm")
+)
+
+(
+ (variable_declaration
+ (identifier) @name
+ (struct_declaration)) @subtree
+ (#set! role struct)
+)
+
+(
+ (variable_declaration
+ (identifier) @name
+ (enum_declaration)) @subtree
+ (#set! role enum)
+)
+
+(
+ (variable_declaration
+ (identifier) @name
+ (union_declaration)) @subtree
+ (#set! role struct)
+)
+
+(
+ (variable_declaration
+ (identifier) @name
+ (opaque_declaration)) @subtree
+ (#set! role struct)
+)
+
+(
+ (variable_declaration
+ (identifier) @name
+ (error_set_declaration)) @subtree
+ (#set! role enum)
+)
+
+(
+ (container_field
+ name: (identifier) @name) @subtree
+ (#set! role property)
+)
+
+(
+ (container_field
+ name: (primary_type_expression
+ (identifier) @name)) @subtree
+ (#set! role property)
+)
+
+(
+ (test_declaration
+ (string) @name @displayname) @subtree
+ (#set! role function)
+)
+
+(
+ (test_declaration
+ (identifier) @name @displayname) @subtree
+ (#set! role function)
+)
+
+(
+ (block
+ (variable_declaration
+ (identifier) @name) @subtree)
+ (#set! role variable)
+)
diff --git a/Zig.novaextension/README.md b/Zig.novaextension/README.md
new file mode 100644
index 0000000..3741954
--- /dev/null
+++ b/Zig.novaextension/README.md
@@ -0,0 +1,118 @@
+# Zig for Nova
+
+Zig language support – ZLS, LLDB, Tree-Sitter grammar
+
+## Requirements
+
+- [Zig](https://ziglang.org/download/) installed and on `PATH` (or configured in extension preferences)
+- [ZLS](https://github.com/zigtools/zls)\* for language intelligence
+- [LLDB DAP](https://lldb.llvm.org/use/lldbdap.html)\* for debugging (discovered automatically via `xcrun` or `PATH`; install the Xcode Command Line Tools if absent: `xcode-select --install`)
+
+\* optional but recommended for full experience
+
+## Why
+
+A personal project to learn Zig without leaving Nova and without giving up the comfort of language server and debugging features.
+
+This extension is not endorsed by, affiliated with, or associated with the [Zig Software Foundation](https://ziglang.org/zsf/) or the Zig core team in any way. It is maintained by an independent person, for fun.
+
+## Features
+
+### Editing
+
+- Syntax highlighting for `.zig` and `.zon`
+- Code folding for blocks, declarations, and control-flow expressions
+- Document symbols (outline, breadcrumbs, Go to Symbol)
+- Diagnostics, completion, hover, go to definition, find references, select all references, code actions, formatting (all via ZLS)
+- Inline compiler errors from `zig build`
+
+### Tasks
+
+Four templates expose the `zig build` surface — pick one in **Project Settings > Tasks > New Task**:
+
+- **Zig Package**: build and run a Zig package. Configurable optimize mode, target triple, custom `-D` user options, run step, build/program arguments, and console (Internal Console or External Terminal).
+- **Zig Debug**: build and launch under lldb-dap. Same flag surface as Zig Package, plus program path (auto-detected from `build.zig.zon` when blank), console, and stop-on-entry.
+- **Zig Test**: run `zig build test` with optional `--test-filter`, `--summary`, plus optimize/target/user-options.
+- **Zig Watch**: run `zig build --watch` with configurable step, debounce, and incremental flag.
+
+Every runnable template also exposes a clean action that removes `.zig-cache`, `zig-cache`, and `zig-out`. Clean refuses to run when the working directory resolves to `/`, `$HOME`, or anywhere outside the workspace; when the project's `build.zig` exposes an `uninstall` step, it runs first.
+
+Two ad-hoc tasks appear automatically:
+
+- **Current Zig File**: run via `zig run <file>` and clean the nearest `build.zig` ancestor.
+- **Zig Build: \<step\>**: one task per step discovered from `zig build --list-steps`. The list is cached and refreshed when `build.zig` or `build.zig.zon` changes.
+
+The legacy **Zig Package (macOS Terminal)** template is preserved for backward compatibility. New configurations should use **Zig Package** with Console set to External Terminal.
+
+#### Watch-mode caveat
+
+Nova issue matchers fire only on the first build cycle of a long-running task. With `zig build --watch`, errors from later cycles will not appear inline — re-run the task to refresh the issue overlay.
+
+### Debugging
+
+- Breakpoint support in Zig source files
+- LLDB-based debug adapter, auto-discovered via `xcrun` or `PATH`
+
+## Configuration
+
+Global settings live under **Nova Preferences > Extensions > Zig**.
+Per-workspace overrides live under **Project Settings > Zig** and take precedence.
+
+### Tooling
+
+Paths to the `zig`, `zls`, and `lldb-dap` executables. Leave any blank to discover from `PATH`.
+
+### Language Server
+
+- Enable or disable ZLS
+- Allow ZLS to run its build/check on save
+- Log ZLS traffic to the Extension Console (for troubleshooting)
+
+## Known Issues
+
+### Auto-discovered tasks cannot open the report automatically
+
+The **Current Zig File** and **zig build \<step\>** tasks are provided programmatically by the extension. Nova’s extension API does not expose the report-opening setting for tasks created this way. Open the report manually or use a task template (**Project Settings > Tasks > New Task**) if automatic report opening matters.
+
+### A large part of the file turns red after one error
+
+The parser may report a broad recovery range when it hits an incomplete expression, and Nova highlights that entire range. Fix the first syntax error shown and the overlay should narrow to the real error locations.
+
+## Development
+
+The Tree-sitter parser is built from the vendored grammar snapshot under `vendor/tree-sitter-zig`. The pinned upstream commit is recorded in `vendor/tree-sitter-zig/VENDORING.md`.
+
+Rebuild the parser dylib:
+
+```sh
+./Scripts/build-parser.sh
+```
+
+Bump the vendored snapshot (and rebuild):
+
+```sh
+./Scripts/update-parser.sh # upstream HEAD
+./Scripts/update-parser.sh <ref> # specific tag, branch, or SHA
+```
+
+Validate the extension bundle:
+
+```sh
+/Applications/Nova.app/Contents/SharedSupport/nova extension validate .
+```
+
+## License
+
+The extension's source code is licensed under the BSD 2-Clause License
+(see [`LICENSE`](LICENSE)). Bundled assets and vendored third-party
+code carry their own licenses, documented in
+[`NOTICES.md`](NOTICES.md):
+
+- The Zig logo and the icon/image assets derived from it
+ (`extension.png`, `extension@2x.png`, `Images/zig-debug/*.png`,
+ `Resources/Zig@32px.af`) are the work of the
+ [Zig Software Foundation](https://ziglang.org) and remain licensed
+ under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/).
+- The Tree-sitter grammar for Zig is sourced from
+ [github.com/tree-sitter-grammars/tree-sitter-zig](https://github.com/tree-sitter-grammars/tree-sitter-zig)
+ and is distributed under the MIT License.
diff --git a/Zig.novaextension/Resources/Zig@32px.af b/Zig.novaextension/Resources/Zig@32px.af
new file mode 100644
index 0000000..1b8b1be
--- /dev/null
+++ b/Zig.novaextension/Resources/Zig@32px.af
Binary files differ
diff --git a/Zig.novaextension/Scripts/build-parser.sh b/Zig.novaextension/Scripts/build-parser.sh
new file mode 100755
index 0000000..5cd7039
--- /dev/null
+++ b/Zig.novaextension/Scripts/build-parser.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+#
+# build-parser.sh — Compile the Nova-compatible tree-sitter-zig parser.
+#
+# Purpose:
+# Build vendor/tree-sitter-zig/src/parser.c into a universal macOS dylib
+# that Nova loads via its private SyntaxKit framework.
+#
+# What it does:
+# - clang -dynamiclib for arm64 + x86_64
+# - links against Nova’s SyntaxKit framework (from /Applications/Nova.app)
+# - sets rpath @loader_path/../Frameworks so the dylib finds SyntaxKit
+# when Nova loads it from the bundled extension
+# - ad-hoc codesigns the dylib (Gatekeeper requirement)
+# - writes the result to Syntaxes/libtree-sitter-zig.dylib
+#
+# Usage:
+# ./Scripts/build-parser.sh
+#
+# Environment overrides:
+# NOVA_APP Path to Nova.app (default: /Applications/Nova.app)
+# SDKROOT macOS SDK path (default: `xcrun --show-sdk-path`)
+#
+# Requirements:
+# macOS, Xcode Command Line Tools (clang + xcrun), Nova installed.
+
+set -eu
+
+ROOT="$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)"
+VENDOR_DIR="$ROOT/vendor/tree-sitter-zig"
+BUILD_DIR="$ROOT/build"
+OUTPUT="$ROOT/Syntaxes/libtree-sitter-zig.dylib"
+NOVA_APP="${NOVA_APP:-/Applications/Nova.app}"
+SDKROOT="${SDKROOT:-$(xcrun --show-sdk-path)}"
+
+mkdir -p "$BUILD_DIR"
+
+clang \
+ -dynamiclib \
+ -O2 \
+ -fPIC \
+ -arch arm64 \
+ -arch x86_64 \
+ -isysroot "$SDKROOT" \
+ -I"$VENDOR_DIR/src" \
+ -F"$NOVA_APP/Contents/Frameworks" \
+ -framework SyntaxKit \
+ -Wl,-rpath,@loader_path/../Frameworks \
+ -o "$BUILD_DIR/libtree-sitter-zig.dylib" \
+ "$VENDOR_DIR/src/parser.c"
+
+codesign --force --sign - "$BUILD_DIR/libtree-sitter-zig.dylib"
+cp "$BUILD_DIR/libtree-sitter-zig.dylib" "$OUTPUT"
diff --git a/Zig.novaextension/Scripts/lldb-dap-proxy.pl b/Zig.novaextension/Scripts/lldb-dap-proxy.pl
new file mode 100644
index 0000000..beb6eaa
--- /dev/null
+++ b/Zig.novaextension/Scripts/lldb-dap-proxy.pl
@@ -0,0 +1,224 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Fcntl qw(O_WRONLY O_APPEND O_CREAT O_NOFOLLOW);
+use IPC::Open3;
+use IO::Select;
+use JSON::PP;
+use Symbol 'gensym';
+
+my $adapter_path = $ARGV[0] // $ENV{NOVA_ZIG_LLDB_DAP_PATH};
+my $log_path = $ARGV[1] // $ENV{NOVA_ZIG_DEBUG_LOG};
+my @adapter_args = @ARGV > 2 ? @ARGV[2 .. $#ARGV] : ();
+
+sub log_msg {
+ return unless defined $log_path;
+ my ($msg) = @_;
+ sysopen(my $fh, $log_path, O_WRONLY | O_APPEND | O_CREAT | O_NOFOLLOW, 0600) or return;
+ my @t = gmtime(time);
+ printf $fh "[%04d-%02d-%02dT%02d:%02d:%02dZ] %s\n",
+ $t[5] + 1900, $t[4] + 1, $t[3], $t[2], $t[1], $t[0], $msg;
+ close($fh);
+}
+
+unless (defined $adapter_path) {
+ log_msg('missing adapter path');
+ print STDERR "lldb-dap proxy requires a target adapter path.\n";
+ exit(1);
+}
+
+log_msg("proxy start adapter=$adapter_path");
+
+# open3 treats an undef handle as "inherit parent STDIN/STDOUT", so pre-allocate
+# anonymous typeglobs to force pipe creation for all three streams.
+my ($child_in, $child_out, $child_err) = (gensym(), gensym(), gensym());
+my $pid = eval { open3($child_in, $child_out, $child_err, $adapter_path, @adapter_args) };
+if ($@) {
+ (my $err = $@) =~ s/\s+$//;
+ log_msg("adapter error $err");
+ print STDERR "$err\n";
+ exit(1);
+}
+
+binmode($_, ':raw') for \*STDIN, \*STDOUT, $child_in, $child_out, $child_err;
+
+$SIG{INT} = sub { log_msg('received SIGINT'); kill 'INT', $pid };
+$SIG{TERM} = sub { log_msg('received SIGTERM'); kill 'TERM', $pid };
+
+my $json = JSON::PP->new->utf8;
+
+my ($next_client_seq, $next_adapter_seq) = (1, 1);
+my (%client_request_seq_map, %adapter_request_seq_map, %request_args_by_client_seq);
+my ($stdin_buf, $child_buf) = ('', '');
+
+my $stdin_fn = fileno(\*STDIN);
+my $child_out_fn = fileno($child_out);
+
+my $sel = IO::Select->new(\*STDIN, $child_out, $child_err);
+
+LOOP: while ($sel->count > 0) {
+ my @ready = $sel->can_read;
+ for my $fh (@ready) {
+ my ($chunk, $n);
+ $n = sysread($fh, $chunk, 65536);
+ unless (defined $n && $n > 0) {
+ $sel->remove($fh);
+ my $fn = fileno($fh);
+ if ($fn == $stdin_fn) {
+ log_msg('stdin closed');
+ close($child_in);
+ } elsif ($fn == $child_out_fn) {
+ log_msg('adapter stdout closed');
+ last LOOP;
+ } else {
+ log_msg('adapter stderr closed');
+ }
+ next;
+ }
+ my $fn = fileno($fh);
+ if ($fn == $stdin_fn) {
+ $stdin_buf .= $chunk;
+ flush_dap(\$stdin_buf, $child_in, \&rewrite_client_message);
+ } elsif ($fn == $child_out_fn) {
+ $child_buf .= $chunk;
+ flush_dap(\$child_buf, \*STDOUT, \&rewrite_adapter_message);
+ } else {
+ log_msg('stderr ' . $chunk);
+ print STDERR $chunk;
+ }
+ }
+}
+
+waitpid($pid, 0);
+log_msg(sprintf 'adapter exited status=%d signal=%d', $? >> 8, $? & 127);
+# $? encodes exit code in the high byte and terminating signal in the low 7 bits.
+# Re-raise the signal so the parent sees a signal-killed exit rather than exit(0).
+my $signal = $? & 127;
+if ($signal) {
+ $SIG{$_} = 'DEFAULT' for qw(INT TERM);
+ kill $signal, $$;
+ sleep 1;
+}
+log_msg(sprintf 'proxy exit code=%d', $? >> 8);
+exit($? >> 8);
+
+sub flush_dap {
+ my ($buf_ref, $dest, $rewrite_fn) = @_;
+ while (1) {
+ my $header_end = index($$buf_ref, "\r\n\r\n");
+ last if $header_end == -1;
+
+ my $header = substr($$buf_ref, 0, $header_end);
+ unless ($header =~ /Content-Length:\s*(\d+)/i) {
+ syswrite($dest, $$buf_ref);
+ $$buf_ref = '';
+ return;
+ }
+
+ my $body_len = 0 + $1;
+ my $frame_len = $header_end + 4 + $body_len;
+ last if length($$buf_ref) < $frame_len;
+
+ my $body = substr($$buf_ref, $header_end + 4, $body_len);
+ $$buf_ref = substr($$buf_ref, $frame_len);
+
+ my $msg = eval { $json->decode($body) };
+ if (!$@ && ref($msg) eq 'HASH') {
+ $rewrite_fn->($msg);
+ my $out = $json->encode($msg);
+ syswrite($dest, 'Content-Length: ' . length($out) . "\r\n\r\n");
+ syswrite($dest, $out);
+ } else {
+ log_msg(sprintf 'non-json message forwarded%s', $@ ? " error=$@" : ' reason=not-a-hash');
+ syswrite($dest, 'Content-Length: ' . $body_len . "\r\n\r\n");
+ syswrite($dest, $body);
+ }
+ }
+}
+
+sub rewrite_client_message {
+ my ($msg) = @_;
+ my $type = $msg->{type} // '';
+ my $command = $msg->{command} // '';
+ log_msg(sprintf 'client message type=%s command=%s seq_in=%s request_seq_in=%s',
+ $type, $command, $msg->{seq} // '', $msg->{request_seq} // '');
+
+ # Nova omits the filters array on setExceptionBreakpoints requests, which
+ # lldb-dap then rejects. Ensure the field is always present before forwarding.
+ if ($type eq 'request' && $command eq 'setExceptionBreakpoints') {
+ $msg->{arguments} = {} unless ref($msg->{arguments}) eq 'HASH';
+ $msg->{arguments}{filters} = [] unless ref($msg->{arguments}{filters}) eq 'ARRAY';
+ }
+
+ if ($type eq 'response' && defined $msg->{request_seq}) {
+ my $rseq = $msg->{request_seq};
+ if (exists $adapter_request_seq_map{$rseq}) {
+ $msg->{request_seq} = $adapter_request_seq_map{$rseq};
+ delete $adapter_request_seq_map{$rseq};
+ }
+ }
+
+ if (defined $msg->{seq}) {
+ my $orig = $msg->{seq};
+ my $rewrite = $next_client_seq++;
+ if ($type eq 'request') {
+ $client_request_seq_map{$rewrite} = $orig;
+ $request_args_by_client_seq{$rewrite} = {
+ command => $command,
+ arguments => $msg->{arguments},
+ };
+ log_msg(sprintf 'client seq map %s=>%s', $orig, $rewrite);
+ }
+ $msg->{seq} = $rewrite;
+ }
+
+ log_msg(sprintf 'client message seq_out=%s request_seq_out=%s',
+ $msg->{seq} // '', $msg->{request_seq} // '');
+}
+
+sub rewrite_adapter_message {
+ my ($msg) = @_;
+ my $type = $msg->{type} // '';
+ my $command = $msg->{command} // '';
+ log_msg(sprintf 'adapter message type=%s command=%s seq_in=%s request_seq_in=%s',
+ $type, $command, $msg->{seq} // '', $msg->{request_seq} // '');
+
+ if ($type eq 'response' && defined $msg->{request_seq}) {
+ my $rseq = $msg->{request_seq};
+ my $orig_req = $request_args_by_client_seq{$rseq};
+ my $orig_seq = $client_request_seq_map{$rseq};
+
+ if (defined $orig_seq) {
+ $msg->{request_seq} = $orig_seq;
+ delete $client_request_seq_map{$rseq};
+ }
+
+ # lldb-dap returns a successful setExceptionBreakpoints response without
+ # the breakpoints array the DAP spec requires. Synthesise one verified
+ # entry per filter so Nova's client confirms each breakpoint as registered.
+ if (defined $orig_req
+ && ($orig_req->{command} // '') eq 'setExceptionBreakpoints'
+ && $msg->{success})
+ {
+ my $filters = ref($orig_req->{arguments}{filters}) eq 'ARRAY'
+ ? $orig_req->{arguments}{filters} : [];
+ $msg->{body} = {} unless ref($msg->{body}) eq 'HASH';
+ unless (ref($msg->{body}{breakpoints}) eq 'ARRAY') {
+ $msg->{body}{breakpoints} = [ map { +{ verified => JSON::PP::true } } @$filters ];
+ }
+ }
+
+ delete $request_args_by_client_seq{$rseq};
+ }
+
+ if (defined $msg->{seq}) {
+ my $orig = $msg->{seq};
+ my $rewrite = $next_adapter_seq++;
+ $adapter_request_seq_map{$rewrite} = $orig if $type eq 'request';
+ $msg->{seq} = $rewrite;
+ log_msg("adapter seq map $orig=>$rewrite");
+ }
+
+ log_msg(sprintf 'adapter message seq_out=%s request_seq_out=%s',
+ $msg->{seq} // '', $msg->{request_seq} // '');
+}
diff --git a/Zig.novaextension/Scripts/main.js b/Zig.novaextension/Scripts/main.js
new file mode 100644
index 0000000..7477993
--- /dev/null
+++ b/Zig.novaextension/Scripts/main.js
@@ -0,0 +1,1583 @@
+"use strict";
+
+// --- CONSTANTS ---------------------------------------------------------------
+
+const EXTENSION_ID = "at.dcz.nova-zig";
+const TASK_ASSISTANT_ID = `${EXTENSION_ID}.tasks`;
+const LANGUAGE_CLIENT_ID = `${EXTENSION_ID}.zls`;
+const ISSUE_MATCHER = "zig.compiler";
+const USER_OPTION_REGEX = /^[A-Za-z_][A-Za-z0-9_-]*(=.*)?$/;
+const STEP_CACHE_TTL_MS = 5 * 60 * 1000;
+
+const CONFIG_KEYS = {
+ zigPath: `${EXTENSION_ID}.toolchain.zig-path`,
+ zlsPath: `${EXTENSION_ID}.toolchain.zls-path`,
+ lldbDapPath: `${EXTENSION_ID}.toolchain.lldb-dap-path`,
+ zlsEnabled: `${EXTENSION_ID}.zls.enabled`,
+ zlsBuildOnSave: `${EXTENSION_ID}.zls.build-on-save`,
+ zlsDebug: `${EXTENSION_ID}.zls.debug`,
+ lldbDapDebug: `${EXTENSION_ID}.debug-adapter.debug`,
+ discoverSteps: `${EXTENSION_ID}.tasks.discover-steps`,
+};
+
+// --- LIFECYCLE ---------------------------------------------------------------
+
+let languageServer = null;
+let taskAssistant = null;
+let commandRegistrations = [];
+// Tracks when the last ZLS client was stopped. Used by start() to wait for
+// the old ZLS process to fully exit before spawning a new one — survives
+// across instances when Nova auto-restarts the extension.
+let lastZlsStopAt = 0;
+const ZLS_RESTART_GRACE_MS = 500;
+
+exports.activate = function activate() {
+ // Nova may call activate() again without first calling deactivate() when a
+ // language server crashes and the extension is auto-restarted. Dispose any
+ // existing instances so their config observers and ZLS processes don't leak.
+ if (languageServer) {
+ languageServer.dispose();
+ languageServer = null;
+ }
+ if (taskAssistant) {
+ taskAssistant.dispose();
+ taskAssistant = null;
+ }
+ commandRegistrations.forEach((disposable) => {
+ if (disposable && typeof disposable.dispose === "function") {
+ try {
+ disposable.dispose();
+ } catch (error) {
+ console.error(
+ `[${EXTENSION_ID}] Failed to dispose registration`,
+ error,
+ );
+ }
+ }
+ });
+ commandRegistrations = [];
+
+ registerCommands();
+ taskAssistant = new ZigTaskAssistant();
+ languageServer = new ZigLanguageServer();
+ console.log(`[${EXTENSION_ID}] activated`);
+};
+
+exports.deactivate = function deactivate() {
+ if (languageServer) {
+ languageServer.dispose();
+ languageServer = null;
+ }
+
+ if (taskAssistant) {
+ taskAssistant.dispose();
+ taskAssistant = null;
+ }
+
+ commandRegistrations.forEach((disposable) => {
+ if (disposable && typeof disposable.dispose === "function") {
+ try {
+ disposable.dispose();
+ } catch (error) {
+ console.error(
+ `[${EXTENSION_ID}] Failed to dispose registration`,
+ error,
+ );
+ }
+ }
+ });
+ commandRegistrations = [];
+
+ console.log(`[${EXTENSION_ID}] deactivated`);
+};
+
+// --- CONFIG HELPERS ----------------------------------------------------------
+
+/**
+ * Resolve a configuration value respecting precedence
+ *
+ * @param {string} key - Configuration key
+ */
+function getConfigValue(key) {
+ const workspaceValue = nova.workspace.config.get(key);
+ if (
+ workspaceValue !== undefined &&
+ workspaceValue !== null &&
+ workspaceValue !== ""
+ ) {
+ return workspaceValue;
+ }
+
+ const globalValue = nova.config.get(key);
+ if (globalValue !== undefined && globalValue !== null && globalValue !== "") {
+ return globalValue;
+ }
+
+ return null;
+}
+
+/**
+ * Resolve a configuration boolean value respecting precedence
+ *
+ * @param {string} key - Configuration key
+ * @param {boolean} fallback - Fallback value
+ */
+function getBooleanConfigValue(key, fallback) {
+ const workspaceValue = nova.workspace.config.get(key);
+ if (workspaceValue === "enabled") return true;
+ if (workspaceValue === "disabled") return false;
+ if (typeof workspaceValue === "boolean") return workspaceValue;
+
+ const globalValue = nova.config.get(key);
+ if (typeof globalValue === "boolean") return globalValue;
+
+ return fallback;
+}
+
+/**
+ * Normalize a config array value by converting to array, filtering nulls,
+ * trimming whitespace, and removing empty entries
+ *
+ * @param {*} value - Any value
+ * @returns {string[]} - Clean array of non-empty strings
+ */
+function normalizeArray(value) {
+ if (!Array.isArray(value)) {
+ return [];
+ }
+
+ return value
+ .map((entry) =>
+ entry === null || entry === undefined ? "" : String(entry).trim(),
+ )
+ .filter((entry) => entry.length > 0);
+}
+
+/**
+ * Resolve a relative path to an absolute path, using a provided base directory
+ * or workspace root as fallback
+ *
+ * @param {string} path - Relative path
+ * @param {string|null} base - Base path or null to use the workspace as base
+ */
+function resolvePathAgainstBase(path, base) {
+ if (!path) {
+ return null;
+ }
+
+ if (path.startsWith("/")) {
+ return path;
+ }
+
+ if (base) {
+ return nova.path.join(base, path);
+ }
+
+ if (nova.workspace.path) {
+ return nova.path.join(nova.workspace.path, path);
+ }
+
+ return path;
+}
+
+/**
+ * Safely retrieve a task config value
+ *
+ * @param {Object} config - Nova task configuration object
+ * @param {string} key - Key
+ * @returns {string} - Value
+ */
+function getTaskConfigValue(config, key) {
+ if (!config) {
+ return null;
+ }
+
+ const value = config.get(key);
+ return value === undefined || value === null || value === "" ? null : value;
+}
+
+/**
+ * Safely retrieve a task config argument list
+ *
+ * Specialized version of {@link getTaskConfigValue}
+ *
+ * @param {Object} config - Nova task configuration object
+ * @param {string} key - Key
+ * @returns {string[]} - Argument list
+ */
+function getTaskArgs(config, key) {
+ return normalizeArray(getTaskConfigValue(config, key));
+}
+
+/**
+ * Resolves the task working directory from config, falling back to workspace root.
+ *
+ * @param {Object} config - Nova task configuration object
+ * @returns {string|null} - Absolute working directory path or null if no workspace is open
+ */
+function getTaskCwd(config) {
+ const configured = getTaskConfigValue(config, "cwd");
+ if (configured) {
+ return resolvePathAgainstBase(configured, null);
+ }
+
+ return nova.workspace.path || null;
+}
+
+// --- TASK ARG HELPERS --------------------------------------------------------
+
+// Returns the configured step string, or null if the step should be omitted.
+// An explicit empty string means "no step argument" — `zig build` then runs the
+// default install step (Ziglings-style projects rely on this). Missing/null
+// returns the caller-supplied fallback.
+function resolveStep(config, key, fallback) {
+ const raw = config ? config.get(key) : undefined;
+ if (raw === undefined || raw === null) {
+ return fallback === undefined ? null : fallback;
+ }
+ const trimmed = String(raw).trim();
+ return trimmed.length > 0 ? trimmed : null;
+}
+
+// Backwards-compatible wrapper: zigBuildRun's `runStep` field. The legacy
+// `step` key is still honored for users on pre-0.1 task configs.
+function resolveRunStep(config) {
+ const raw = config ? config.get("runStep") : undefined;
+ if (raw === undefined || raw === null) {
+ const legacy = getTaskConfigValue(config, "step");
+ return legacy ? legacy : null;
+ }
+ const trimmed = String(raw).trim();
+ return trimmed.length > 0 ? trimmed : null;
+}
+
+// Builds the `zig build` argv prefix shared by Build/Run/Test/Watch resolvers.
+// Order: ["build", -Doptimize, -Dtarget, -D<userOptions>, ...buildArgs, step?].
+// `runArgs` are the caller's responsibility — they go after `--`.
+function buildZigArgv(config, options) {
+ const opts = options || {};
+ const argv = ["build"];
+
+ const optimize =
+ getTaskConfigValue(config, "optimize") || opts.defaultOptimize || null;
+ if (optimize) argv.push(`-Doptimize=${optimize}`);
+
+ const target = getTaskConfigValue(config, "target");
+ if (target) argv.push(`-Dtarget=${target}`);
+
+ for (const entry of getTaskArgs(config, "userOptions")) {
+ if (USER_OPTION_REGEX.test(entry)) {
+ argv.push(`-D${entry}`);
+ } else {
+ console.warn(`[zig-task] Skipping invalid userOptions entry: ${entry}`);
+ }
+ }
+
+ argv.push(...getTaskArgs(config, "buildArgs"));
+
+ if (opts.step) argv.push(opts.step);
+ return argv;
+}
+
+// --- PATH & WORKSPACE HELPERS ------------------------------------------------
+
+/**
+ * Validate cwd is safe to clean (absolute, inside workspace, not root or home),
+ * returning directories or null
+ *
+ * @param {string} cwd - Directory to clean
+ * @returns {string[]|null} - Directories to remove
+ */
+function resolveCleanPaths(cwd) {
+ const showWarning = () => {
+ nova.workspace.showWarningMessage(
+ localizeText(
+ "warning.clean.unsafe_cwd",
+ "Refusing to clean: the working directory must be inside this workspace.",
+ ),
+ );
+ };
+
+ if (!cwd || typeof cwd !== "string" || !cwd.startsWith("/")) {
+ showWarning();
+ return null;
+ }
+
+ const normalized = nova.path.normalize(cwd);
+ if (normalized === "/" || normalized === "") {
+ showWarning();
+ return null;
+ }
+
+ const home = nova.environment ? nova.environment.HOME : null;
+ if (
+ home &&
+ (normalized === home || normalized === nova.path.normalize(home))
+ ) {
+ showWarning();
+ return null;
+ }
+
+ const workspacePath = nova.workspace.path;
+ if (!workspacePath) {
+ showWarning();
+ return null;
+ }
+
+ const workspaceNormalized = nova.path.normalize(workspacePath);
+ if (
+ normalized !== workspaceNormalized &&
+ !normalized.startsWith(workspaceNormalized + "/")
+ ) {
+ showWarning();
+ return null;
+ }
+
+ return [".zig-cache", "zig-cache", "zig-out"];
+}
+
+/**
+ * Walk up from startDir to find the nearest ancestor containing build.zig,
+ * stopping at workspace root
+ *
+ * @param {string} startDir - Start directory
+ */
+function findNearestZigBuildDir(startDir) {
+ const workspacePath = nova.workspace.path
+ ? nova.path.normalize(nova.workspace.path)
+ : null;
+
+ if (!startDir) return workspacePath || null;
+
+ let current = nova.path.normalize(startDir);
+ for (let i = 0; i < 64; i++) {
+ if (nova.fs.stat(nova.path.join(current, "build.zig"))) {
+ return current;
+ }
+ if (current === "/" || (workspacePath && current === workspacePath)) {
+ return workspacePath || null;
+ }
+ const parent = nova.path.dirname(current);
+ if (!parent || parent === current) break;
+ current = parent;
+ }
+ return workspacePath || null;
+}
+
+/**
+ * Extract the package name from `build.zig.zon` by regex,
+ * returning null if absent or unreadable
+ *
+ * @param {string} cwd - Directory in which to search for build.zig.zon
+ * @returns {string|null} - Package name or null if parsing fails
+ */
+function parseProjectName(cwd) {
+ if (!cwd) return null;
+ const zonPath = nova.path.join(cwd, "build.zig.zon");
+ if (!nova.fs.stat(zonPath)) return null;
+
+ let content = "";
+ try {
+ const file = nova.fs.open(zonPath, "r");
+ try {
+ content = file.read() || "";
+ } finally {
+ file.close();
+ }
+ } catch (error) {
+ console.warn(`[zig] Failed to read ${zonPath}: ${error}`);
+ return null;
+ }
+ if (typeof content !== "string" || content.length === 0) return null;
+
+ const stripped = content.replace(/\/\/[^\n]*/g, "");
+ const match = stripped.match(/\.name\s*=\s*(?:"([^"]+)"|\.([A-Za-z_][\w]*))/);
+ if (!match) return null;
+ return match[1] || match[2] || null;
+}
+
+/**
+ * Resolve the path of the active zig file
+ *
+ * @returns {string|null} - Path or null if active editor is not a zig file
+ */
+function activeZigFilePath() {
+ const editor = nova.workspace.activeTextEditor;
+ if (!editor || !editor.document || !editor.document.path) {
+ return null;
+ }
+
+ if (editor.document.syntax !== "zig") {
+ return null;
+ }
+
+ return editor.document.path;
+}
+
+/**
+ * Resolve the directory of the active zig file
+ *
+ * @returns {string|null} - Directory or null if active editor is not a zig file
+ */
+function activeZigFileDirectory() {
+ const filePath = activeZigFilePath();
+ if (!filePath) {
+ return null;
+ }
+
+ return nova.path.dirname(filePath);
+}
+
+// --- STEP CACHE --------------------------------------------------------------
+
+// Per-cwd cache for `zig build --list-steps`. Invalidated by mtime changes on
+// build.zig / build.zig.zon and a soft 5-minute TTL. `getOrFetch` returns
+// cached steps synchronously and kicks off a background refresh when stale,
+// nudging Nova to reload tasks once the refresh lands.
+const STEP_DISCOVERY_WARN_THROTTLE_MS = 5 * 60 * 1000;
+
+const stepCache = {
+ entries: new Map(),
+ pending: new Map(),
+ lastWarnedAt: new Map(),
+
+ getOrFetch(cwd) {
+ if (!cwd) return null;
+
+ const buildZigPath = nova.path.join(cwd, "build.zig");
+ const buildZigStat = nova.fs.stat(buildZigPath);
+ if (!buildZigStat) return null;
+
+ const buildZonStat = nova.fs.stat(nova.path.join(cwd, "build.zig.zon"));
+ const buildZigMtimeMs = buildZigStat.mtime
+ ? buildZigStat.mtime.getTime()
+ : 0;
+ const buildZonMtimeMs =
+ buildZonStat && buildZonStat.mtime ? buildZonStat.mtime.getTime() : 0;
+
+ const cached = this.entries.get(cwd);
+ const fresh =
+ cached &&
+ cached.buildZigMtimeMs === buildZigMtimeMs &&
+ cached.buildZonMtimeMs === buildZonMtimeMs &&
+ Date.now() - cached.fetchedAt < STEP_CACHE_TTL_MS;
+
+ if (fresh) return cached.steps;
+
+ if (!this.pending.has(cwd)) {
+ const promise = this.fetch(cwd, buildZigMtimeMs, buildZonMtimeMs).finally(
+ () => {
+ this.pending.delete(cwd);
+ if (typeof nova.workspace.reloadTasks === "function") {
+ try {
+ nova.workspace.reloadTasks(TASK_ASSISTANT_ID);
+ } catch (_) {}
+ }
+ },
+ );
+ this.pending.set(cwd, promise);
+ }
+
+ return cached ? cached.steps : null;
+ },
+
+ async fetch(cwd, buildZigMtimeMs, buildZonMtimeMs) {
+ console.log(`[${TASK_ASSISTANT_ID}] stepCache.fetch: cwd=${cwd}`);
+ const zigPath = await resolveZigExecutable();
+ if (!zigPath) return null;
+
+ const result = await runProcess(zigPath, {
+ args: ["build", "--list-steps"],
+ cwd,
+ timeoutMs: 60000,
+ });
+ if (result.status !== 0) {
+ const last = this.lastWarnedAt.get(cwd) || 0;
+ if (Date.now() - last >= STEP_DISCOVERY_WARN_THROTTLE_MS) {
+ this.lastWarnedAt.set(cwd, Date.now());
+ const detail = (result.stderr || "").trim().slice(0, 500);
+ console.warn(
+ `[zig-task] Step discovery failed in ${cwd} (status ${result.status})${detail ? ": " + detail : ""}`,
+ );
+ }
+ return null;
+ }
+
+ const steps = result.stdout
+ .split("\n")
+ .map((line) => line.trim())
+ .filter((line) => line.length > 0)
+ .map((line) => line.split(/\s+/, 1)[0])
+ .filter((name) => /^[A-Za-z_][\w-]*$/.test(name));
+
+ this.entries.set(cwd, {
+ steps,
+ buildZigMtimeMs,
+ buildZonMtimeMs,
+ fetchedAt: Date.now(),
+ });
+ return steps;
+ },
+
+ invalidate(cwd) {
+ if (cwd) {
+ this.entries.delete(cwd);
+ this.lastWarnedAt.delete(cwd);
+ } else {
+ this.entries.clear();
+ this.lastWarnedAt.clear();
+ }
+ },
+};
+
+// --- LOCALIZATION -------------------------------------------------------------
+
+/**
+ * Resolve a localized string, substituting `{variable}` placeholders
+ *
+ * @param {string} key - Key
+ * @param {string} fallback - Fallback text
+ * @param {*} variables - Dictionary of variable names and values
+ */
+function localizeText(key, fallback, variables) {
+ let text = nova.localize(key, null);
+
+ if (key === text) {
+ return `Localization missing for ${key}`;
+ }
+
+ if (!variables || typeof variables !== "object") {
+ return text;
+ }
+
+ for (const [name, value] of Object.entries(variables)) {
+ text = text.split(`{${name}}`).join(String(value));
+ }
+
+ return text;
+}
+
+function localize(key, variables) {
+ let text = nova.localize(key, null);
+
+ if (key === text) {
+ console.warn(`[locales] Missing localization for ${key}`);
+ return text;
+ }
+
+ if (!variables || typeof variables !== "object") {
+ return text;
+ }
+
+ for (const [name, value] of Object.entries(variables)) {
+ text = text.split(`{${name}}`).join(String(value));
+ }
+
+ return text;
+}
+
+// --- PROCESS -----------------------------------------------------------------
+
+/**
+ * Wraps Nova's Process API in a Promise, resolving with stdout, stderr, and exit status.
+ *
+ * @param {string} command - Absolute path to the executable
+ * @param {Object} options - Options passed to Nova's Process constructor (args, cwd, env, …);
+ * the optional `timeoutMs` is consumed here and removed before construction
+ * @returns {Promise<{status: number, stdout: string, stderr: string}>}
+ */
+function runProcess(command, options) {
+ return new Promise((resolve, reject) => {
+ const stdout = [];
+ const stderr = [];
+ const { timeoutMs, ...processOptions } = options || {};
+ const process = new Process(command, processOptions);
+
+ let settled = false;
+ let timer = null;
+ const settle = (result) => {
+ if (settled) return;
+ settled = true;
+ if (timer) clearTimeout(timer);
+ resolve(result);
+ };
+
+ process.onStdout((line) => stdout.push(line));
+ process.onStderr((line) => stderr.push(line));
+ process.onDidExit((status) => {
+ settle({
+ status,
+ stdout: stdout.join(""),
+ stderr: stderr.join(""),
+ });
+ });
+
+ try {
+ process.start();
+ } catch (error) {
+ if (timer) clearTimeout(timer);
+ reject(error);
+ return;
+ }
+
+ if (typeof timeoutMs === "number" && timeoutMs > 0) {
+ timer = setTimeout(() => {
+ try {
+ if (typeof process.signal === "function") {
+ process.signal("SIGTERM");
+ }
+ } catch (_) {}
+ settle({
+ status: -1,
+ stdout: stdout.join(""),
+ stderr: `${stderr.join("")}\n[timeout after ${timeoutMs}ms]`,
+ });
+ }, timeoutMs);
+ }
+ });
+}
+
+// --- EXECUTABLE RESOLVERS ----------------------------------------------------
+
+/**
+ * Find executable on PATH using `which`
+ *
+ * @param {string} executableName - Executable name
+ * @returns {Promise<string|null>} - Path to the executable or null if not found
+ */
+async function findOnPath(executableName) {
+ // Pass Nova's own PATH explicitly — child processes do not inherit the parent
+ // environment automatically, so without this the subprocess sees a stripped PATH
+ // that misses entries like /opt/homebrew/bin.
+ const novaPath = (nova.environment && nova.environment.PATH) || "";
+
+ // Augment Nova's PATH with well-known prefixes that Homebrew and common
+ // package managers use but that may be absent when Nova is launched from the
+ // Dock (where launchd provides a narrower system PATH than a login shell).
+ const fallbackPrefixes = [
+ "/opt/homebrew/bin", // Homebrew – Apple Silicon
+ "/usr/local/bin", // Homebrew – Intel / manual installs
+ `${nova.environment && nova.environment.HOME}/.local/bin`, // mise, cargo, etc.
+ ];
+ const augmentedPath = [
+ ...new Set([...novaPath.split(":").filter(Boolean), ...fallbackPrefixes]),
+ ].join(":");
+
+ const result = await runProcess("/usr/bin/env", {
+ args: ["which", executableName],
+ env: { PATH: augmentedPath },
+ });
+ const found = result.stdout.trim();
+ console.log(
+ `[${EXTENSION_ID}] findOnPath: ${executableName} → ${result.status === 0 ? found : "not found"}`,
+ );
+
+ if (result.status !== 0) {
+ return null;
+ }
+
+ return found.length > 0 ? found : null;
+}
+
+/**
+ * Resolves an executable path from config first, falling back to PATH search
+ *
+ * @param {string} configKey - Configuration key
+ * @param {string} defaultCommand - Default command to be searched on PATH
+ * @returns {Promise<string|null>} - Path to the executable or null if not found
+ */
+async function resolveExecutable(configKey, defaultCommand) {
+ const configuredPath = getConfigValue(configKey);
+ if (configuredPath) {
+ console.log(
+ `[${EXTENSION_ID}] findOnPath: ${defaultCommand} → config: ${configuredPath}`,
+ );
+ return configuredPath;
+ }
+
+ return await findOnPath(defaultCommand);
+}
+
+/**
+ * Resolve the zig executable, show warning if not found
+ *
+ * @returns {Promise<string|null>} - Path to the zig executable or null if not found
+ */
+async function resolveZigExecutable() {
+ const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
+ if (!zigPath) {
+ nova.workspace.showWarningMessage(
+ localizeText(
+ "warning.zig.not-found",
+ "Zig was not found. Install it or set a Zig executable path in Zig extension settings.",
+ ),
+ );
+ return null;
+ }
+ return zigPath;
+}
+
+/**
+ * Resolve executable using Xcode `xcrun`
+ *
+ * @param {string} executableName - Executable name
+ * @returns {Promise<string|null>} - Path to the executable or null if not found
+ */
+async function findWithXcode(executableName) {
+ const result = await runProcess("/usr/bin/xcrun", {
+ args: ["--find", executableName],
+ });
+
+ if (result.status !== 0) {
+ return null;
+ }
+
+ const path = result.stdout.trim();
+ return path.length > 0 ? path : null;
+}
+
+/**
+ * Resolve the lldb-dap executable
+ *
+ * @returns {Promise<string|null>} - Path to the lldb-dap executable or null if not found
+ */
+async function resolveLldbDapExecutable() {
+ const configuredPath = getConfigValue(CONFIG_KEYS.lldbDapPath);
+ if (configuredPath) {
+ return configuredPath;
+ }
+
+ const xcodePath = await findWithXcode("lldb-dap");
+ if (xcodePath) {
+ return xcodePath;
+ }
+
+ return await findOnPath("lldb-dap");
+}
+
+/**
+ * Return Dynamic Linker (DYLD) framework search paths for LLDB
+ *
+ * @returns {string[]} - Search paths
+ */
+function lldbFrameworkPaths() {
+ return [
+ "/Applications/Xcode-beta.app/Contents/SharedFrameworks/",
+ "/Applications/Xcode.app/Contents/SharedFrameworks/",
+ "/Library/Developer/CommandLineTools/Library/PrivateFrameworks/",
+ ];
+}
+
+/**
+ * Return the absolute path to the lldb-dap proxy Perl script bundled
+ * with the extension
+ *
+ * @returns {string} - Path
+ */
+function lldbDapProxyPath() {
+ return nova.path.join(nova.extension.path, "Scripts", "lldb-dap-proxy.pl");
+}
+
+/**
+ * Return the path to the debug adapter log file in extension global storage
+ *
+ * @returns {string} - Path
+ */
+function debugAdapterLogPath() {
+ return nova.path.join(nova.extension.globalStoragePath, "lldb-dap-proxy.log");
+}
+
+/**
+ * Return the absolute path to the issue normalizer Perl script bundled
+ * with the extension
+ *
+ * @returns {string} - Path
+ */
+function issueNormalizerScriptPath() {
+ return nova.path.join(
+ nova.extension.path,
+ "Scripts",
+ "normalize-zig-issues.pl",
+ );
+}
+
+// --- SHELL UTILITIES ---------------------------------------------------------
+
+/**
+ * Wrap a value in single quotes with embedded single quotes escaped,
+ * safe for POSIX shell
+ *
+ * @param {string} value - String
+ * @returns {string} - Quoted string
+ */
+function quoteShellArgument(value) {
+ const text = value === null || value === undefined ? "" : String(value);
+ return `'${text.replace(/'/g, `'\\''`)}'`;
+}
+
+/**
+ * Escape backslashes and double quotes for safe embedding in AppleScript
+ *
+ * @param {string} value - String
+ * @returns {string} - Escaped string
+ */
+function escapeAppleScriptString(value) {
+ const text = value === null || value === undefined ? "" : String(value);
+ return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
+}
+
+/**
+ * Build a safe shell command string with cd preamble
+ *
+ * @param {string} command - Command
+ * @param {string[]} args - Arguments
+ * @param {string} cwd - Working directory
+ * @returns {string} - Safe shell command
+ */
+function buildShellCommand(command, args, cwd) {
+ const segments = [];
+ if (cwd) {
+ segments.push(`cd ${quoteShellArgument(cwd)}`);
+ }
+
+ segments.push(
+ [quoteShellArgument(command), ...(args || []).map(quoteShellArgument)].join(
+ " ",
+ ),
+ );
+ return segments.join("; ");
+}
+
+/**
+ * Build a shell command that pipes zig output through the issue normalizer
+ *
+ * @param {string} command - Command
+ * @param {string[]} args - Arguments
+ * @returns {string} - Piped command
+ */
+function buildIssueNormalizedCommand(command, args) {
+ const commandLine = [
+ quoteShellArgument(command),
+ ...(args || []).map(quoteShellArgument),
+ ].join(" ");
+ const rewriter = `/usr/bin/perl ${quoteShellArgument(issueNormalizerScriptPath())}`;
+ return `setopt pipefail; ${commandLine} 2>&1 | ${rewriter}`;
+}
+
+/**
+ * Runs a shell command string in a new macOS Terminal window.
+ *
+ * @param {string} commandLine - Command to run
+ * @returns {Promise<{status: number, stderr: string}>}
+ */
+function launchInTerminal(commandLine) {
+ return new Promise((resolve) => {
+ const script = `tell application "Terminal"
+activate
+do script "${escapeAppleScriptString(commandLine)}"
+end tell`;
+ const process = new Process("/usr/bin/osascript", {
+ args: ["-e", script],
+ });
+
+ let stderr = "";
+ process.onStderr((line) => {
+ stderr += line;
+ });
+ process.onDidExit((status) => {
+ resolve({ status, stderr: stderr.trim() });
+ });
+
+ try {
+ process.start();
+ } catch (error) {
+ resolve({ status: -1, stderr: String(error) });
+ }
+ });
+}
+
+// --- COMMANDS ----------------------------------------------------------------
+
+/** Registers the internal runInTerminal command used by external-terminal task actions. */
+function registerCommands() {
+ commandRegistrations.push(
+ nova.commands.register(
+ `${EXTENSION_ID}.runInTerminal`,
+ async (workspace, payload) => {
+ const command = payload && payload.command;
+ const args = (payload && payload.args) || [];
+ const cwd = (payload && payload.cwd) || workspace.path || null;
+ console.log(
+ `[${EXTENSION_ID}] runInTerminal: command=${command} cwd=${cwd}`,
+ );
+
+ if (!command) {
+ workspace.showWarningMessage(
+ localizeText(
+ "warning.terminal.launch_failed",
+ "Unable to launch the Zig task in Terminal.",
+ ),
+ );
+ return;
+ }
+
+ const result = await launchInTerminal(
+ buildShellCommand(command, args, cwd),
+ );
+ if (result.status !== 0) {
+ const prefix = localizeText(
+ "warning.terminal.open_failed",
+ "Unable to open Terminal for the Zig task.",
+ );
+ const suffix = result.stderr ? ` ${result.stderr}` : "";
+ workspace.showWarningMessage(`${prefix}${suffix}`);
+ }
+ },
+ ),
+ );
+}
+
+// --- LANGUAGE SERVER ---------------------------------------------------------
+
+function syncWorkspaceZlsConfiguration(settings) {
+ const bridge = {
+ "zls.zig_exe_path": settings.zig_exe_path,
+ "zls.enable_build_on_save": settings.enable_build_on_save,
+ };
+
+ Object.entries(bridge).forEach(([key, value]) => {
+ if (value === undefined || value === null || value === "") {
+ nova.workspace.config.remove(key);
+ } else {
+ nova.workspace.config.set(key, value);
+ }
+ });
+}
+
+class ZigLanguageServer {
+ constructor() {
+ this.client = null;
+ this.clientStopDisposable = null;
+ this.restartGeneration = 0;
+ this.warnedMissing = new Set();
+ this.disposables = [];
+
+ this.observeConfig(CONFIG_KEYS.zigPath, true);
+ this.observeConfig(CONFIG_KEYS.zlsPath, true);
+ this.observeConfig(CONFIG_KEYS.zlsEnabled, true);
+ this.observeConfig(CONFIG_KEYS.zlsBuildOnSave, true);
+ this.observeConfig(CONFIG_KEYS.zlsDebug, true);
+ this.disposables.push(
+ nova.workspace.onDidChangePath(() => {
+ this.start();
+ }),
+ );
+
+ this.start();
+ }
+
+ observeConfig(key, restart) {
+ const onChange = () => {
+ if (restart) {
+ console.log(
+ `[${LANGUAGE_CLIENT_ID}] config changed (${key}) → restart`,
+ );
+ this.start();
+ } else {
+ console.log(
+ `[${LANGUAGE_CLIENT_ID}] config changed (${key}) → push configuration`,
+ );
+ this.pushConfiguration().catch((error) => {
+ console.error(
+ `[${LANGUAGE_CLIENT_ID}] pushConfiguration failed`,
+ error,
+ );
+ });
+ }
+ };
+ this.disposables.push(nova.config.onDidChange(key, onChange));
+ this.disposables.push(nova.workspace.config.onDidChange(key, onChange));
+ }
+
+ dispose() {
+ this.stop();
+ // Remove observers first so the workspace config cleanup below does not
+ // accidentally fire any remaining callbacks.
+ this.disposables.forEach((disposable) => {
+ if (disposable && typeof disposable.dispose === "function") {
+ try {
+ disposable.dispose();
+ } catch (error) {
+ console.error(
+ `[${LANGUAGE_CLIENT_ID}] Failed to dispose subscription`,
+ error,
+ );
+ }
+ }
+ });
+ this.disposables = [];
+ }
+
+ async start() {
+ // Guard against stale async continuations: if a config change triggers
+ // another start() while we are awaiting, the generation check below bails out.
+ const generation = ++this.restartGeneration;
+ console.log(`[${LANGUAGE_CLIENT_ID}] start (generation ${generation})`);
+ this.stop();
+
+ // If a ZLS process was recently stopped (either on this instance or on a
+ // previous instance after Nova auto-restarted the extension), wait for it
+ // to fully exit before spawning a new one. Without this pause the two
+ // processes overlap, Nova sees two clients with the same ID, and kills
+ // the new one before the LSP handshake completes.
+ const sinceLastStop = Date.now() - lastZlsStopAt;
+ if (lastZlsStopAt > 0 && sinceLastStop < ZLS_RESTART_GRACE_MS) {
+ const wait = ZLS_RESTART_GRACE_MS - sinceLastStop;
+ console.log(
+ `[${LANGUAGE_CLIENT_ID}] waiting ${wait}ms for previous ZLS to exit`,
+ );
+ await new Promise((resolve) => setTimeout(resolve, wait));
+ if (generation !== this.restartGeneration) {
+ return;
+ }
+ }
+
+ if (!getBooleanConfigValue(CONFIG_KEYS.zlsEnabled, true)) {
+ console.log(`[${LANGUAGE_CLIENT_ID}] start: ZLS disabled, skipping`);
+ return;
+ }
+
+ const zlsPath = await resolveExecutable(CONFIG_KEYS.zlsPath, "zls");
+ if (generation !== this.restartGeneration) {
+ return;
+ }
+
+ if (!zlsPath) {
+ this.warnMissingTool(
+ "zls",
+ localizeText(
+ "warning.zls.not-found",
+ "ZLS was not found. Install it or set a ZLS executable path in Zig extension settings.",
+ ),
+ );
+ return;
+ }
+
+ const { settings, zigPath } = await this.resolveSettings();
+ if (generation !== this.restartGeneration) {
+ return;
+ }
+
+ const debugLogs = getBooleanConfigValue(CONFIG_KEYS.zlsDebug, false);
+
+ const serverOptions = {
+ path: zlsPath,
+ args: debugLogs ? [] : ["--disable-lsp-logs"],
+ };
+
+ const clientOptions = {
+ syntaxes: [{ syntax: "zig", languageId: "zig" }],
+ debug: debugLogs,
+ initializationOptions: settings,
+ };
+
+ const client = new LanguageClient(
+ LANGUAGE_CLIENT_ID,
+ localizeText("name.language_server", "Zig Language Server"),
+ serverOptions,
+ clientOptions,
+ );
+
+ client.onNotification("window/logMessage", ({ type, message }) => {
+ // type: 1=Error, 2=Warning, 3=Info, 4=Log
+ const enriched =
+ message === "ParseError"
+ ? "ParseError — ZLS could not fully parse the Zig source (normal while editing)"
+ : message;
+ if (type === 1) {
+ console.error(`[ZLS] ${enriched}`);
+ } else if (type === 2) {
+ console.warn(`[ZLS] ${enriched}`);
+ } else if (debugLogs) {
+ console.log(`[ZLS] ${enriched}`);
+ }
+ });
+
+ this.clientStopDisposable = client.onDidStop((error) => {
+ if (error) {
+ console.error(`[${LANGUAGE_CLIENT_ID}] ${error.message}`);
+ nova.workspace.showWarningMessage(
+ localizeText(
+ "warning.zls.stopped_unexpectedly",
+ "The Zig Language Server stopped unexpectedly ({executable}).",
+ { executable: zlsPath || "zls" },
+ ),
+ );
+ }
+ });
+
+ console.log(
+ `[${LANGUAGE_CLIENT_ID}] starting client: zls=${zlsPath} zig=${zigPath || "not found"}`,
+ );
+ try {
+ client.start();
+ this.client = client;
+ nova.subscriptions.add(client);
+ this.pushConfiguration(settings).catch((error) => {
+ console.error(
+ `[${LANGUAGE_CLIENT_ID}] pushConfiguration failed`,
+ error,
+ );
+ });
+ this.warnedMissing.delete("zls");
+ if (zigPath) {
+ this.warnedMissing.delete("zig");
+ }
+ } catch (error) {
+ console.error(`[${LANGUAGE_CLIENT_ID}] Failed to start ZLS`, error);
+ nova.workspace.showWarningMessage(
+ localizeText(
+ "warning.zls.start_failed",
+ "Unable to start the Zig language server at {path}.",
+ { path: zlsPath },
+ ),
+ );
+ this.stop();
+ }
+ }
+
+ async resolveSettings() {
+ const settings = {
+ enable_build_on_save: getBooleanConfigValue(
+ CONFIG_KEYS.zlsBuildOnSave,
+ false,
+ ),
+ };
+ const zigPath = await resolveExecutable(CONFIG_KEYS.zigPath, "zig");
+ if (zigPath) {
+ settings.zig_exe_path = zigPath;
+ }
+
+ return { settings, zigPath };
+ }
+
+ async pushConfiguration(preResolvedSettings) {
+ console.log(`[${LANGUAGE_CLIENT_ID}] pushConfiguration`);
+ const generation = this.restartGeneration;
+ const { settings } = preResolvedSettings
+ ? { settings: preResolvedSettings }
+ : await this.resolveSettings();
+ if (
+ generation !== this.restartGeneration ||
+ !this.client ||
+ !this.client.running
+ ) {
+ return;
+ }
+
+ this.client.sendNotification("workspace/didChangeConfiguration", {
+ settings,
+ });
+ }
+
+ stop() {
+ if (this.client) {
+ console.log(`[${LANGUAGE_CLIENT_ID}] stop`);
+ }
+ if (
+ this.clientStopDisposable &&
+ typeof this.clientStopDisposable.dispose === "function"
+ ) {
+ this.clientStopDisposable.dispose();
+ this.clientStopDisposable = null;
+ }
+
+ if (this.client) {
+ this.client.stop();
+ nova.subscriptions.remove(this.client);
+ this.client = null;
+ lastZlsStopAt = Date.now();
+ }
+ }
+
+ warnMissingTool(tool, message) {
+ if (this.warnedMissing.has(tool)) {
+ return;
+ }
+
+ this.warnedMissing.add(tool);
+ nova.workspace.showWarningMessage(message);
+ }
+}
+
+// --- TASK ASSISTANT ----------------------------------------------------------
+
+class ZigTaskAssistant {
+ constructor() {
+ this.disposable = nova.assistants.registerTaskAssistant(this, {
+ identifier: TASK_ASSISTANT_ID,
+ name: localize("autotasks.title"),
+ });
+ }
+
+ dispose() {
+ if (this.disposable && typeof this.disposable.dispose === "function") {
+ this.disposable.dispose();
+ this.disposable = null;
+ }
+ }
+
+ provideTasks() {
+ const tasks = [];
+
+ const currentFile = new Task(localize("autotasks.current-file.name"));
+ currentFile.image = "zig-script";
+ currentFile.setAction(
+ Task.Run,
+ new TaskResolvableAction({
+ data: {
+ type: "current-file-run",
+ },
+ }),
+ );
+ currentFile.setAction(
+ Task.Clean,
+ new TaskResolvableAction({
+ data: {
+ type: "current-file-clean",
+ },
+ }),
+ );
+ tasks.push(currentFile);
+
+ const workspacePath = nova.workspace.path;
+ if (
+ workspacePath &&
+ getBooleanConfigValue(CONFIG_KEYS.discoverSteps, true)
+ ) {
+ const steps = stepCache.getOrFetch(workspacePath);
+ if (steps && steps.length > 0) {
+ for (const step of steps) {
+ const task = new Task(localize("autotasks.buildstep.name", { step }));
+ task.image = "zig-hex";
+ task.setAction(
+ Task.Run,
+ new TaskResolvableAction({
+ data: {
+ type: "build-step",
+ step,
+ },
+ }),
+ );
+ tasks.push(task);
+ }
+ }
+ }
+
+ return tasks;
+ }
+
+ async resolveTaskAction(context) {
+ const type = context.data && context.data.type;
+ const config = context.config;
+ const cwd = getTaskCwd(config);
+ console.log(
+ `[${TASK_ASSISTANT_ID}] resolveTaskAction: type=${type} cwd=${cwd}`,
+ );
+
+ if (type === "clean") {
+ return this.resolveCleanAction(cwd);
+ }
+
+ switch (type) {
+ case "build":
+ return this.resolveBuildAction(config, cwd);
+ case "build-debug":
+ return this.resolveDebugBuildAction(config, cwd);
+ case "build-run":
+ return this.resolveBuildRunAction(config, cwd);
+ case "build-step":
+ return this.resolveBuildStepAction(context.data && context.data.step);
+ case "current-file-run":
+ return this.resolveCurrentFileRunAction();
+ case "current-file-clean":
+ return this.resolveCurrentFileCleanAction();
+ case "debug":
+ return this.resolveDebugAction(config, cwd);
+ case "test-build":
+ case "test-run":
+ return this.resolveTestAction(config, cwd);
+ case "watch":
+ return this.resolveWatchAction(config, cwd);
+ default:
+ return null;
+ }
+ }
+
+ createAction(command, args, cwd) {
+ return new TaskProcessAction("/bin/zsh", {
+ args: ["-lc", buildIssueNormalizedCommand(command, args)],
+ cwd,
+ env: {
+ NOVA_ZIG_TASK_CWD: cwd || "",
+ },
+ matchers: [ISSUE_MATCHER],
+ });
+ }
+
+ async resolveCleanAction(cwd) {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveCleanAction: cwd=${cwd}`);
+ if (!cwd) {
+ nova.workspace.showWarningMessage(
+ localizeText(
+ "warning.clean.missing_cwd",
+ "Choose a workspace or working directory before cleaning Zig build artifacts.",
+ ),
+ );
+ return null;
+ }
+
+ const paths = resolveCleanPaths(cwd);
+ if (!paths) return null;
+
+ // If the project exposes an `uninstall` step, prefer running it before
+ // wiping the cache directories. Use only what's already cached — don't
+ // block clean on a fresh `--list-steps` invocation.
+ const cached = stepCache.entries.get(cwd);
+ const args = ["-rf", ...paths];
+
+ if (
+ cached &&
+ Array.isArray(cached.steps) &&
+ cached.steps.includes("uninstall")
+ ) {
+ const zigPath = await resolveZigExecutable();
+ if (zigPath) {
+ // `;` (not `&&`) is intentional: rm should run even if uninstall fails.
+ const uninstall = buildShellCommand(zigPath, ["build", "uninstall"]);
+ const remove = buildShellCommand("/bin/rm", args);
+ return new TaskProcessAction("/bin/zsh", {
+ args: ["-lc", `${uninstall}; ${remove}`],
+ cwd,
+ matchers: [ISSUE_MATCHER],
+ });
+ }
+ }
+
+ return new TaskProcessAction("/bin/rm", { args, cwd });
+ }
+
+ async resolveBuildAction(config, cwd) {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveBuildAction: cwd=${cwd}`);
+ const zigPath = await resolveZigExecutable();
+ if (!zigPath) return null;
+
+ return this.createAction(zigPath, buildZigArgv(config), cwd);
+ }
+
+ async resolveDebugBuildAction(config, cwd) {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveDebugBuildAction: cwd=${cwd}`);
+ const zigPath = await resolveZigExecutable();
+ if (!zigPath) return null;
+
+ return this.createAction(
+ zigPath,
+ buildZigArgv(config, { defaultOptimize: "Debug" }),
+ cwd,
+ );
+ }
+
+ async resolveBuildRunAction(config, cwd, forceConsole) {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveBuildRunAction: cwd=${cwd}`);
+ const zigPath = await resolveZigExecutable();
+ if (!zigPath) return null;
+
+ const step = resolveRunStep(config);
+ const argv = buildZigArgv(config, { step });
+ const runArgs = getTaskArgs(config, "runArgs");
+ if (runArgs.length > 0) argv.push("--", ...runArgs);
+
+ const consoleMode =
+ forceConsole ||
+ getTaskConfigValue(config, "console") ||
+ "internalConsole";
+
+ if (consoleMode === "externalTerminal") {
+ return new TaskCommandAction(`${EXTENSION_ID}.runInTerminal`, {
+ args: [
+ {
+ command: zigPath,
+ args: argv,
+ cwd,
+ },
+ ],
+ });
+ }
+
+ return this.createAction(zigPath, argv, cwd);
+ }
+
+ async resolveBuildStepAction(step) {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveBuildStepAction: step=${step}`);
+ const zigPath = await resolveZigExecutable();
+ if (!zigPath) return null;
+
+ const cwd = nova.workspace.path || null;
+ if (!cwd || !step || !/^[A-Za-z_][\w-]*$/.test(step)) return null;
+
+ return this.createAction(zigPath, ["build", step], cwd);
+ }
+
+ async resolveTestAction(config, cwd) {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveTestAction: cwd=${cwd}`);
+ const zigPath = await resolveZigExecutable();
+ if (!zigPath) return null;
+
+ const argv = buildZigArgv(config, { step: "test" });
+
+ const summary = getTaskConfigValue(config, "summary");
+ if (summary) argv.push(`--summary=${summary}`);
+
+ const testFilter = getTaskConfigValue(config, "testFilter");
+ if (testFilter) argv.push("--test-filter", testFilter);
+
+ const runArgs = getTaskArgs(config, "runArgs");
+ if (runArgs.length > 0) argv.push("--", ...runArgs);
+
+ return this.createAction(zigPath, argv, cwd);
+ }
+
+ async resolveWatchAction(config, cwd) {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveWatchAction: cwd=${cwd}`);
+ const zigPath = await resolveZigExecutable();
+ if (!zigPath) return null;
+
+ const step = resolveStep(config, "step", null);
+ const argv = buildZigArgv(config, { step });
+ argv.push("--watch");
+
+ const debounce = getTaskConfigValue(config, "debounceMs");
+ if (debounce !== null && debounce !== undefined && debounce !== "") {
+ const n = Number(debounce);
+ if (Number.isFinite(n) && n >= 0)
+ argv.push("--debounce", String(Math.floor(n)));
+ }
+
+ const incremental = getTaskConfigValue(config, "incremental");
+ if (incremental === "on") argv.push("-fincremental");
+ else if (incremental === "off") argv.push("-fno-incremental");
+
+ return this.createAction(zigPath, argv, cwd);
+ }
+
+ async resolveCurrentFileRunAction() {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveCurrentFileRunAction`);
+ const zigPath = await resolveZigExecutable();
+ if (!zigPath) return null;
+
+ const filePath = activeZigFilePath();
+ const cwd = filePath ? nova.path.dirname(filePath) : null;
+ if (!filePath || !cwd) {
+ nova.workspace.showWarningMessage(
+ localizeText(
+ "warning.current_file.focus_editor_for_run",
+ "Focus a Zig editor before running Current Zig File.",
+ ),
+ );
+ return null;
+ }
+
+ return this.createAction(zigPath, ["run", filePath], cwd);
+ }
+
+ async resolveCurrentFileCleanAction() {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveCurrentFileCleanAction`);
+ const startDir = activeZigFileDirectory();
+ if (!startDir) {
+ nova.workspace.showWarningMessage(
+ localizeText(
+ "warning.current_file.focus_editor_for_clean",
+ "Focus a Zig editor before cleaning Current Zig File artifacts.",
+ ),
+ );
+ return null;
+ }
+
+ const cwd = findNearestZigBuildDir(startDir);
+ return this.resolveCleanAction(cwd);
+ }
+
+ async resolveDebugAction(config, cwd) {
+ console.log(`[${TASK_ASSISTANT_ID}] resolveDebugAction: cwd=${cwd}`);
+ const lldbDapPath = await resolveLldbDapExecutable();
+ if (!lldbDapPath) {
+ nova.workspace.showWarningMessage(
+ localizeText(
+ "warning.lldb_dap.not-found",
+ "lldb-dap was not found. Install Xcode Command Line Tools or set an LLDB DAP executable path in Zig extension settings.",
+ ),
+ );
+ return null;
+ }
+
+ const configuredProgramPath = getTaskConfigValue(config, "programPath");
+ let programPath = resolvePathAgainstBase(configuredProgramPath, cwd);
+ if (!programPath) {
+ const projectName = parseProjectName(cwd);
+ if (projectName) {
+ const candidate = nova.path.join(cwd, "zig-out", "bin", projectName);
+ if (nova.fs.stat(candidate)) {
+ programPath = candidate;
+ }
+ }
+ }
+ if (!programPath) {
+ nova.workspace.showWarningMessage(
+ localizeText(
+ "warning.debug.choose_program",
+ "Choose a program path before running Zig Debug.",
+ ),
+ );
+ return null;
+ }
+
+ const consoleMode =
+ getTaskConfigValue(config, "console") || "internalConsole";
+ const stopOnEntry = Boolean(config && config.get("stopOnEntry"));
+
+ const action = new TaskDebugAdapterAction("zig-lldb-dap");
+ action.transport = "stdio";
+ action.command = "/usr/bin/perl";
+ const debugLog = getBooleanConfigValue(CONFIG_KEYS.lldbDapDebug, false);
+ let logPath;
+ if (debugLog) {
+ try {
+ const p = debugAdapterLogPath();
+ const dir = nova.path.dirname(p);
+ if (!nova.fs.stat(dir)) nova.fs.mkdir(dir);
+ logPath = p;
+ } catch (_) {}
+ }
+ action.args = logPath
+ ? [lldbDapProxyPath(), lldbDapPath, logPath]
+ : [lldbDapProxyPath(), lldbDapPath];
+ action.debugRequest = "launch";
+ action.env = {
+ DYLD_FRAMEWORK_PATH: lldbFrameworkPaths().join(":"),
+ NOVA_ZIG_LLDB_DAP_PATH: lldbDapPath,
+ ...(logPath ? { NOVA_ZIG_DEBUG_LOG: logPath } : {}),
+ };
+ action.debugArgs = {
+ program: programPath,
+ cwd,
+ args: getTaskArgs(config, "runArgs"),
+ stopOnEntry,
+ };
+
+ if (consoleMode !== "internalConsole") {
+ action.debugArgs.console = consoleMode;
+ }
+
+ return action;
+ }
+}
diff --git a/Zig.novaextension/Scripts/normalize-zig-issues.pl b/Zig.novaextension/Scripts/normalize-zig-issues.pl
new file mode 100644
index 0000000..b6ff328
--- /dev/null
+++ b/Zig.novaextension/Scripts/normalize-zig-issues.pl
@@ -0,0 +1,93 @@
+use strict;
+use warnings;
+$| = 1;
+
+my $base = $ENV{NOVA_ZIG_TASK_CWD} || $ENV{PWD} || "";
+my %file_cache = ();
+
+sub normalize_path {
+ my ($path) = @_;
+ return $path if $path =~ m{^/} || $path =~ m{^[A-Za-z]:[\/\\]};
+
+ # The Zig compiler occasionally emits paths without a leading slash (e.g.
+ # "Users/..." instead of "/Users/...") when traversing relative directories.
+ # Detect known top-level directory names and restore the leading slash.
+ if ($path =~ m{^(?:Users|private|opt|Library|System|usr|var|tmp|etc|home)/}) {
+ return "/$path";
+ }
+
+ return $base eq "" ? $path : "$base/$path";
+}
+
+sub source_line_for_path {
+ my ($path, $line_number) = @_;
+ return undef if $line_number < 1;
+
+ if (!exists $file_cache{$path}) {
+ if (open my $fh, "<", $path) {
+ my @lines = <$fh>;
+ close $fh;
+ $file_cache{$path} = \@lines;
+ } else {
+ $file_cache{$path} = undef;
+ }
+ }
+
+ my $lines = $file_cache{$path};
+ return undef if !defined $lines;
+ return undef if $line_number > scalar(@$lines);
+
+ my $line = $lines->[$line_number - 1];
+ $line =~ s/\r?\n$//;
+ return $line;
+}
+
+# Zig reports argument-count errors at the column of the opening parenthesis,
+# which causes Nova to underline the entire argument list. For method-call
+# expressions (receiver.method(...)), shift the column back to the start of
+# the callee identifier so only the call site itself is highlighted.
+sub adjusted_call_column {
+ my ($source_line, $column) = @_;
+ return $column if !defined $source_line || $column < 1;
+
+ my $column_index = $column - 1;
+ my $open_paren = index($source_line, "(", $column_index);
+ return $column if $open_paren < 0;
+
+ my $prefix = substr($source_line, 0, $open_paren);
+ return $column if $prefix !~ /([A-Za-z_][A-Za-z0-9_]*)\s*$/;
+
+ my $callee = $1;
+ my $callee_index = rindex($prefix, $callee);
+ return $column if $callee_index < 0;
+
+ my $target_column = $callee_index + 1;
+ return $column if $target_column <= $column;
+
+ my $between = substr($source_line, $column_index, $target_column - $column);
+ return $column if $between !~ /\./;
+
+ return $target_column;
+}
+
+while (my $line = <STDIN>) {
+ my $newline = $line =~ s/\r?\n$// ? "\n" : "";
+
+ if ($line =~ m{^([^:\n]+\.zig):(\d+):(\d+):\s*(error|warning|note):\s*(.*)$}) {
+ my ($path, $line_number, $column, $severity, $message) = ($1, $2, $3, $4, $5);
+ my $normalized = normalize_path($path);
+
+ if ($message =~ /\bexpected\b.*argument\(s\).*\bfound\b/i) {
+ my $source_line = source_line_for_path($normalized, $line_number);
+ $column = adjusted_call_column($source_line, $column);
+ }
+
+ $line = "$normalized:$line_number:$column: $severity: $message";
+ } elsif ($line =~ m{^([^:\n]+\.zig):(\d+):(\d+):}) {
+ my ($path, $line_number, $column) = ($1, $2, $3);
+ my $normalized = normalize_path($path);
+ $line =~ s{^[^:\n]+\.zig:\d+:\d+:}{$normalized:$line_number:$column:};
+ }
+
+ print $line, $newline;
+}
diff --git a/Zig.novaextension/Scripts/update-parser.sh b/Zig.novaextension/Scripts/update-parser.sh
new file mode 100755
index 0000000..6e1fd63
--- /dev/null
+++ b/Zig.novaextension/Scripts/update-parser.sh
@@ -0,0 +1,64 @@
+#!/bin/sh
+#
+# update-parser.sh — Bump vendored tree-sitter-zig and rebuild the dylib.
+#
+# Purpose:
+# Refresh the vendored snapshot under vendor/tree-sitter-zig/ to a newer
+# upstream commit, update VENDORING.md, and rebuild the parser dylib.
+#
+# What it does:
+# - clones tree-sitter-grammars/tree-sitter-zig into a temp dir
+# - checks out the requested ref (default: HEAD of the default branch)
+# - exits early if the upstream SHA matches the pinned SHA
+# - replaces src/, queries/, grammar.js, tree-sitter.json, LICENSE
+# with the upstream copies; leaves VENDORING.md and README.upstream.md
+# untouched
+# - rewrites the "Pinned commit:" line in VENDORING.md
+# - invokes build-parser.sh to rebuild Syntaxes/libtree-sitter-zig.dylib
+# - prints a GitHub compare link for the diff
+#
+# Usage:
+# ./Scripts/update-parser.sh # bump to upstream HEAD
+# ./Scripts/update-parser.sh <ref> # bump to a tag, branch, or SHA
+#
+# Caveats:
+# - any local edits inside the listed paths are overwritten — review
+# `git diff vendor/` afterwards before committing
+# - parses VENDORING.md by an exact "Pinned commit: <sha>" line prefix;
+# keep that line format intact
+#
+# Requirements:
+# git, plus everything build-parser.sh needs.
+
+set -eu
+
+ROOT="$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)"
+VENDOR_DIR="$ROOT/vendor/tree-sitter-zig"
+UPSTREAM="https://github.com/tree-sitter-grammars/tree-sitter-zig.git"
+REF="${1:-HEAD}"
+
+TMP="$(mktemp -d)"
+trap 'rm -rf "$TMP"' EXIT
+
+git clone --quiet "$UPSTREAM" "$TMP/repo"
+git -C "$TMP/repo" checkout --quiet "$REF"
+SHA="$(git -C "$TMP/repo" rev-parse HEAD)"
+
+OLD_SHA="$(awk '/^Pinned commit:/ {print $3}' "$VENDOR_DIR/VENDORING.md")"
+if [ "$SHA" = "$OLD_SHA" ]; then
+ echo "Already at $SHA — nothing to do."
+ exit 0
+fi
+
+for path in src queries grammar.js tree-sitter.json LICENSE; do
+ rm -rf "$VENDOR_DIR/$path"
+ cp -R "$TMP/repo/$path" "$VENDOR_DIR/$path"
+done
+
+sed -i.bak "s/^Pinned commit: .*/Pinned commit: $SHA/" "$VENDOR_DIR/VENDORING.md"
+rm "$VENDOR_DIR/VENDORING.md.bak"
+
+"$ROOT/Scripts/build-parser.sh"
+
+echo "Updated $OLD_SHA -> $SHA"
+echo "Compare: https://github.com/tree-sitter-grammars/tree-sitter-zig/compare/$OLD_SHA...$SHA"
diff --git a/Zig.novaextension/Syntaxes/Zig.xml b/Zig.novaextension/Syntaxes/Zig.xml
new file mode 100644
index 0000000..40e8ec8
--- /dev/null
+++ b/Zig.novaextension/Syntaxes/Zig.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<syntax name="zig">
+ <meta>
+ <name>Zig</name>
+ <type>compiled</type>
+ <preferred-file-extension>zig</preferred-file-extension>
+ </meta>
+
+ <detectors>
+ <extension priority="1.0">zig,zon</extension>
+ <filename priority="1.0">build.zig</filename>
+ <filename priority="1.0">build.zig.zon</filename>
+ </detectors>
+
+ <indentation>
+ <increase>
+ <expression>(\{[^}\"']*$)|(\[[^\]\"']*$)|(\([^)\"']*$)</expression>
+ </increase>
+ <decrease>
+ <expression>^\s*[\}\]\)]</expression>
+ </decrease>
+ </indentation>
+
+ <comments>
+ <single>
+ <expression>//</expression>
+ </single>
+ </comments>
+
+ <brackets>
+ <pair open="{" close="}" />
+ <pair open="[" close="]" />
+ <pair open="(" close=")" />
+ <pair open="|" close="|" />
+ </brackets>
+
+ <surrounding-pairs>
+ <pair open="{" close="}" />
+ <pair open="[" close="]" />
+ <pair open="(" close=")" />
+ <pair open="'" close="'" />
+ <pair open="&quot;" close="&quot;" />
+ <pair open="|" close="|" />
+ </surrounding-pairs>
+
+ <injection>
+ <expression>^(zig|zon)$</expression>
+ </injection>
+
+ <tree-sitter>
+ <highlights />
+ <symbols />
+ <folds />
+ </tree-sitter>
+</syntax>
diff --git a/Zig.novaextension/Syntaxes/libtree-sitter-zig.dylib b/Zig.novaextension/Syntaxes/libtree-sitter-zig.dylib
new file mode 100755
index 0000000..91de187
--- /dev/null
+++ b/Zig.novaextension/Syntaxes/libtree-sitter-zig.dylib
Binary files differ
diff --git a/Zig.novaextension/de.lproj/strings.json b/Zig.novaextension/de.lproj/strings.json
new file mode 100644
index 0000000..3ebacf6
--- /dev/null
+++ b/Zig.novaextension/de.lproj/strings.json
@@ -0,0 +1,149 @@
+{
+ "Zig": "Zig",
+ "zig": "zig",
+ "Zig (LLDB DAP)": "Zig (LLDB DAP)",
+ "zls": "zls",
+
+ "Zig Package": "Zig-Paket",
+ "Build and run a Zig package from one Nova task configuration.": "Ein Zig-Paket aus einer einzigen Nova-Task-Konfiguration bauen und ausführen.",
+ "Working Directory": "Arbeitsverzeichnis",
+ "Workspace Root": "Projektverzeichnis",
+ "Run Step": "Ausführungsschritt",
+ "run": "run",
+ "install": "install",
+ "host": "host",
+ "The `zig build` step to execute for the Run action.": "Der `zig build`-Schritt, der für die Ausführen-Aktion ausgeführt wird.",
+ "The `zig build` step to execute for the Run action. Leave blank to run `zig build` with no step (Zig's default install step).": "Der `zig build`-Schritt für die Ausführen-Aktion. Leer lassen, um `zig build` ohne Schritt auszuführen (Zigs Standardschritt `install`).",
+ "Build Arguments": "Build-Argumente",
+ "Additional arguments appended after `zig build` for both Build and Run.": "Zusätzliche Argumente, die nach `zig build` sowohl für Bauen als auch Ausführen angehängt werden.",
+ "Additional arguments appended after `zig build`.": "Zusätzliche Argumente, die nach `zig build` angehängt werden.",
+ "Additional arguments appended after `zig build test`.": "Zusätzliche Argumente, die nach `zig build test` angehängt werden.",
+ "Program Arguments": "Programmargumente",
+ "Arguments passed after `--` to the built program.": "Argumente, die nach `--` an das gebaute Programm übergeben werden.",
+ "Arguments passed after `--` to the test runner.": "Argumente, die nach `--` an den Test-Runner übergeben werden.",
+ "Optimize": "Optimierung",
+ "Project Default": "Projektstandard",
+ "Passed as `-Doptimize=<mode>`. Leave on Project Default to let `build.zig` decide.": "Wird als `-Doptimize=<mode>` übergeben. Bei „Projektstandard“ entscheidet `build.zig`.",
+ "Passed as `-Doptimize=<mode>`. Debug is the recommended default for stepping through code.": "Wird als `-Doptimize=<mode>` übergeben. „Debug“ ist der empfohlene Standard zum Durchlaufen des Codes.",
+ "Passed as `-Doptimize=<mode>`.": "Wird als `-Doptimize=<mode>` übergeben.",
+ "Target": "Ziel",
+ "Cross-compile target triple, passed as `-Dtarget=`. Leave blank for the host.": "Cross-Compile-Ziel-Triplet, wird als `-Dtarget=` übergeben. Leer lassen für den Host.",
+ "User Options": "Benutzeroptionen",
+ "Custom `-D` flags. Each entry is `key` (boolean flag) or `key=value`.": "Eigene `-D`-Flags. Jeder Eintrag ist `key` (boolesches Flag) oder `key=value`.",
+ "Where to run the program. External Terminal launches the macOS Terminal app.": "Wo das Programm ausgeführt werden soll. „Externes Terminal“ startet die macOS-Terminal-App.",
+ "Zig Debug": "Zig-Debug",
+ "Build a Zig package in Debug mode and launch it under lldb-dap.": "Ein Zig-Paket im Debug-Modus bauen und unter lldb-dap starten.",
+ "Program": "Programm",
+ "zig-out/bin/app": "zig-out/bin/app",
+ "Path to the executable to debug. Relative paths are resolved against the working directory.": "Pfad zur zu debuggenden ausführbaren Datei. Relative Pfade werden gegen das Arbeitsverzeichnis aufgelöst.",
+ "Path to the executable to debug. Relative paths are resolved against the working directory. Leave blank to auto-detect from `build.zig.zon`.": "Pfad zur zu debuggenden ausführbaren Datei. Relative Pfade werden gegen das Arbeitsverzeichnis aufgelöst. Leer lassen, um aus `build.zig.zon` automatisch zu erkennen.",
+ "Additional arguments appended after `zig build -Doptimize=Debug`.": "Zusätzliche Argumente, die nach `zig build -Doptimize=Debug` angehängt werden.",
+ "Arguments passed to the debugged program.": "Argumente, die an das zu debuggende Programm übergeben werden.",
+ "Console": "Konsole",
+ "Internal Console": "Interne Konsole",
+ "Integrated Terminal": "Integriertes Terminal",
+ "External Terminal": "Externes Terminal",
+ "Where the debugged program should run.": "Wo das zu debuggende Programm ausgeführt werden soll.",
+ "Stop On Entry": "Beim Start anhalten",
+ "Pause immediately when the program starts.": "Sofort anhalten, wenn das Programm startet.",
+ "warning.terminal.launch_failed": "Zig-Task konnte im Terminal nicht gestartet werden.",
+ "warning.terminal.open_failed": "Terminal für die Zig-Task konnte nicht geöffnet werden.",
+
+ "name.language_server": "Zig Language Server",
+ "warning.zls.stopped_unexpectedly": "Der Zig Language Server wurde unerwartet beendet ({executable}).",
+ "warning.zls.start_failed": "Der Zig Language Server konnte unter {path} nicht gestartet werden.",
+ "name.extension": "Zig",
+ "task.current_file.name": "Aktuelle Zig-Datei",
+ "warning.clean.missing_cwd": "Wähle vor dem Bereinigen von Zig-Build-Artefakten einen Workspace oder ein Arbeitsverzeichnis aus.",
+ "warning.current_file.focus_editor_for_run": "Fokussiere einen Zig-Editor, bevor du „Aktuelle Zig-Datei“ ausführst.",
+ "warning.current_file.focus_editor_for_clean": "Fokussiere einen Zig-Editor, bevor du Artefakte der „Aktuelle Zig-Datei“ bereinigst.",
+
+ "warning.debug.choose_program": "Wähle vor dem Ausführen von Zig Debug einen Programmpfad aus.",
+
+ "Debug Adapter": "Debug-Adapter",
+ "Controls for the lldb-dap integration.": "Steuerelemente für die lldb-dap-Integration.",
+ "Enable Proxy Log": "Proxy-Protokoll aktivieren",
+ "Write lldb-dap-proxy traffic to a log file in the extension's storage directory. For extension development only.": "lldb-dap-Proxy-Datenverkehr in eine Protokolldatei im Speicherverzeichnis der Erweiterung schreiben. Nur für die Erweiterungsentwicklung.",
+ "Zig Test": "Zig-Test",
+ "Run `zig build test` with optional --test-filter and --summary controls.": "Führt `zig build test` mit optionalen --test-filter- und --summary-Steuerungen aus.",
+ "Test Filter": "Testfilter",
+ "substring of test name": "Teil eines Testnamens",
+ "Substring of the test name. Passed as `--test-filter`.": "Teil des Testnamens. Wird als `--test-filter` übergeben.",
+ "Summary": "Zusammenfassung",
+ "Default": "Standard",
+ "All": "Alle",
+ "Failures only": "Nur Fehler",
+ "None": "Keine",
+ "Controls `zig build`'s --summary verbosity.": "Steuert die Ausführlichkeit von `zig build --summary`.",
+ "Zig Watch": "Zig-Watch",
+ "Run `zig build --watch` and rebuild on file changes. Note: inline issues only update on the first build cycle — Nova's matchers do not re-arm for streaming output.": "Führt `zig build --watch` aus und baut bei Dateiänderungen neu. Hinweis: Inline-Probleme werden nur im ersten Build-Zyklus aktualisiert — Novas Matcher werden für Streaming-Ausgaben nicht erneut aktiviert.",
+ "Step": "Schritt",
+ "Build step to watch. Leave blank for `zig build`'s default install step.": "Build-Schritt, der überwacht werden soll. Leer lassen für Zigs Standardschritt `install`.",
+ "Debounce (ms)": "Entprellung (ms)",
+ "Passed as `--debounce <N>`. Leave blank for Zig's default.": "Wird als `--debounce <N>` übergeben. Leer lassen für Zigs Standard.",
+ "Incremental": "Inkrementell",
+ "On": "Ein",
+ "Off": "Aus",
+ "Toggle `-fincremental` / `-fno-incremental`.": "Schaltet `-fincremental` / `-fno-incremental` um.",
+ "warning.clean.unsafe_cwd": "Reinigung verweigert: Das Arbeitsverzeichnis muss innerhalb dieses Workspaces liegen.",
+ "warning.fmt.no_file": "Öffne eine gespeicherte Zig-Datei, bevor du „Aktuelle Zig-Datei formatieren“ ausführst.",
+ "warning.fmt.not_zig": "„Aktuelle Zig-Datei formatieren“ funktioniert nur mit Zig-Dateien.",
+ "warning.fmt.no_workspace": "Öffne einen Workspace, bevor du „Workspace formatieren“ ausführst.",
+ "warning.fmt.failed": "zig fmt ist fehlgeschlagen.",
+
+ "warning.zig.not-found": "zig wurde nicht gefunden. Stelle sicher, dass zig über PATH gefunden werden kann, oder gib den Pfad explizit an in den Erweiterungs- oder Projekteinstellungen.",
+ "warning.zls.not-found": "zls wurde nicht gefunden. Stelle sicher, dass zls über PATH gefunden werden kann, oder gib den Pfad explizit an in den Erweiterungs- oder Projekteinstellungen",
+ "warning.node.not-found": "node wurde nicht gefunden. Stelle sicher, dass node über PATH gefunden werden kann, oder gib den Pfad explizit an in den Erweiterungs- oder Projekteinstellungen",
+ "warning.lldb_dap.not-found": "lldb-dap wurde nicht gefunden. Installiere die Xcode Command line tools, oder gib den Pfad explizit an in den Erweiterungs- oder Projekteinstellungen.",
+
+ "tasks.buildrun.name": "Zig Build",
+ "tasks.buildrun.info": "Zig kompilieren und ausführen",
+
+ "tasks.debug.name": "Zig Debug",
+ "tasks.debug.info": "Zig mit Debug-Symbolen für LLDB kompilieren und ausführen",
+
+ "tasks.test.name": "Zig Test",
+ "tasks.test.info": "Tests ausführen, die im Zig-Projekt definiert sind",
+
+ "tasks.watch.name": "Zig Watch",
+ "tasks.watch.info": "Zig-Dateien beobachten und bei Änderungen autimatisch neu kompilieren",
+
+ "autotasks.title": "Automatisch erkannte Aufgaben",
+ "autotasks.current-file.name": "Aktuelle Zig-Datei ausführen",
+ "autotasks.buildstep.name": "zig build {step}",
+
+ "config.general.title": "Allgemein",
+ "config.zig.executable-path.title": "Pfad zu zig",
+ "config.zig.executable-path.info": "Leer lassen, um zig über die Umgebungsvariable PATH zu finden",
+
+ "config.zls.title": "Erweiterte Sprachfunktionen",
+ "config.zls.info": "Diagnose, Vervollständigung, Navigation und mehr",
+ "config.zls.enable.title": "ZLS aktivieren",
+ "config.zls.enable.info": "Für diese Funktion muss ZLS installiert sein",
+ "config.zls.build-on-save.title": "Beim Speichern bauen",
+ "config.zls.build-on-save.info": "Erlaube ZLS, den build / check-Runner beim Speichern aufzurufen",
+ "config.zls.log-communication.title": "ZLS-Protokoll",
+ "config.zls.log-communication.info": "Kommunikation zwischen ZLS und Nova in der Erweiterungskonsole protokollieren",
+ "config.zls.executable-path.title": "Pfad zu zls",
+ "config.zls.executable-path.info": "Leer lassen, um zls über die Umgebungsvariable PATH zu finden",
+
+ "config.debugging.title": "Debugging",
+ "config.debugging.info": "Einstellungen für die LLDB-Integration",
+ "config.debugging.lldb-dap-path.title": "Pfad zu lldb-dap",
+ "config.debugging.lldb-dap-path.info": "Leer lassen, um lldb-dap über das Xcode Command line tool xcrun oder über die Umgebungsvariable PATH zu finden",
+ "config.debugging.logging.title": "LLDB-Protokoll",
+ "config.debugging.logging.info": "Ausgabe des lldb-dap-Proxy im Erweiterungsverzeichnis protokollieren",
+
+ "config.tasks.title": "Aufgaben",
+ "config.tasks.info": "Aufgabenbezogene Einstellungen",
+
+ "config.tasks.autodiscover.title": "Aufgaben automatisch erkennen",
+ "config.tasks.autodiscover.info": "Erkenne Build Steps und füge sie automatisch in die Aufgabenliste hinzu",
+
+ "config.enabled": "Aktiviert",
+ "config.disabled": "Deaktiviert",
+ "config.inherit": "Globale Einstellung verwenden",
+
+ "workspaceconfig.inherit.info": "Leer lassen, um die globale Einstellung zu verwenden",
+ "workspaceconfig.inherit.placeholder": "Globale Einstellung"
+}
diff --git a/Zig.novaextension/en.lproj/strings.json b/Zig.novaextension/en.lproj/strings.json
new file mode 100644
index 0000000..0646258
--- /dev/null
+++ b/Zig.novaextension/en.lproj/strings.json
@@ -0,0 +1,144 @@
+{
+ "Zig": "Zig",
+ "zig": "zig",
+ "Zig (LLDB DAP)": "Zig (LLDB DAP)",
+ "zls": "zls",
+
+ "Working Directory": "Arbeitsverzeichnis",
+ "Workspace Root": "Workspace root",
+ "Run Step": "Ausführungsschritt",
+ "run": "run",
+ "install": "install",
+ "host": "host",
+ "The `zig build` step to execute for the Run action.": "Der `zig build`-Schritt, der für die Ausführen-Aktion ausgeführt wird.",
+ "The `zig build` step to execute for the Run action. Leave blank to run `zig build` with no step (Zig's default install step).": "Der `zig build`-Schritt für die Ausführen-Aktion. Leer lassen, um `zig build` ohne Schritt auszuführen (Zigs Standardschritt `install`).",
+ "Build Arguments": "Build-Argumente",
+ "Additional arguments appended after `zig build` for both Build and Run.": "Zusätzliche Argumente, die nach `zig build` sowohl für Bauen als auch Ausführen angehängt werden.",
+ "Additional arguments appended after `zig build`.": "Zusätzliche Argumente, die nach `zig build` angehängt werden.",
+ "Additional arguments appended after `zig build test`.": "Zusätzliche Argumente, die nach `zig build test` angehängt werden.",
+ "Program Arguments": "Programmargumente",
+ "Arguments passed after `--` to the built program.": "Argumente, die nach `--` an das gebaute Programm übergeben werden.",
+ "Arguments passed after `--` to the test runner.": "Argumente, die nach `--` an den Test-Runner übergeben werden.",
+ "Optimize": "Optimierung",
+ "Project Default": "Projektstandard",
+ "Passed as `-Doptimize=<mode>`. Leave on Project Default to let `build.zig` decide.": "Wird als `-Doptimize=<mode>` übergeben. Bei „Projektstandard“ entscheidet `build.zig`.",
+ "Passed as `-Doptimize=<mode>`. Debug is the recommended default for stepping through code.": "Wird als `-Doptimize=<mode>` übergeben. „Debug“ ist der empfohlene Standard zum Durchlaufen des Codes.",
+ "Passed as `-Doptimize=<mode>`.": "Wird als `-Doptimize=<mode>` übergeben.",
+ "Target": "Ziel",
+ "Cross-compile target triple, passed as `-Dtarget=`. Leave blank for the host.": "Cross-Compile-Ziel-Triplet, wird als `-Dtarget=` übergeben. Leer lassen für den Host.",
+ "User Options": "Benutzeroptionen",
+ "Custom `-D` flags. Each entry is `key` (boolean flag) or `key=value`.": "Eigene `-D`-Flags. Jeder Eintrag ist `key` (boolesches Flag) oder `key=value`.",
+ "Where to run the program. External Terminal launches the macOS Terminal app.": "Wo das Programm ausgeführt werden soll. „Externes Terminal“ startet die macOS-Terminal-App.",
+ "Zig Debug": "Zig-Debug",
+ "Build a Zig package in Debug mode and launch it under lldb-dap.": "Ein Zig-Paket im Debug-Modus bauen und unter lldb-dap starten.",
+ "Program": "Programm",
+ "zig-out/bin/app": "zig-out/bin/app",
+ "Path to the executable to debug. Relative paths are resolved against the working directory.": "Pfad zur zu debuggenden ausführbaren Datei. Relative Pfade werden gegen das Arbeitsverzeichnis aufgelöst.",
+ "Path to the executable to debug. Relative paths are resolved against the working directory. Leave blank to auto-detect from `build.zig.zon`.": "Pfad zur zu debuggenden ausführbaren Datei. Relative Pfade werden gegen das Arbeitsverzeichnis aufgelöst. Leer lassen, um aus `build.zig.zon` automatisch zu erkennen.",
+ "Additional arguments appended after `zig build -Doptimize=Debug`.": "Zusätzliche Argumente, die nach `zig build -Doptimize=Debug` angehängt werden.",
+ "Arguments passed to the debugged program.": "Argumente, die an das zu debuggende Programm übergeben werden.",
+ "Console": "Konsole",
+ "Internal Console": "Interne Konsole",
+ "Integrated Terminal": "Integriertes Terminal",
+ "External Terminal": "Externes Terminal",
+ "Where the debugged program should run.": "Wo das zu debuggende Programm ausgeführt werden soll.",
+ "Stop On Entry": "Beim Start anhalten",
+ "Pause immediately when the program starts.": "Sofort anhalten, wenn das Programm startet.",
+ "warning.terminal.launch_failed": "Zig-Task konnte im Terminal nicht gestartet werden.",
+ "warning.terminal.open_failed": "Terminal für die Zig-Task konnte nicht geöffnet werden.",
+
+ "name.language_server": "Zig Language Server",
+ "warning.zls.stopped_unexpectedly": "Der Zig Language Server wurde unerwartet beendet ({executable}).",
+ "warning.zls.start_failed": "Der Zig Language Server konnte unter {path} nicht gestartet werden.",
+ "name.extension": "Zig",
+ "task.current_file.name": "Aktuelle Zig-Datei",
+ "warning.clean.missing_cwd": "Wähle vor dem Bereinigen von Zig-Build-Artefakten einen Workspace oder ein Arbeitsverzeichnis aus.",
+
+ "warning.current_file.focus_editor_for_run": "Fokussiere einen Zig-Editor, bevor du „Aktuelle Zig-Datei“ ausführst.",
+ "warning.current_file.focus_editor_for_clean": "Fokussiere einen Zig-Editor, bevor du Artefakte der „Aktuelle Zig-Datei“ bereinigst.",
+
+ "warning.debug.choose_program": "Wähle vor dem Ausführen von Zig Debug einen Programmpfad aus.",
+
+ "Zig Test": "Zig-Test",
+ "Run `zig build test` with optional --test-filter and --summary controls.": "Führt `zig build test` mit optionalen --test-filter- und --summary-Steuerungen aus.",
+ "Test Filter": "Testfilter",
+ "substring of test name": "Teil eines Testnamens",
+ "Substring of the test name. Passed as `--test-filter`.": "Teil des Testnamens. Wird als `--test-filter` übergeben.",
+ "Summary": "Zusammenfassung",
+ "Default": "Standard",
+ "All": "Alle",
+ "Failures only": "Nur Fehler",
+ "None": "Keine",
+ "Controls `zig build`'s --summary verbosity.": "Steuert die Ausführlichkeit von `zig build --summary`.",
+ "Zig Watch": "Zig-Watch",
+ "Run `zig build --watch` and rebuild on file changes. Note: inline issues only update on the first build cycle — Nova's matchers do not re-arm for streaming output.": "Führt `zig build --watch` aus und baut bei Dateiänderungen neu. Hinweis: Inline-Probleme werden nur im ersten Build-Zyklus aktualisiert — Novas Matcher werden für Streaming-Ausgaben nicht erneut aktiviert.",
+ "Step": "Schritt",
+ "Build step to watch. Leave blank for `zig build`'s default install step.": "Build-Schritt, der überwacht werden soll. Leer lassen für Zigs Standardschritt `install`.",
+ "Debounce (ms)": "Entprellung (ms)",
+ "Passed as `--debounce <N>`. Leave blank for Zig's default.": "Wird als `--debounce <N>` übergeben. Leer lassen für Zigs Standard.",
+ "Incremental": "Inkrementell",
+ "On": "Ein",
+ "Off": "Aus",
+ "Toggle `-fincremental` / `-fno-incremental`.": "Schaltet `-fincremental` / `-fno-incremental` um.",
+ "warning.clean.unsafe_cwd": "Reinigung verweigert: Das Arbeitsverzeichnis muss innerhalb dieses Workspaces liegen.",
+ "warning.fmt.no_file": "Öffne eine gespeicherte Zig-Datei, bevor du „Aktuelle Zig-Datei formatieren“ ausführst.",
+ "warning.fmt.not_zig": "„Aktuelle Zig-Datei formatieren“ funktioniert nur mit Zig-Dateien.",
+ "warning.fmt.no_workspace": "Öffne einen Workspace, bevor du „Workspace formatieren“ ausführst.",
+ "warning.fmt.failed": "zig fmt ist fehlgeschlagen.",
+
+ "warning.zig.not-found": "zig executable not found. Make sure it can be found in the PATH or explicitly define the path in the extension or workspace settings.",
+ "warning.zls.not-found": "zls executable not found. Make sure zls can be found in the PATH or explicitly define the path in the extension or workspace settings.",
+ "warning.node.not-found": "node executable not found. Make sure node can be found in the PATH. Without it, debugging cannot work.",
+ "warning.lldb_dap.not-found": "lldb-dap executable not found. Install Xcode command line tools or explicitly define the path in the extension or workspace settings.",
+
+ "tasks.buildrun.name": "Zig build",
+ "tasks.buildrun.info": "Build and run Zig",
+
+ "tasks.debug.name": "Zig debug",
+ "tasks.debug.info": "Build and run Zig with debug symbols for use with LLDB",
+
+ "tasks.test.name": "Zig test",
+ "tasks.test.info": "Build and run tests defined in your Zig project",
+
+ "tasks.watch.name": "Zig watch",
+ "tasks.watch.info": "Monitor Zig files and recompile on changes",
+
+ "autotasks.title": "Auto-discovered tasks",
+ "autotasks.current-file.name": "Run active Zig file",
+ "autotasks.buildstep.name": "zig build {step}",
+
+ "config.general.title": "General",
+ "config.zig.executable-path.title": "Path to zig",
+ "config.zig.executable-path.info": "Leave empty to resolve zig via the environment variable PATH",
+
+ "config.zls.title": "Advanced language features",
+ "config.zls.info": "Diagnostics, completions, hover, navigation",
+ "config.zls.enable.title": "Enable ZLS",
+ "config.zls.enable.info": "ZLS needs to be installed for this to work",
+ "config.zls.build-on-save.title": "Bei Speichern bauen",
+ "config.zls.build-on-save.info": "Allow ZLS to call the build / check runner on save",
+ "config.zls.log-communication.title": "ZLS log",
+ "config.zls.log-communication.info": "Log communication between ZLS and Nova to the extension console",
+ "config.zls.executable-path.title": "Path to zls",
+ "config.zls.executable-path.info": "Leave empty to resolve zls via the environment variable PATH",
+
+ "config.debugging.title": "Debugging",
+ "config.debugging.info": "Settings for the LLDB integration",
+ "config.debugging.lldb-dap-path.title": "Path to lldb-dap",
+ "config.debugging.lldb-dap-path.info": "Leave empty to resolve lldb-dap via the Xcode Command line tool xcrun or via the environment variable PATH",
+ "config.debugging.logging.title": "LLDB log",
+ "config.debugging.logging.info": "Log output of the lldb-dap proxy to the extension directory",
+
+ "config.tasks.title": "Tasks",
+ "config.tasks.info": "Task-related settings",
+
+ "config.tasks.autodiscover.title": "Auto-discover tasks",
+ "config.tasks.autodiscover.info": "Discover build steps and add them to the list of tasks",
+
+ "config.enabled": "Enabled",
+ "config.disabled": "Disabled",
+ "config.inherit": "Inherit from Global",
+
+ "workspaceconfig.inherit.info": "Leave empty to inherit the global configuration",
+ "workspaceconfig.inherit.placeholder": "Global configuration"
+}
diff --git a/Zig.novaextension/extension.json b/Zig.novaextension/extension.json
new file mode 100644
index 0000000..34a9d02
--- /dev/null
+++ b/Zig.novaextension/extension.json
@@ -0,0 +1,634 @@
+{
+ "identifier": "at.dcz.nova-zig",
+ "organization": "David Czihak",
+ "name": "Zig",
+ "version": "0.1.8",
+ "license": "BSD-2-Clause",
+
+ "description": "Zig language support – ZLS, LLDB, Tree-Sitter grammar",
+ "categories": ["completions", "formatters", "issues", "languages", "tasks"],
+ "keywords": ["zig", "zon", "zls"],
+
+ "repository": "https://git.dcz.at/Zig.novaextension/",
+ "bugs": { "email": "bugs@dcz.at" },
+
+ "min_runtime": "10",
+
+ "entitlements": {
+ "process": true,
+ "filesystem": "readwrite"
+ },
+
+ "main": "main.js",
+
+ "activationEvents": [
+ "onLanguage:zig",
+ "onWorkspaceContains:*.zig",
+ "onWorkspaceContains:build.zig",
+ "onWorkspaceContains:build.zig.zon"
+ ],
+
+ "config": [
+ {
+ "type": "section",
+ "title": "config.general.title",
+ "children": [
+ {
+ "key": "at.dcz.nova-zig.toolchain.zig-path",
+ "title": "config.zig.executable-path.title",
+ "type": "path",
+ "placeholder": "zig",
+ "description": "config.zig.executable-path.info",
+ "allowFiles": true,
+ "allowFolders": false,
+ "filetype": "public.unix-executable"
+ }
+ ]
+ },
+ {
+ "type": "section",
+ "title": "config.zls.title",
+ "description": "config.zls.info",
+ "children": [
+ {
+ "key": "at.dcz.nova-zig.zls.enabled",
+ "title": "config.zls.enable.title",
+ "type": "boolean",
+ "default": true,
+ "description": "config.zls.enable.info"
+ },
+ {
+ "key": "at.dcz.nova-zig.toolchain.zls-path",
+ "title": "config.zls.executable-path.title",
+ "type": "path",
+ "placeholder": "zls",
+ "description": "config.zls.executable-path.info",
+ "allowFiles": true,
+ "allowFolders": false,
+ "filetype": "public.unix-executable"
+ },
+ {
+ "key": "at.dcz.nova-zig.zls.build-on-save",
+ "title": "config.zls.build-on-save.title",
+ "type": "boolean",
+ "default": false,
+ "description": "config.zls.build-on-save.info"
+ },
+ {
+ "key": "at.dcz.nova-zig.zls.debug",
+ "title": "config.zls.log-communication.title",
+ "type": "boolean",
+ "default": false,
+ "description": "config.zls.log-communication.info"
+ }
+ ]
+ },
+ {
+ "type": "section",
+ "title": "config.debugging.title",
+ "description": "config.debugging.info",
+ "children": [
+ {
+ "key": "at.dcz.nova-zig.toolchain.lldb-dap-path",
+ "title": "config.debugging.lldb-dap-path.title",
+ "type": "path",
+ "placeholder": "lldb-dap",
+ "description": "config.debugging.lldb-dap-path.info",
+ "allowFiles": true,
+ "allowFolders": false,
+ "filetype": "public.unix-executable"
+ },
+ {
+ "key": "at.dcz.nova-zig.debug-adapter.debug",
+ "title": "config.debugging.logging.title",
+ "type": "boolean",
+ "default": false,
+ "description": "config.debugging.logging.info"
+ }
+ ]
+ },
+ {
+ "type": "section",
+ "title": "config.tasks.title",
+ "description": "config.tasks.info",
+ "children": [
+ {
+ "key": "at.dcz.nova-zig.tasks.discover-steps",
+ "title": "config.tasks.autodiscover.title",
+ "type": "boolean",
+ "default": true,
+ "description": "config.tasks.autodiscover.info"
+ }
+ ]
+ }
+ ],
+
+ "configWorkspace": [
+ {
+ "type": "section",
+ "title": "config.general.title",
+ "children": [
+ {
+ "key": "at.dcz.nova-zig.toolchain.zig-path",
+ "title": "config.zig.executable-path.title",
+ "type": "path",
+ "placeholder": "workspaceconfig.inherit.placeholder",
+ "description": "workspaceconfig.inherit.info",
+ "allowFiles": true,
+ "allowFolders": false,
+ "filetype": "public.unix-executable"
+ }
+ ]
+ },
+ {
+ "type": "section",
+ "title": "config.zls.title",
+ "description": "config.zls.info",
+ "children": [
+ {
+ "key": "at.dcz.nova-zig.zls.enabled",
+ "title": "config.zls.enable.title",
+ "type": "enum",
+ "default": "inherit",
+ "values": [
+ ["inherit", "config.inherit"],
+ ["enabled", "config.enabled"],
+ ["disabled", "config.disabled"]
+ ],
+ "description": "config.zls.enable.info"
+ },
+ {
+ "key": "at.dcz.nova-zig.toolchain.zls-path",
+ "title": "config.zls.executable-path.title",
+ "type": "path",
+ "placeholder": "workspaceconfig.inherit.placeholder",
+ "description": "workspaceconfig.inherit.info",
+ "allowFiles": true,
+ "allowFolders": false,
+ "filetype": "public.unix-executable"
+ },
+ {
+ "key": "at.dcz.nova-zig.zls.build-on-save",
+ "title": "config.zls.build-on-save.title",
+ "type": "enum",
+ "default": "inherit",
+ "values": [
+ ["inherit", "config.inherit"],
+ ["enabled", "config.enabled"],
+ ["disabled", "config.disabled"]
+ ],
+ "description": "config.zls.build-on-save.info"
+ }
+ ]
+ },
+ {
+ "type": "section",
+ "title": "config.debugging.title",
+ "description": "config.debugging.info",
+ "children": [
+ {
+ "key": "at.dcz.nova-zig.toolchain.lldb-dap-path",
+ "title": "config.debugging.lldb-dap-path.title",
+ "type": "path",
+ "placeholder": "workspaceconfig.inherit.placeholder",
+ "description": "workspaceconfig.inherit.info",
+ "allowFiles": true,
+ "allowFolders": false,
+ "filetype": "public.unix-executable"
+ }
+ ]
+ },
+ {
+ "type": "section",
+ "title": "config.tasks.title",
+ "description": "config.tasks.info",
+ "children": [
+ {
+ "key": "at.dcz.nova-zig.tasks.discover-steps",
+ "title": "config.tasks.autodiscover.title",
+ "type": "enum",
+ "default": "inherit",
+ "values": [
+ ["inherit", "config.inherit"],
+ ["enabled", "config.enabled"],
+ ["disabled", "config.disabled"]
+ ],
+ "description": "config.tasks.autodiscover.info"
+ }
+ ]
+ }
+ ],
+
+ "breakpoints": [
+ {
+ "syntax": "zig"
+ }
+ ],
+
+ "debugAdapters": {
+ "zig-lldb-dap": {
+ "name": "LLDB",
+ "image": "zig-mark"
+ }
+ },
+
+ "taskTemplates": {
+ "zigBuildRun": {
+ "name": "tasks.buildrun.name",
+ "description": "tasks.buildrun.info",
+ "image": "zig-hex",
+ "tasks": {
+ "build": {
+ "resolve": "at.dcz.nova-zig.tasks",
+ "data": {
+ "type": "build"
+ }
+ },
+ "run": {
+ "resolve": "at.dcz.nova-zig.tasks",
+ "data": {
+ "type": "build-run"
+ }
+ },
+ "clean": {
+ "resolve": "at.dcz.nova-zig.tasks",
+ "data": {
+ "type": "clean"
+ }
+ }
+ },
+ "config": [
+ {
+ "key": "cwd",
+ "title": "Working Directory",
+ "type": "path",
+ "allowFiles": false,
+ "allowFolders": true,
+ "relative": true,
+ "placeholder": "Workspace Root"
+ },
+ {
+ "key": "runStep",
+ "title": "Run Step",
+ "type": "string",
+ "default": "",
+ "placeholder": "install",
+ "description": "The `zig build` step to execute for the Run action. Leave blank to run `zig build` with no step (Zig's default install step)."
+ },
+ {
+ "key": "optimize",
+ "title": "Optimize",
+ "type": "enum",
+ "default": "",
+ "values": [
+ ["", "Project Default"],
+ ["Debug", "Debug"],
+ ["ReleaseSafe", "ReleaseSafe"],
+ ["ReleaseFast", "ReleaseFast"],
+ ["ReleaseSmall", "ReleaseSmall"]
+ ],
+ "description": "Passed as `-Doptimize=<mode>`. Leave on Project Default to let `build.zig` decide."
+ },
+ {
+ "key": "target",
+ "title": "Target",
+ "type": "string",
+ "placeholder": "host",
+ "description": "Cross-compile target triple, passed as `-Dtarget=`. Leave blank for the host."
+ },
+ {
+ "key": "userOptions",
+ "title": "User Options",
+ "type": "stringArray",
+ "description": "Custom `-D` flags. Each entry is `key` (boolean flag) or `key=value`."
+ },
+ {
+ "key": "buildArgs",
+ "title": "Build Arguments",
+ "type": "stringArray",
+ "description": "Additional arguments appended after `zig build` for both Build and Run."
+ },
+ {
+ "key": "runArgs",
+ "title": "Program Arguments",
+ "type": "stringArray",
+ "description": "Arguments passed after `--` to the built program."
+ },
+ {
+ "key": "console",
+ "title": "Console",
+ "type": "enum",
+ "default": "internalConsole",
+ "values": [
+ ["internalConsole", "Internal Console"],
+ ["externalTerminal", "External Terminal"]
+ ],
+ "description": "Where to run the program. External Terminal launches the macOS Terminal app."
+ }
+ ]
+ },
+ "zigDebug": {
+ "name": "tasks.debug.name",
+ "description": "tasks.debug.info",
+ "image": "zig-hex",
+ "tasks": {
+ "build": {
+ "resolve": "at.dcz.nova-zig.tasks",
+ "data": {
+ "type": "build-debug"
+ }
+ },
+ "run": {
+ "resolve": "at.dcz.nova-zig.tasks",
+ "buildBeforeRunning": true,
+ "data": {
+ "type": "debug"
+ }
+ },
+ "clean": {
+ "resolve": "at.dcz.nova-zig.tasks",
+ "data": {
+ "type": "clean"
+ }
+ }
+ },
+ "config": [
+ {
+ "key": "cwd",
+ "title": "Working Directory",
+ "type": "path",
+ "allowFiles": false,
+ "allowFolders": true,
+ "relative": true,
+ "placeholder": "Workspace Root"
+ },
+ {
+ "key": "programPath",
+ "title": "Program",
+ "type": "path",
+ "allowFiles": true,
+ "allowFolders": false,
+ "relative": true,
+ "placeholder": "zig-out/bin/app",
+ "description": "Path to the executable to debug. Relative paths are resolved against the working directory. Leave blank to auto-detect from `build.zig.zon`."
+ },
+ {
+ "key": "optimize",
+ "title": "Optimize",
+ "type": "enum",
+ "default": "Debug",
+ "values": [
+ ["Debug", "Debug"],
+ ["ReleaseSafe", "ReleaseSafe"],
+ ["ReleaseFast", "ReleaseFast"],
+ ["ReleaseSmall", "ReleaseSmall"]
+ ],
+ "description": "Passed as `-Doptimize=<mode>`. Debug is the recommended default for stepping through code."
+ },
+ {
+ "key": "target",
+ "title": "Target",
+ "type": "string",
+ "placeholder": "host",
+ "description": "Cross-compile target triple, passed as `-Dtarget=`. Leave blank for the host."
+ },
+ {
+ "key": "userOptions",
+ "title": "User Options",
+ "type": "stringArray",
+ "description": "Custom `-D` flags. Each entry is `key` (boolean flag) or `key=value`."
+ },
+ {
+ "key": "buildArgs",
+ "title": "Build Arguments",
+ "type": "stringArray",
+ "description": "Additional arguments appended after `zig build`."
+ },
+ {
+ "key": "runArgs",
+ "title": "Program Arguments",
+ "type": "stringArray",
+ "description": "Arguments passed to the debugged program."
+ },
+ {
+ "key": "console",
+ "title": "Console",
+ "type": "enum",
+ "default": "internalConsole",
+ "values": [
+ ["internalConsole", "Internal Console"],
+ ["integratedTerminal", "Integrated Terminal"],
+ ["externalTerminal", "External Terminal"]
+ ],
+ "description": "Where the debugged program should run."
+ },
+ {
+ "key": "stopOnEntry",
+ "title": "Stop On Entry",
+ "type": "boolean",
+ "default": false,
+ "description": "Pause immediately when the program starts."
+ }
+ ]
+ },
+ "zigTest": {
+ "name": "tasks.test.name",
+ "description": "tasks.test.info",
+ "image": "zig-hex",
+ "tasks": {
+ "build": {
+ "resolve": "at.dcz.nova-zig.tasks",
+ "data": {
+ "type": "test-build"
+ }
+ },
+ "run": {
+ "resolve": "at.dcz.nova-zig.tasks",
+ "data": {
+ "type": "test-run"
+ }
+ },
+ "clean": {
+ "resolve": "at.dcz.nova-zig.tasks",
+ "data": {
+ "type": "clean"
+ }
+ }
+ },
+ "config": [
+ {
+ "key": "cwd",
+ "title": "Working Directory",
+ "type": "path",
+ "allowFiles": false,
+ "allowFolders": true,
+ "relative": true,
+ "placeholder": "Workspace Root"
+ },
+ {
+ "key": "testFilter",
+ "title": "Test Filter",
+ "type": "string",
+ "placeholder": "substring of test name",
+ "description": "Substring of the test name. Passed as `--test-filter`."
+ },
+ {
+ "key": "summary",
+ "title": "Summary",
+ "type": "enum",
+ "default": "",
+ "values": [
+ ["", "Default"],
+ ["all", "All"],
+ ["failures", "Failures only"],
+ ["none", "None"]
+ ],
+ "description": "Controls `zig build`'s --summary verbosity."
+ },
+ {
+ "key": "optimize",
+ "title": "Optimize",
+ "type": "enum",
+ "default": "",
+ "values": [
+ ["", "Project Default"],
+ ["Debug", "Debug"],
+ ["ReleaseSafe", "ReleaseSafe"],
+ ["ReleaseFast", "ReleaseFast"],
+ ["ReleaseSmall", "ReleaseSmall"]
+ ],
+ "description": "Passed as `-Doptimize=<mode>`."
+ },
+ {
+ "key": "target",
+ "title": "Target",
+ "type": "string",
+ "placeholder": "host",
+ "description": "Cross-compile target triple, passed as `-Dtarget=`. Leave blank for the host."
+ },
+ {
+ "key": "userOptions",
+ "title": "User Options",
+ "type": "stringArray",
+ "description": "Custom `-D` flags. Each entry is `key` (boolean flag) or `key=value`."
+ },
+ {
+ "key": "buildArgs",
+ "title": "Build Arguments",
+ "type": "stringArray",
+ "description": "Additional arguments appended after `zig build test`."
+ },
+ {
+ "key": "runArgs",
+ "title": "Program Arguments",
+ "type": "stringArray",
+ "description": "Arguments passed after `--` to the test runner."
+ }
+ ]
+ },
+ "zigWatch": {
+ "name": "tasks.watch.name",
+ "description": "tasks.watch.info",
+ "image": "zig-hex",
+ "tasks": {
+ "build": {
+ "resolve": "at.dcz.nova-zig.tasks",
+ "data": {
+ "type": "watch"
+ }
+ },
+ "clean": {
+ "resolve": "at.dcz.nova-zig.tasks",
+ "data": {
+ "type": "clean"
+ }
+ }
+ },
+ "config": [
+ {
+ "key": "cwd",
+ "title": "Working Directory",
+ "type": "path",
+ "allowFiles": false,
+ "allowFolders": true,
+ "relative": true,
+ "placeholder": "Workspace Root"
+ },
+ {
+ "key": "step",
+ "title": "Step",
+ "type": "string",
+ "default": "",
+ "placeholder": "install",
+ "description": "Build step to watch. Leave blank for `zig build`'s default install step."
+ },
+ {
+ "key": "debounceMs",
+ "title": "Debounce (ms)",
+ "type": "number",
+ "description": "Passed as `--debounce <N>`. Leave blank for Zig's default."
+ },
+ {
+ "key": "incremental",
+ "title": "Incremental",
+ "type": "enum",
+ "default": "",
+ "values": [
+ ["", "Default"],
+ ["on", "On"],
+ ["off", "Off"]
+ ],
+ "description": "Toggle `-fincremental` / `-fno-incremental`."
+ },
+ {
+ "key": "optimize",
+ "title": "Optimize",
+ "type": "enum",
+ "default": "",
+ "values": [
+ ["", "Project Default"],
+ ["Debug", "Debug"],
+ ["ReleaseSafe", "ReleaseSafe"],
+ ["ReleaseFast", "ReleaseFast"],
+ ["ReleaseSmall", "ReleaseSmall"]
+ ],
+ "description": "Passed as `-Doptimize=<mode>`."
+ },
+ {
+ "key": "target",
+ "title": "Target",
+ "type": "string",
+ "placeholder": "host",
+ "description": "Cross-compile target triple, passed as `-Dtarget=`. Leave blank for the host."
+ },
+ {
+ "key": "userOptions",
+ "title": "User Options",
+ "type": "stringArray",
+ "description": "Custom `-D` flags. Each entry is `key` (boolean flag) or `key=value`."
+ },
+ {
+ "key": "buildArgs",
+ "title": "Build Arguments",
+ "type": "stringArray",
+ "description": "Additional arguments appended after `zig build`."
+ }
+ ]
+ }
+ },
+
+ "issueMatchers": {
+ "zig.compiler": {
+ "pattern": [
+ {
+ "regexp": "^(.+?):(\\d+):(\\d+):\\s*(error|warning):\\s*(.+)$",
+ "file": 1,
+ "line": 2,
+ "column": 3,
+ "severity": 4,
+ "message": 5
+ }
+ ]
+ }
+ }
+}
diff --git a/Zig.novaextension/extension.png b/Zig.novaextension/extension.png
new file mode 100644
index 0000000..73e0d47
--- /dev/null
+++ b/Zig.novaextension/extension.png
Binary files differ
diff --git a/Zig.novaextension/extension@2x.png b/Zig.novaextension/extension@2x.png
new file mode 100644
index 0000000..c3600ab
--- /dev/null
+++ b/Zig.novaextension/extension@2x.png
Binary files differ