class DEBUGGER__::Session

def register_default_command

def register_default_command
  ### Control flow
  # * `s[tep]`
  #   * Step in. Resume the program until next breakable point.
  # * `s[tep] <n>`
  #   * Step in, resume the program at `<n>`th breakable point.
  register_command 's', 'step',
                   repeat: true,
                   cancel_auto_continue: true,
                   postmortem: false do |arg|
    step_command :in, arg
  end
  # * `n[ext]`
  #   * Step over. Resume the program until next line.
  # * `n[ext] <n>`
  #   * Step over, same as `step <n>`.
  register_command 'n', 'next',
                   repeat: true,
                   cancel_auto_continue: true,
                   postmortem: false do |arg|
    step_command :next, arg
  end
  # * `fin[ish]`
  #   * Finish this frame. Resume the program until the current frame is finished.
  # * `fin[ish] <n>`
  #   * Finish `<n>`th frames.
  register_command 'fin', 'finish',
                   repeat: true,
                   cancel_auto_continue: true,
                   postmortem: false do |arg|
    if arg&.to_i == 0
      raise 'finish command with 0 does not make sense.'
    end
    step_command :finish, arg
  end
  # * `u[ntil]`
  #   * Similar to `next` command, but only stop later lines or the end of the current frame.
  #   * Similar to gdb's `advance` command.
  # * `u[ntil] <[file:]line>`
  #   * Run til the program reaches given location or the end of the current frame.
  # * `u[ntil] <name>`
  #   * Run til the program invokes a method `<name>`. `<name>` can be a regexp with `/name/`.
  register_command 'u', 'until',
                   repeat: true,
                   cancel_auto_continue: true,
                   postmortem: false do |arg|
    step_command :until, arg
  end
  # * `c` or `cont` or `continue`
  #   * Resume the program.
  register_command 'c', 'cont', 'continue',
                   repeat: true,
                   cancel_auto_continue: true do |arg|
    leave_subsession :continue
  end
  # * `q[uit]` or `Ctrl-D`
  #   * Finish debugger (with the debuggee process on non-remote debugging).
  register_command 'q', 'quit' do |arg|
    if ask 'Really quit?'
      @ui.quit arg.to_i do
        request_tc :quit
      end
      leave_subsession :continue
    else
      next :retry
    end
  end
  # * `q[uit]!`
  #   * Same as q[uit] but without the confirmation prompt.
  register_command 'q!', 'quit!', unsafe: false do |arg|
    @ui.quit arg.to_i do
      request_tc :quit
    end
    leave_subsession :continue
  end
  # * `kill`
  #   * Stop the debuggee process with `Kernel#exit!`.
  register_command 'kill' do |arg|
    if ask 'Really kill?'
      exit! (arg || 1).to_i
    else
      next :retry
    end
  end
  # * `kill!`
  #   * Same as kill but without the confirmation prompt.
  register_command 'kill!', unsafe: false do |arg|
    exit! (arg || 1).to_i
  end
  # * `sigint`
  #   * Execute SIGINT handler registered by the debuggee.
  #   * Note that this command should be used just after stop by `SIGINT`.
  register_command 'sigint' do
    begin
      case cmd = @intercepted_sigint_cmd
      when nil, 'IGNORE', :IGNORE, 'DEFAULT', :DEFAULT
        # ignore
      when String
        eval(cmd)
      when Proc
        cmd.call
      end
      leave_subsession :continue
    rescue Exception => e
      @ui.puts "Exception: #{e}"
      @ui.puts e.backtrace.map{|line| "  #{e}"}
      next :retry
    end
  end
  ### Breakpoint
  # * `b[reak]`
  #   * Show all breakpoints.
  # * `b[reak] <line>`
  #   * Set breakpoint on `<line>` at the current frame's file.
  # * `b[reak] <file>:<line>` or `<file> <line>`
  #   * Set breakpoint on `<file>:<line>`.
  # * `b[reak] <class>#<name>`
  #    * Set breakpoint on the method `<class>#<name>`.
  # * `b[reak] <expr>.<name>`
  #    * Set breakpoint on the method `<expr>.<name>`.
  # * `b[reak] ... if: <expr>`
  #   * break if `<expr>` is true at specified location.
  # * `b[reak] ... pre: <command>`
  #   * break and run `<command>` before stopping.
  # * `b[reak] ... do: <command>`
  #   * break and run `<command>`, and continue.
  # * `b[reak] ... path: <path>`
  #   * break if the path matches to `<path>`. `<path>` can be a regexp with `/regexp/`.
  # * `b[reak] if: <expr>`
  #   * break if: `<expr>` is true at any lines.
  #   * Note that this feature is super slow.
  register_command 'b', 'break', postmortem: false, unsafe: false do |arg|
    if arg == nil
      show_bps
      next :retry
    else
      case bp = repl_add_breakpoint(arg)
      when :noretry
      when nil
        next :retry
      else
        show_bps bp
        next :retry
      end
    end
  end
  # * `catch <Error>`
  #   * Set breakpoint on raising `<Error>`.
  # * `catch ... if: <expr>`
  #   * stops only if `<expr>` is true as well.
  # * `catch ... pre: <command>`
  #   * runs `<command>` before stopping.
  # * `catch ... do: <command>`
  #   * stops and run `<command>`, and continue.
  # * `catch ... path: <path>`
  #   * stops if the exception is raised from a `<path>`. `<path>` can be a regexp with `/regexp/`.
  register_command 'catch', postmortem: false, unsafe: false do |arg|
    if arg
      bp = repl_add_catch_breakpoint arg
      show_bps bp if bp
    else
      show_bps
    end
    :retry
  end
  # * `watch @ivar`
  #   * Stop the execution when the result of current scope's `@ivar` is changed.
  #   * Note that this feature is super slow.
  # * `watch ... if: <expr>`
  #   * stops only if `<expr>` is true as well.
  # * `watch ... pre: <command>`
  #   * runs `<command>` before stopping.
  # * `watch ... do: <command>`
  #   * stops and run `<command>`, and continue.
  # * `watch ... path: <path>`
  #   * stops if the path matches `<path>`. `<path>` can be a regexp with `/regexp/`.
  register_command 'wat', 'watch', postmortem: false, unsafe: false do |arg|
    if arg && arg.match?(/\A@\w+/)
      repl_add_watch_breakpoint(arg)
    else
      show_bps
      :retry
    end
  end
  # * `del[ete]`
  #   * delete all breakpoints.
  # * `del[ete] <bpnum>`
  #   * delete specified breakpoint.
  register_command 'del', 'delete', postmortem: false, unsafe: false do |arg|
    case arg
    when nil
      show_bps
      if ask "Remove all breakpoints?", 'N'
        delete_bp
      end
    when /\d+/
      bp = delete_bp arg.to_i
    else
      nil
    end
    @ui.puts "deleted: \##{bp[0]} #{bp[1]}" if bp
    :retry
  end
  ### Information
  # * `bt` or `backtrace`
  #   * Show backtrace (frame) information.
  # * `bt <num>` or `backtrace <num>`
  #   * Only shows first `<num>` frames.
  # * `bt /regexp/` or `backtrace /regexp/`
  #   * Only shows frames with method name or location info that matches `/regexp/`.
  # * `bt <num> /regexp/` or `backtrace <num> /regexp/`
  #   * Only shows first `<num>` frames with method name or location info that matches `/regexp/`.
  register_command 'bt', 'backtrace', unsafe: false do |arg|
    case arg
    when /\A(\d+)\z/
      request_tc_with_restarted_threads [:show, :backtrace, arg.to_i, nil]
    when /\A\/(.*)\/\z/
      pattern = $1
      request_tc_with_restarted_threads [:show, :backtrace, nil, Regexp.compile(pattern)]
    when /\A(\d+)\s+\/(.*)\/\z/
      max, pattern = $1, $2
      request_tc_with_restarted_threads [:show, :backtrace, max.to_i, Regexp.compile(pattern)]
    else
      request_tc_with_restarted_threads [:show, :backtrace, nil, nil]
    end
  end
  # * `l[ist]`
  #   * Show current frame's source code.
  #   * Next `list` command shows the successor lines.
  # * `l[ist] -`
  #   * Show predecessor lines as opposed to the `list` command.
  # * `l[ist] <start>` or `l[ist] <start>-<end>`
  #   * Show current frame's source code from the line <start> to <end> if given.
  register_command 'l', 'list', repeat: true, unsafe: false do |arg|
    case arg ? arg.strip : nil
    when /\A(\d+)\z/
      request_tc [:show, :list, {start_line: arg.to_i - 1}]
    when /\A-\z/
      request_tc [:show, :list, {dir: -1}]
    when /\A(\d+)-(\d+)\z/
      request_tc [:show, :list, {start_line: $1.to_i - 1, end_line: $2.to_i}]
    when nil
      request_tc [:show, :list]
    else
      @ui.puts "Can not handle list argument: #{arg}"
      :retry
    end
  end
  # * `whereami`
  #   * Show the current frame with source code.
  register_command 'whereami', unsafe: false do
    request_tc [:show, :whereami]
  end
  # * `edit`
  #   * Open the current file on the editor (use `EDITOR` environment variable).
  #   * Note that edited file will not be reloaded.
  # * `edit <file>`
  #   * Open <file> on the editor.
  register_command 'edit' do |arg|
    if @ui.remote?
      @ui.puts "not supported on the remote console."
      next :retry
    end
    begin
      arg = resolve_path(arg) if arg
    rescue Errno::ENOENT
      @ui.puts "not found: #{arg}"
      next :retry
    end
    request_tc [:show, :edit, arg]
  end
  info_subcommands = nil
  info_subcommands_abbrev = nil
  # * `i[nfo]`
  #   * Show information about current frame (local/instance variables and defined constants).
  # * `i[nfo]` <subcommand>
  #   * `info` has the following sub-commands.
  #   * Sub-commands can be specified with few letters which is unambiguous, like `l` for 'locals'.
  # * `i[nfo] l or locals or local_variables`
  #   * Show information about the current frame (local variables)
  #   * It includes `self` as `%self` and a return value as `_return`.
  # * `i[nfo] i or ivars or instance_variables`
  #   * Show information about instance variables about `self`.
  #   * `info ivars <expr>` shows the instance variables of the result of `<expr>`.
  # * `i[nfo] c or consts or constants`
  #   * Show information about accessible constants except toplevel constants.
  #   * `info consts <expr>` shows the constants of a class/module of the result of `<expr>`
  # * `i[nfo] g or globals or global_variables`
  #   * Show information about global variables
  # * `i[nfo] th or threads`
  #   * Show all threads (same as `th[read]`).
  # * `i[nfo] b or breakpoints or w or watchpoints`
  #   * Show all breakpoints and watchpoints.
  # * `i[nfo] ... /regexp/`
  #   * Filter the output with `/regexp/`.
  register_command 'i', 'info', unsafe: false do |arg|
    if /\/(.+)\/\z/ =~ arg
      pat = Regexp.compile($1)
      sub = $~.pre_match.strip
    else
      sub = arg
    end
    if /\A(.+?)\b(.+)/ =~ sub
      sub = $1
      opt = $2.strip
      opt = nil if opt.empty?
    end
    if sub && !info_subcommands
      info_subcommands = {
        locals: %w[ locals local_variables ],
        ivars:  %w[ ivars instance_variables ],
        consts: %w[ consts constants ],
        globals:%w[ globals global_variables ],
        threads:%w[ threads ],
        breaks: %w[ breakpoints ],
        watchs: %w[ watchpoints ],
      }
      require_relative 'abbrev_command'
      info_subcommands_abbrev = AbbrevCommand.new(info_subcommands)
    end
    if sub
      sub = info_subcommands_abbrev.search sub, :unknown do |candidates|
        # note: unreached now
        @ui.puts "Ambiguous command '#{sub}': #{candidates.join(' ')}"
      end
    end
    case sub
    when nil
      request_tc_with_restarted_threads [:show, :default, pat] # something useful
    when :locals
      request_tc_with_restarted_threads [:show, :locals, pat]
    when :ivars
      request_tc_with_restarted_threads [:show, :ivars, pat, opt]
    when :consts
      request_tc_with_restarted_threads [:show, :consts, pat, opt]
    when :globals
      request_tc_with_restarted_threads [:show, :globals, pat]
    when :threads
      thread_list
      :retry
    when :breaks, :watchs
      show_bps
      :retry
    else
      @ui.puts "unrecognized argument for info command: #{arg}"
      show_help 'info'
      :retry
    end
  end
  # * `o[utline]` or `ls`
  #   * Show you available methods, constants, local variables, and instance variables in the current scope.
  # * `o[utline] <expr>` or `ls <expr>`
  #   * Show you available methods and instance variables of the given object.
  #   * If the object is a class/module, it also lists its constants.
  register_command 'outline', 'o', 'ls', unsafe: false do |arg|
    request_tc_with_restarted_threads [:show, :outline, arg]
  end
  # * `display`
  #   * Show display setting.
  # * `display <expr>`
  #   * Show the result of `<expr>` at every suspended timing.
  register_command 'display', postmortem: false do |arg|
    if arg && !arg.empty?
      @displays << arg
      request_eval :try_display, @displays
    else
      request_eval :display, @displays
    end
  end
  # * `undisplay`
  #   * Remove all display settings.
  # * `undisplay <displaynum>`
  #   * Remove a specified display setting.
  register_command 'undisplay', postmortem: false, unsafe: false do |arg|
    case arg
    when /(\d+)/
      if @displays[n = $1.to_i]
        @displays.delete_at n
      end
      request_eval :display, @displays
    when nil
      if ask "clear all?", 'N'
        @displays.clear
      end
      :retry
    end
  end
  ### Frame control
  # * `f[rame]`
  #   * Show the current frame.
  # * `f[rame] <framenum>`
  #   * Specify a current frame. Evaluation are run on specified frame.
  register_command 'frame', 'f', unsafe: false do |arg|
    request_tc [:frame, :set, arg]
  end
  # * `up`
  #   * Specify the upper frame.
  register_command 'up', repeat: true, unsafe: false do |arg|
    request_tc [:frame, :up]
  end
  # * `down`
  #   * Specify the lower frame.
  register_command 'down', repeat: true, unsafe: false do |arg|
    request_tc [:frame, :down]
  end
  ### Evaluate
  # * `p <expr>`
  #   * Evaluate like `p <expr>` on the current frame.
  register_command 'p' do |arg|
    request_eval :p, arg.to_s
  end
  # * `pp <expr>`
  #   * Evaluate like `pp <expr>` on the current frame.
  register_command 'pp' do |arg|
    request_eval :pp, arg.to_s
  end
  # * `eval <expr>`
  #   * Evaluate `<expr>` on the current frame.
  register_command 'eval', 'call' do |arg|
    if arg == nil || arg.empty?
      show_help 'eval'
      @ui.puts "\nTo evaluate the variable `#{cmd}`, use `pp #{cmd}` instead."
      :retry
    else
      request_eval :call, arg
    end
  end
  # * `irb`
  #   * Invoke `irb` on the current frame.
  register_command 'irb' do |arg|
    if @ui.remote?
      @ui.puts "\nIRB is not supported on the remote console."
      :retry
    else
      request_eval :irb, nil
    end
  end
  ### Trace
  # * `trace`
  #   * Show available tracers list.
  # * `trace line`
  #   * Add a line tracer. It indicates line events.
  # * `trace call`
  #   * Add a call tracer. It indicate call/return events.
  # * `trace exception`
  #   * Add an exception tracer. It indicates raising exceptions.
  # * `trace object <expr>`
  #   * Add an object tracer. It indicates that an object by `<expr>` is passed as a parameter or a receiver on method call.
  # * `trace ... /regexp/`
  #   * Indicates only matched events to `/regexp/`.
  # * `trace ... into: <file>`
  #   * Save trace information into: `<file>`.
  # * `trace off <num>`
  #   * Disable tracer specified by `<num>` (use `trace` command to check the numbers).
  # * `trace off [line|call|pass]`
  #   * Disable all tracers. If `<type>` is provided, disable specified type tracers.
  register_command 'trace', postmortem: false, unsafe: false do |arg|
    if (re = /\s+into:\s*(.+)/) =~ arg
      into = $1
      arg.sub!(re, '')
    end
    if (re = /\s\/(.+)\/\z/) =~ arg
      pattern = $1
      arg.sub!(re, '')
    end
    case arg
    when nil
      @ui.puts 'Tracers:'
      @tracers.values.each_with_index{|t, i|
        @ui.puts "* \##{i} #{t}"
      }
      @ui.puts
      :retry
    when /\Aline\z/
      add_tracer LineTracer.new(@ui, pattern: pattern, into: into)
      :retry
    when /\Acall\z/
      add_tracer CallTracer.new(@ui, pattern: pattern, into: into)
      :retry
    when /\Aexception\z/
      add_tracer ExceptionTracer.new(@ui, pattern: pattern, into: into)
      :retry
    when /\Aobject\s+(.+)/
      request_tc_with_restarted_threads [:trace, :object, $1.strip, {pattern: pattern, into: into}]
    when /\Aoff\s+(\d+)\z/
      if t = @tracers.values[$1.to_i]
        t.disable
        @ui.puts "Disable #{t.to_s}"
      else
        @ui.puts "Unmatched: #{$1}"
      end
      :retry
    when /\Aoff(\s+(line|call|exception|object))?\z/
      @tracers.values.each{|t|
        if $2.nil? || t.type == $2
          t.disable
          @ui.puts "Disable #{t.to_s}"
        end
      }
      :retry
    else
      @ui.puts "Unknown trace option: #{arg.inspect}"
      :retry
    end
  end
  # Record
  # * `record`
  #   * Show recording status.
  # * `record [on|off]`
  #   * Start/Stop recording.
  # * `step back`
  #   * Start replay. Step back with the last execution log.
  #   * `s[tep]` does stepping forward with the last log.
  # * `step reset`
  #   * Stop replay .
  register_command 'record', postmortem: false, unsafe: false do |arg|
    case arg
    when nil, 'on', 'off'
      request_tc [:record, arg&.to_sym]
    else
      @ui.puts "unknown command: #{arg}"
      :retry
    end
  end
  ### Thread control
  # * `th[read]`
  #   * Show all threads.
  # * `th[read] <thnum>`
  #   * Switch thread specified by `<thnum>`.
  register_command 'th', 'thread', unsafe: false do |arg|
    case arg
    when nil, 'list', 'l'
      thread_list
    when /(\d+)/
      switch_thread $1.to_i
    else
      @ui.puts "unknown thread command: #{arg}"
    end
    :retry
  end
  ### Configuration
  # * `config`
  #   * Show all configuration with description.
  # * `config <name>`
  #   * Show current configuration of <name>.
  # * `config set <name> <val>` or `config <name> = <val>`
  #   * Set <name> to <val>.
  # * `config append <name> <val>` or `config <name> << <val>`
  #   * Append `<val>` to `<name>` if it is an array.
  # * `config unset <name>`
  #   * Set <name> to default.
  register_command 'config', unsafe: false do |arg|
    config_command arg
    :retry
  end
  # * `source <file>`
  #   * Evaluate lines in `<file>` as debug commands.
  register_command 'source' do |arg|
    if arg
      begin
        cmds = File.readlines(path = File.expand_path(arg))
        add_preset_commands path, cmds, kick: true, continue: false
      rescue Errno::ENOENT
        @ui.puts "File not found: #{arg}"
      end
    else
      show_help 'source'
    end
    :retry
  end
  # * `open`
  #   * open debuggee port on UNIX domain socket and wait for attaching.
  #   * Note that `open` command is EXPERIMENTAL.
  # * `open [<host>:]<port>`
  #   * open debuggee port on TCP/IP with given `[<host>:]<port>` and wait for attaching.
  # * `open vscode`
  #   * open debuggee port for VSCode and launch VSCode if available.
  # * `open chrome`
  #   * open debuggee port for Chrome and wait for attaching.
  register_command 'open' do |arg|
    case arg&.downcase
    when '', nil
      ::DEBUGGER__.open nonstop: true
    when /\A(\d+)z/
      ::DEBUGGER__.open_tcp host: nil, port: $1.to_i, nonstop: true
    when /\A(.+):(\d+)\z/
      ::DEBUGGER__.open_tcp host: $1, port: $2.to_i, nonstop: true
    when 'tcp'
      ::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true
    when 'vscode'
      CONFIG[:open] = 'vscode'
      ::DEBUGGER__.open nonstop: true
    when 'chrome', 'cdp'
      CONFIG[:open] = 'chrome'
      ::DEBUGGER__.open_tcp host: CONFIG[:host], port: (CONFIG[:port] || 0), nonstop: true
    else
      raise "Unknown arg: #{arg}"
    end
    :retry
  end
  ### Help
  # * `h[elp]`
  #   * Show help for all commands.
  # * `h[elp] <command>`
  #   * Show help for the given command.
  register_command 'h', 'help', '?', unsafe: false do |arg|
    show_help arg
    :retry
  end
end