lib/byebug/commands/threads.rb
require 'byebug/command' # # TODO: Implement thread commands as a single command with subcommands, just # like `info`, `var`, `enable` and `disable`. This allows consistent help # format and we can go back to showing help for a single command in the `help` # command. # module Byebug # # Utilities to assist commands related to threads. # module ThreadFunctions def display_context(context, should_show_top_frame = true) puts pr('thread.context', thread_arguments(context, should_show_top_frame)) end def thread_arguments(context, should_show_top_frame = true) status_flag = if context.suspended? '$' else context.thread == Thread.current ? '+' : ' ' end debug_flag = context.ignored? ? '!' : ' ' if should_show_top_frame if context == Byebug.current_context file_line = "#{@state.file}:#{@state.line}" else backtrace = context.thread.backtrace_locations if backtrace && backtrace[0] file_line = "#{backtrace[0].path}:#{backtrace[0].lineno}" end end end { status_flag: status_flag, debug_flag: debug_flag, id: context.thnum, thread: context.thread.inspect, file_line: file_line || '' } end def parse_thread_num(subcmd, arg) thnum, err = get_int(arg, subcmd, 1) return [nil, err] unless thnum Byebug.contexts.find { |c| c.thnum == thnum } end def parse_thread_num_for_cmd(subcmd, arg) c, err = parse_thread_num(subcmd, arg) case when err [c, err] when c.nil? [nil, pr('thread.errors.no_thread')] when @state.context == c [c, pr('thread.errors.current_thread')] when c.ignored? [c, pr('thread.errors.wrong_action', subcmd: subcmd, arg: arg)] else [c, nil] end end end # # List current threads. # class ThreadListCommand < Command include ThreadFunctions self.allow_in_control = true def regexp /^\s* th(?:read)? \s+ l(?:ist)? \s*$/x end def execute contexts = Byebug.contexts.sort_by(&:thnum) thread_list = prc('thread.context', contexts) do |context, _| thread_arguments(context) end print(thread_list) end class << self def names %w(thread) end def description prettify <<-EOD th[read] l[ist] Lists all threads. EOD end end end # # Show current thread. # class ThreadCurrentCommand < Command include ThreadFunctions def regexp /^\s* th(?:read)? \s+ (?:cur(?:rent)?)? \s*$/x end def execute display_context(@state.context) end class << self def names %w(thread) end def description prettify <<-EOD th[read] cur[rent] Shows current thread. EOD end end end # # Stop execution of a thread. # class ThreadStopCommand < Command include ThreadFunctions self.allow_in_control = true self.allow_in_post_mortem = false def regexp /^\s* th(?:read)? \s+ stop \s* (\S*) \s*$/x end def execute c, err = parse_thread_num_for_cmd('thread stop', @match[1]) return errmsg(err) if err c.suspend display_context(c) end class << self def names %w(thread) end def description prettify <<-EOD th[read] stop <n> Stops thread <n>. EOD end end end # # Resume execution of a thread. # class ThreadResumeCommand < Command include ThreadFunctions self.allow_in_control = true self.allow_in_post_mortem = false def regexp /^\s* th(?:read)? \s+ resume \s* (\S*) \s*$/x end def execute c, err = parse_thread_num_for_cmd('thread resume', @match[1]) return errmsg(err) if err return errmsg pr('thread.errors.already_running') unless c.suspended? c.resume display_context(c) end class << self def names %w(thread) end def description prettify <<-EOD th[read] resume <n> Resumes thread <n>. EOD end end end # # Switch execution to a different thread. # class ThreadSwitchCommand < Command include ThreadFunctions self.allow_in_control = true self.allow_in_post_mortem = false def regexp /^\s* th(?:read)? \s+ sw(?:itch)? (?:\s+(\S+))? \s*$/x end def execute c, err = parse_thread_num_for_cmd('thread switch', @match[1]) return errmsg(err) if err display_context(c) c.switch @state.proceed end class << self def names %w(thread) end def description prettify <<-EOD th[read] sw[itch] <n> Switches thread context to <n>. EOD end end end end