@@ -202,7 +202,6 @@ def dap_setup bytes
202202 send_event 'initialized'
203203 puts <<~WELCOME
204204 Ruby REPL: You can run any Ruby expression here.
205- Note that output to the STDOUT/ERR printed on the TERMINAL.
206205 [experimental]
207206 `,COMMAND` runs `COMMAND` debug command (ex: `,info`).
208207 `,help` to list all debug commands.
@@ -298,6 +297,7 @@ def process
298297 process_request ( req )
299298 end
300299 ensure
300+ restore_debuggee_stdio
301301 send_event :terminated unless @sock . closed?
302302 end
303303
@@ -314,6 +314,7 @@ def process_request req
314314 UI_DAP . local_fs_map_set req . dig ( 'arguments' , 'localfs' ) || req . dig ( 'arguments' , 'localfsMap' ) || true
315315 @nonstop = true
316316
317+ capture_debuggee_stdio
317318 load_extensions req
318319
319320 when 'attach'
@@ -326,6 +327,7 @@ def process_request req
326327 @nonstop = false
327328 end
328329
330+ capture_debuggee_stdio
329331 load_extensions req
330332
331333 when 'configurationDone'
@@ -397,6 +399,7 @@ def process_request req
397399 terminate = args . fetch ( "terminateDebuggee" , false )
398400
399401 SESSION . clear_all_breakpoints
402+ restore_debuggee_stdio
400403 send_response req
401404
402405 if SESSION . in_subsession?
@@ -506,6 +509,56 @@ def puts result = ""
506509 send_event 'output' , category : 'console' , output : "#{ result &.chomp } \n "
507510 end
508511
512+ def capture_debuggee_stdio
513+ return if @stdio_captured
514+
515+ @original_stdout = $stdout
516+ @original_stderr = $stderr
517+
518+ @stdout_reader , @stdout_writer = IO . pipe
519+ @stderr_reader , @stderr_writer = IO . pipe
520+
521+ @stdout_writer . sync = true
522+ @stderr_writer . sync = true
523+
524+ $stdout = @stdout_writer
525+ $stderr = @stderr_writer
526+ @stdio_captured = true
527+
528+ @stdout_monitor = start_monitor_thread ( @stdout_reader , 'stdout' )
529+ @stderr_monitor = start_monitor_thread ( @stderr_reader , 'stderr' )
530+ end
531+
532+ def restore_debuggee_stdio
533+ return unless @stdio_captured
534+
535+ $stdout = @original_stdout if @original_stdout
536+ $stderr = @original_stderr if @original_stderr
537+
538+ [ @stdout_writer , @stderr_writer ]
539+ . filter { |writer | !writer &.closed? }
540+ . each ( &:close )
541+
542+ [ @stdout_monitor , @stderr_monitor ] . each { |monitor | monitor &.join }
543+ [ @stdout_reader , @stderr_reader ] . each { |reader | reader &.close unless reader &.closed? }
544+
545+ @stdio_captured = false
546+ end
547+
548+ private
549+
550+ def start_monitor_thread ( reader , category )
551+ Thread . new do
552+ reader . each_line do |line |
553+ send_event 'output' , category : category , output : line
554+ end
555+ rescue IOError , Errno ::EBADF
556+ # Pipe closed, exit gracefully
557+ end
558+ end
559+
560+ public
561+
509562 def ignore_output_on_suspend?
510563 true
511564 end
0 commit comments