diff options
| -rw-r--r-- | CHANGELOG.md | 7 | ||||
| -rw-r--r-- | README.md | 2 | ||||
| -rw-r--r-- | Scripts/lldb-dap-proxy.pl | 23 | ||||
| -rw-r--r-- | Scripts/main.js | 34 | ||||
| -rw-r--r-- | extension.json | 31 |
5 files changed, 86 insertions, 11 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 59dfffe..ef55853 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md | |||
| @@ -1,5 +1,12 @@ | |||
| 1 | # Changelog | 1 | # Changelog |
| 2 | 2 | ||
| 3 | ## 0.1.8 — 2026-05-07 | ||
| 4 | |||
| 5 | - Added a setting to write an LLDB proxy log file for debugging the debug adapter. | ||
| 6 | - Improved ZLS error logging in the Extension Console. | ||
| 7 | - Updated German localisation. | ||
| 8 | - Clarified third-party licences for bundled assets and grammar. | ||
| 9 | |||
| 3 | ## 0.1.7 — 2026-05-07 | 10 | ## 0.1.7 — 2026-05-07 |
| 4 | 11 | ||
| 5 | - Initial release: syntax highlighting, ZLS integration, task templates, and LLDB debugging. | 12 | - Initial release: syntax highlighting, ZLS integration, task templates, and LLDB debugging. |
| @@ -14,6 +14,8 @@ Zig language support – ZLS, LLDB, Tree-Sitter grammar | |||
| 14 | 14 | ||
| 15 | A personal project to learn Zig without leaving Nova and without giving up the comfort of language server and debugging features. | 15 | A personal project to learn Zig without leaving Nova and without giving up the comfort of language server and debugging features. |
| 16 | 16 | ||
| 17 | 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. | ||
| 18 | |||
| 17 | ## Features | 19 | ## Features |
| 18 | 20 | ||
| 19 | ### Editing | 21 | ### Editing |
diff --git a/Scripts/lldb-dap-proxy.pl b/Scripts/lldb-dap-proxy.pl index 5f545b1..beb6eaa 100644 --- a/Scripts/lldb-dap-proxy.pl +++ b/Scripts/lldb-dap-proxy.pl | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | #!/usr/bin/perl | 1 | #!/usr/bin/perl |
| 2 | use strict; | 2 | use strict; |
| 3 | use warnings; | 3 | use warnings; |
| 4 | use Fcntl qw(O_WRONLY O_APPEND O_CREAT O_NOFOLLOW); | ||
| 4 | use IPC::Open3; | 5 | use IPC::Open3; |
| 5 | use IO::Select; | 6 | use IO::Select; |
| 6 | use JSON::PP; | 7 | use JSON::PP; |
| @@ -13,7 +14,7 @@ my @adapter_args = @ARGV > 2 ? @ARGV[2 .. $#ARGV] : (); | |||
| 13 | sub log_msg { | 14 | sub log_msg { |
| 14 | return unless defined $log_path; | 15 | return unless defined $log_path; |
| 15 | my ($msg) = @_; | 16 | my ($msg) = @_; |
| 16 | open(my $fh, '>>', $log_path) or return; | 17 | sysopen(my $fh, $log_path, O_WRONLY | O_APPEND | O_CREAT | O_NOFOLLOW, 0600) or return; |
| 17 | my @t = gmtime(time); | 18 | my @t = gmtime(time); |
| 18 | printf $fh "[%04d-%02d-%02dT%02d:%02d:%02dZ] %s\n", | 19 | printf $fh "[%04d-%02d-%02dT%02d:%02d:%02dZ] %s\n", |
| 19 | $t[5] + 1900, $t[4] + 1, $t[3], $t[2], $t[1], $t[0], $msg; | 20 | $t[5] + 1900, $t[4] + 1, $t[3], $t[2], $t[1], $t[0], $msg; |
| @@ -41,8 +42,8 @@ if ($@) { | |||
| 41 | 42 | ||
| 42 | binmode($_, ':raw') for \*STDIN, \*STDOUT, $child_in, $child_out, $child_err; | 43 | binmode($_, ':raw') for \*STDIN, \*STDOUT, $child_in, $child_out, $child_err; |
| 43 | 44 | ||
| 44 | $SIG{INT} = sub { kill 'INT', $pid }; | 45 | $SIG{INT} = sub { log_msg('received SIGINT'); kill 'INT', $pid }; |
| 45 | $SIG{TERM} = sub { kill 'TERM', $pid }; | 46 | $SIG{TERM} = sub { log_msg('received SIGTERM'); kill 'TERM', $pid }; |
| 46 | 47 | ||
| 47 | my $json = JSON::PP->new->utf8; | 48 | my $json = JSON::PP->new->utf8; |
| 48 | 49 | ||
| @@ -63,8 +64,15 @@ LOOP: while ($sel->count > 0) { | |||
| 63 | unless (defined $n && $n > 0) { | 64 | unless (defined $n && $n > 0) { |
| 64 | $sel->remove($fh); | 65 | $sel->remove($fh); |
| 65 | my $fn = fileno($fh); | 66 | my $fn = fileno($fh); |
| 66 | close($child_in) if $fn == $stdin_fn; | 67 | if ($fn == $stdin_fn) { |
| 67 | last LOOP if $fn == $child_out_fn; # adapter stdout closing ends the session | 68 | log_msg('stdin closed'); |
| 69 | close($child_in); | ||
| 70 | } elsif ($fn == $child_out_fn) { | ||
| 71 | log_msg('adapter stdout closed'); | ||
| 72 | last LOOP; | ||
| 73 | } else { | ||
| 74 | log_msg('adapter stderr closed'); | ||
| 75 | } | ||
| 68 | next; | 76 | next; |
| 69 | } | 77 | } |
| 70 | my $fn = fileno($fh); | 78 | my $fn = fileno($fh); |
| @@ -82,6 +90,7 @@ LOOP: while ($sel->count > 0) { | |||
| 82 | } | 90 | } |
| 83 | 91 | ||
| 84 | waitpid($pid, 0); | 92 | waitpid($pid, 0); |
| 93 | log_msg(sprintf 'adapter exited status=%d signal=%d', $? >> 8, $? & 127); | ||
| 85 | # $? encodes exit code in the high byte and terminating signal in the low 7 bits. | 94 | # $? encodes exit code in the high byte and terminating signal in the low 7 bits. |
| 86 | # Re-raise the signal so the parent sees a signal-killed exit rather than exit(0). | 95 | # Re-raise the signal so the parent sees a signal-killed exit rather than exit(0). |
| 87 | my $signal = $? & 127; | 96 | my $signal = $? & 127; |
| @@ -90,6 +99,7 @@ if ($signal) { | |||
| 90 | kill $signal, $$; | 99 | kill $signal, $$; |
| 91 | sleep 1; | 100 | sleep 1; |
| 92 | } | 101 | } |
| 102 | log_msg(sprintf 'proxy exit code=%d', $? >> 8); | ||
| 93 | exit($? >> 8); | 103 | exit($? >> 8); |
| 94 | 104 | ||
| 95 | sub flush_dap { | 105 | sub flush_dap { |
| @@ -119,7 +129,7 @@ sub flush_dap { | |||
| 119 | syswrite($dest, 'Content-Length: ' . length($out) . "\r\n\r\n"); | 129 | syswrite($dest, 'Content-Length: ' . length($out) . "\r\n\r\n"); |
| 120 | syswrite($dest, $out); | 130 | syswrite($dest, $out); |
| 121 | } else { | 131 | } else { |
| 122 | log_msg('non-json message forwarded'); | 132 | log_msg(sprintf 'non-json message forwarded%s', $@ ? " error=$@" : ' reason=not-a-hash'); |
| 123 | syswrite($dest, 'Content-Length: ' . $body_len . "\r\n\r\n"); | 133 | syswrite($dest, 'Content-Length: ' . $body_len . "\r\n\r\n"); |
| 124 | syswrite($dest, $body); | 134 | syswrite($dest, $body); |
| 125 | } | 135 | } |
| @@ -157,6 +167,7 @@ sub rewrite_client_message { | |||
| 157 | command => $command, | 167 | command => $command, |
| 158 | arguments => $msg->{arguments}, | 168 | arguments => $msg->{arguments}, |
| 159 | }; | 169 | }; |
| 170 | log_msg(sprintf 'client seq map %s=>%s', $orig, $rewrite); | ||
| 160 | } | 171 | } |
| 161 | $msg->{seq} = $rewrite; | 172 | $msg->{seq} = $rewrite; |
| 162 | } | 173 | } |
diff --git a/Scripts/main.js b/Scripts/main.js index 1058dfe..6f0a54e 100644 --- a/Scripts/main.js +++ b/Scripts/main.js | |||
| @@ -12,6 +12,7 @@ const CONFIG_KEYS = { | |||
| 12 | zlsEnabled: `${EXTENSION_ID}.zls.enabled`, | 12 | zlsEnabled: `${EXTENSION_ID}.zls.enabled`, |
| 13 | zlsBuildOnSave: `${EXTENSION_ID}.zls.build-on-save`, | 13 | zlsBuildOnSave: `${EXTENSION_ID}.zls.build-on-save`, |
| 14 | zlsDebug: `${EXTENSION_ID}.zls.debug`, | 14 | zlsDebug: `${EXTENSION_ID}.zls.debug`, |
| 15 | lldbDapDebug: `${EXTENSION_ID}.debug-adapter.debug`, | ||
| 15 | }; | 16 | }; |
| 16 | 17 | ||
| 17 | let languageServer = null; | 18 | let languageServer = null; |
| @@ -264,7 +265,7 @@ function lldbDapProxyPath() { | |||
| 264 | } | 265 | } |
| 265 | 266 | ||
| 266 | function debugAdapterLogPath() { | 267 | function debugAdapterLogPath() { |
| 267 | return "/tmp/zig-lldb-dap-proxy.log"; | 268 | return nova.path.join(nova.extension.globalStoragePath, "lldb-dap-proxy.log"); |
| 268 | } | 269 | } |
| 269 | 270 | ||
| 270 | function issueNormalizerScriptPath() { | 271 | function issueNormalizerScriptPath() { |
| @@ -475,6 +476,21 @@ class ZigLanguageServer { | |||
| 475 | clientOptions | 476 | clientOptions |
| 476 | ); | 477 | ); |
| 477 | 478 | ||
| 479 | client.onNotification("window/logMessage", ({ type, message }) => { | ||
| 480 | // type: 1=Error, 2=Warning, 3=Info, 4=Log | ||
| 481 | const enriched = | ||
| 482 | message === "ParseError" | ||
| 483 | ? "ParseError — ZLS could not fully parse the Zig source (normal while editing)" | ||
| 484 | : message; | ||
| 485 | if (type === 1) { | ||
| 486 | console.error(`[ZLS] ${enriched}`); | ||
| 487 | } else if (type === 2) { | ||
| 488 | console.warn(`[ZLS] ${enriched}`); | ||
| 489 | } else if (debugLogs) { | ||
| 490 | console.log(`[ZLS] ${enriched}`); | ||
| 491 | } | ||
| 492 | }); | ||
| 493 | |||
| 478 | this.clientStopDisposable = client.onDidStop((error) => { | 494 | this.clientStopDisposable = client.onDidStop((error) => { |
| 479 | if (error) { | 495 | if (error) { |
| 480 | console.error(`[${LANGUAGE_CLIENT_ID}] ${error.message}`); | 496 | console.error(`[${LANGUAGE_CLIENT_ID}] ${error.message}`); |
| @@ -806,12 +822,24 @@ class ZigTaskAssistant { | |||
| 806 | const action = new TaskDebugAdapterAction("zig-lldb-dap"); | 822 | const action = new TaskDebugAdapterAction("zig-lldb-dap"); |
| 807 | action.transport = "stdio"; | 823 | action.transport = "stdio"; |
| 808 | action.command = "/usr/bin/perl"; | 824 | action.command = "/usr/bin/perl"; |
| 809 | action.args = [lldbDapProxyPath(), lldbDapPath, debugAdapterLogPath()]; | 825 | const debugLog = getBooleanConfigValue(CONFIG_KEYS.lldbDapDebug, false); |
| 826 | let logPath; | ||
| 827 | if (debugLog) { | ||
| 828 | try { | ||
| 829 | const p = debugAdapterLogPath(); | ||
| 830 | const dir = nova.path.dirname(p); | ||
| 831 | if (!nova.fs.stat(dir)) nova.fs.mkdir(dir); | ||
| 832 | logPath = p; | ||
| 833 | } catch (_) {} | ||
| 834 | } | ||
| 835 | action.args = logPath | ||
| 836 | ? [lldbDapProxyPath(), lldbDapPath, logPath] | ||
| 837 | : [lldbDapProxyPath(), lldbDapPath]; | ||
| 810 | action.debugRequest = "launch"; | 838 | action.debugRequest = "launch"; |
| 811 | action.env = { | 839 | action.env = { |
| 812 | DYLD_FRAMEWORK_PATH: lldbFrameworkPaths().join(":"), | 840 | DYLD_FRAMEWORK_PATH: lldbFrameworkPaths().join(":"), |
| 813 | NOVA_ZIG_LLDB_DAP_PATH: lldbDapPath, | 841 | NOVA_ZIG_LLDB_DAP_PATH: lldbDapPath, |
| 814 | NOVA_ZIG_DEBUG_LOG: debugAdapterLogPath(), | 842 | ...(logPath ? { NOVA_ZIG_DEBUG_LOG: logPath } : {}), |
| 815 | }; | 843 | }; |
| 816 | action.debugArgs = { | 844 | action.debugArgs = { |
| 817 | program: programPath, | 845 | program: programPath, |
diff --git a/extension.json b/extension.json index f9e76e9..656a218 100644 --- a/extension.json +++ b/extension.json | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | "name": "Zig", | 3 | "name": "Zig", |
| 4 | "organization": "David Czihak", | 4 | "organization": "David Czihak", |
| 5 | "description": "Zig language support – ZLS, LLDB, Tree-Sitter grammar", | 5 | "description": "Zig language support – ZLS, LLDB, Tree-Sitter grammar", |
| 6 | "version": "0.1.7", | 6 | "version": "0.1.8", |
| 7 | "license": "BSD 2-Clause License", | 7 | "license": "BSD 2-Clause License", |
| 8 | "categories": ["languages", "completions", "formatters", "tasks", "issues"], | 8 | "categories": ["languages", "completions", "formatters", "tasks", "issues"], |
| 9 | "keywords": ["zig", "zon", "zls"], | 9 | "keywords": ["zig", "zon", "zls"], |
| @@ -19,7 +19,7 @@ | |||
| 19 | ], | 19 | ], |
| 20 | "entitlements": { | 20 | "entitlements": { |
| 21 | "process": true, | 21 | "process": true, |
| 22 | "filesystem": "readonly" | 22 | "filesystem": "readwrite" |
| 23 | }, | 23 | }, |
| 24 | "debugAdapters": { | 24 | "debugAdapters": { |
| 25 | "zig-lldb-dap": { | 25 | "zig-lldb-dap": { |
| @@ -88,6 +88,20 @@ | |||
| 88 | "description": "Log ZLS traffic to the Extension Console while developing the extension." | 88 | "description": "Log ZLS traffic to the Extension Console while developing the extension." |
| 89 | } | 89 | } |
| 90 | ] | 90 | ] |
| 91 | }, | ||
| 92 | { | ||
| 93 | "type": "section", | ||
| 94 | "title": "Debug Adapter", | ||
| 95 | "description": "Controls for the lldb-dap integration.", | ||
| 96 | "children": [ | ||
| 97 | { | ||
| 98 | "key": "at.dcz.nova-zig.debug-adapter.debug", | ||
| 99 | "title": "Enable Proxy Log", | ||
| 100 | "type": "boolean", | ||
| 101 | "default": false, | ||
| 102 | "description": "Write lldb-dap-proxy traffic to a log file in the extension's storage directory. For extension development only." | ||
| 103 | } | ||
| 104 | ] | ||
| 91 | } | 105 | } |
| 92 | ], | 106 | ], |
| 93 | "configWorkspace": [ | 107 | "configWorkspace": [ |
| @@ -143,6 +157,19 @@ | |||
| 143 | "description": "Log ZLS traffic to the Extension Console while developing the extension." | 157 | "description": "Log ZLS traffic to the Extension Console while developing the extension." |
| 144 | } | 158 | } |
| 145 | ] | 159 | ] |
| 160 | }, | ||
| 161 | { | ||
| 162 | "type": "section", | ||
| 163 | "title": "Debug Adapter", | ||
| 164 | "description": "Controls for the lldb-dap integration.", | ||
| 165 | "children": [ | ||
| 166 | { | ||
| 167 | "key": "at.dcz.nova-zig.debug-adapter.debug", | ||
| 168 | "title": "Enable Proxy Log", | ||
| 169 | "type": "boolean", | ||
| 170 | "description": "Write lldb-dap-proxy traffic to a log file in the extension's storage directory. For extension development only." | ||
| 171 | } | ||
| 172 | ] | ||
| 146 | } | 173 | } |
| 147 | ], | 174 | ], |
| 148 | "issueMatchers": { | 175 | "issueMatchers": { |
