class Byebug::DAP::CommandProcessor

Processes thread-specific commands and handles Byebug/TracePoint events.

Attributes

context[R]

The thread context. @return [gem:byebug:Byebug::Context]

last_exception[R]

The last exception that occured. @return [std:Exception]

pause_requested[W]

Indicates that the client requested a pause. @return [Boolean] @note This should only be set by {Command::Pause} @api private

Public Class Methods

new(context, session) click to toggle source

Create a new command processor. @param context [gem:byebug:Byebug::Context] the thread context @param session [Session] the debugging session @note This should only be used by Byebug internals @api private

# File lib/byebug/dap/command_processor.rb, line 38
def initialize(context, session)
  @context = context
  @session = session
  @requests = Channel.new
  @exec_mu = Mutex.new
  @exec_ch = Channel.new
end

Public Instance Methods

<<(message) click to toggle source

Send a message to the thread context. @param message the message to send @note Raises a {TimeoutError timeout error} after 1 second if the thread is not paused or not responding.

# File lib/byebug/dap/command_processor.rb, line 54
def <<(message)
  @requests.push(message, timeout: 1) { raise TimeoutError.new(context) }
end
at_breakpoint(breakpoint) click to toggle source

Breakpoint handler. @note This should only be called by Byebug internals @api private

# File lib/byebug/dap/command_processor.rb, line 109
def at_breakpoint(breakpoint)
  @last_breakpoint = breakpoint
end
at_catchpoint(exception) click to toggle source

Catchpoint handler. @note This should only be called by Byebug internals @api private

# File lib/byebug/dap/command_processor.rb, line 116
def at_catchpoint(exception)
  @last_exception = exception
end
at_end() click to toggle source

End of class/module handler. @note This should only be called by Byebug internals @api private

# File lib/byebug/dap/command_processor.rb, line 87
def at_end
  stopped!
end
at_line() click to toggle source

Line handler. @note This should only be called by Byebug internals @api private

# File lib/byebug/dap/command_processor.rb, line 80
def at_line
  stopped!
end
at_return(return_value) click to toggle source

Return handler. @note This should only be called by Byebug internals @api private

# File lib/byebug/dap/command_processor.rb, line 94
def at_return(return_value)
  @at_return = return_value
  stopped!
end
at_tracing() click to toggle source

Tracing handler. @note This should only be called by Byebug internals @api private

# File lib/byebug/dap/command_processor.rb, line 102
def at_tracing
  # @session.puts "Tracing: #{context.full_location}"
end
execute(&block) click to toggle source

Execute a code block in the thread. @yield the code block to execute @note This calls {#<<} and thus may raise a {TimeoutError timeout error}.

# File lib/byebug/dap/command_processor.rb, line 61
def execute(&block)
  raise "Block required" unless block_given?

  r, err = nil, nil
  @exec_mu.synchronize {
    self << block
    r, err = @exec_ch.pop
  }

  if err
    raise err
  else
    r
  end
end
log(*args) click to toggle source

(see Session#log)

# File lib/byebug/dap/command_processor.rb, line 47
def log(*args)
  @session.log(*args)
end

Private Instance Methods

logpoint!() click to toggle source
# File lib/byebug/dap/command_processor.rb, line 187
def logpoint!
  return false unless @last_breakpoint

  breakpoint, @last_breakpoint = @last_breakpoint, nil
  expr = @session.get_log_point(breakpoint)
  return false unless expr

  binding = @context.frame._binding
  msg = expr.gsub(/\{([^\}]+)\}/) do |x|
    safe(binding, :eval, x[1...-1]) { return true } # ignore bad log points
  end

  body = {
    category: 'console',
    output: msg + "\n",
  }

  if breakpoint.pos.is_a?(Integer)
    body[:line] = breakpoint.pos
    body[:source] = {
      name: File.basename(breakpoint.source),
      path: breakpoint.source,
    }
  end

  @session.event! 'output', **body
  return true
end
process_requests() click to toggle source
# File lib/byebug/dap/command_processor.rb, line 122
def process_requests
  loop do
    request = @requests.pop
    break unless request

    if request.is_a?(Proc)
      err = nil
      r = safe(request, :call) { |e| err = e; nil }
      @exec_ch.push [r, err]
      next
    end

    break if ContextualCommand.execute(@session, request, self) == :stop
  end

  @last_exception = nil
  @session.invalidate_handles!

rescue StandardError => e
  log "\n! #{e.message} (#{e.class})", *e.backtrace
end
stopped!() click to toggle source
# File lib/byebug/dap/command_processor.rb, line 144
def stopped!
  return if logpoint!

  case context.stop_reason
  when :breakpoint
    args = {
      reason: 'breakpoint',
      description: 'Hit breakpoint',
      text: "Hit breakpoint at #{context.location}",
    }

  when :catchpoint
    args = {
      reason: 'exception',
      description: 'Hit catchpoint',
      text: "Hit catchpoint at #{context.location}",
    }

  when :step
    if @pause_requested
      @pause_requested = false
      args = {
        reason: 'pause',
        description: 'Paused',
        text: "Paused at #{context.location}"
      }
    else
      args = {
        reason: 'step',
        description: 'Stepped',
        text: "Stepped at #{context.location}"
      }
    end

  else
    log "Stopped for unknown reason: #{context.stop_reason}"
  end

  @session.event! 'stopped', threadId: context.thnum, **args if args

  process_requests
end