aboutsummaryrefslogtreecommitdiff
path: root/Scripts/lldb-dap-proxy.pl
diff options
context:
space:
mode:
authorDavid Czihak <git@dcz.at>2026-05-10 19:21:33 +0200
committerDavid Czihak <git@dcz.at>2026-05-10 19:21:33 +0200
commitb80b9c1f82585677a7c042557576c41b1670d259 (patch)
tree9a741dfd7725205dba35b42bc6d5a6a7e084ced0 /Scripts/lldb-dap-proxy.pl
parent33ea57ddd69f35f3f2db64a1a2d31b410ed7afb2 (diff)
Chore: Move extension bundle into Zig.novaextension/ subdirectory
Separates Nova extension resources from development-only items. Development items (ISSUES.md, vendor/, examples/) remain at the repo root. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'Scripts/lldb-dap-proxy.pl')
-rw-r--r--Scripts/lldb-dap-proxy.pl224
1 files changed, 0 insertions, 224 deletions
diff --git a/Scripts/lldb-dap-proxy.pl b/Scripts/lldb-dap-proxy.pl
deleted file mode 100644
index beb6eaa..0000000
--- a/Scripts/lldb-dap-proxy.pl
+++ /dev/null
@@ -1,224 +0,0 @@
-#!/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} // '');
-}