# frozen_string_literal: true # # irb/multi-irb.rb - multiple irb module # by Keiju ISHITSUKA(keiju@ruby-lang.org) # module IRB class JobManager # Creates a new JobManager object def initialize @jobs = [] @current_job = nil end # The active irb session attr_accessor :current_job # The total number of irb sessions, used to set +irb_name+ of the current # Context. def n_jobs @jobs.size end # Returns the thread for the given +key+ object, see #search for more # information. def thread(key) th, = search(key) th end # Returns the irb session for the given +key+ object, see #search for more # information. def irb(key) _, irb = search(key) irb end # Returns the top level thread. def main_thread @jobs[0][0] end # Returns the top level irb session. def main_irb @jobs[0][1] end # Add the given +irb+ session to the jobs Array. def insert(irb) @jobs.push [Thread.current, irb] end # Changes the current active irb session to the given +key+ in the jobs # Array. # # Raises an IrbAlreadyDead exception if the given +key+ is no longer alive. # # If the given irb session is already active, an IrbSwitchedToCurrentThread # exception is raised. def switch(key) th, irb = search(key) fail IrbAlreadyDead unless th.alive? fail IrbSwitchedToCurrentThread if th == Thread.current @current_job = irb th.run Thread.stop @current_job = irb(Thread.current) end # Terminates the irb sessions specified by the given +keys+. # # Raises an IrbAlreadyDead exception if one of the given +keys+ is already # terminated. # # See Thread#exit for more information. def kill(*keys) for key in keys th, _ = search(key) fail IrbAlreadyDead unless th.alive? th.exit end end # Returns the associated job for the given +key+. # # If given an Integer, it will return the +key+ index for the jobs Array. # # When an instance of Irb is given, it will return the irb session # associated with +key+. # # If given an instance of Thread, it will return the associated thread # +key+ using Object#=== on the jobs Array. # # Otherwise returns the irb session with the same top-level binding as the # given +key+. # # Raises a NoSuchJob exception if no job can be found with the given +key+. def search(key) job = case key when Integer @jobs[key] when Irb @jobs.find{|k, v| v.equal?(key)} when Thread @jobs.assoc(key) else @jobs.find{|k, v| v.context.main.equal?(key)} end fail NoSuchJob, key if job.nil? job end # Deletes the job at the given +key+. def delete(key) case key when Integer fail NoSuchJob, key unless @jobs[key] @jobs[key] = nil else catch(:EXISTS) do @jobs.each_index do |i| if @jobs[i] and (@jobs[i][0] == key || @jobs[i][1] == key || @jobs[i][1].context.main.equal?(key)) @jobs[i] = nil throw :EXISTS end end fail NoSuchJob, key end end until assoc = @jobs.pop; end unless @jobs.empty? @jobs.push assoc end # Outputs a list of jobs, see the irb command +irb_jobs+, or +jobs+. def inspect ary = [] @jobs.each_index do |i| th, irb = @jobs[i] next if th.nil? if th.alive? if th.stop? t_status = "stop" else t_status = "running" end else t_status = "exited" end ary.push format("#%d->%s on %s (%s: %s)", i, irb.context.irb_name, irb.context.main, th, t_status) end ary.join("\n") end end @JobManager = JobManager.new # The current JobManager in the session def IRB.JobManager @JobManager end # The current Context in this session def IRB.CurrentContext IRB.JobManager.irb(Thread.current).context end # Creates a new IRB session, see Irb.new. # # The optional +file+ argument is given to Context.new, along with the # workspace created with the remaining arguments, see WorkSpace.new def IRB.irb(file = nil, *main) workspace = WorkSpace.new(*main) parent_thread = Thread.current Thread.start do begin irb = Irb.new(workspace, file) rescue print "Subirb can't start with context(self): ", workspace.main.inspect, "\n" print "return to main irb\n" Thread.pass Thread.main.wakeup Thread.exit end @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC] @JobManager.insert(irb) @JobManager.current_job = irb begin system_exit = false catch(:IRB_EXIT) do irb.eval_input end rescue SystemExit system_exit = true raise #fail ensure unless system_exit @JobManager.delete(irb) if @JobManager.current_job == irb if parent_thread.alive? @JobManager.current_job = @JobManager.irb(parent_thread) parent_thread.run else @JobManager.current_job = @JobManager.main_irb @JobManager.main_thread.run end end end end end Thread.stop @JobManager.current_job = @JobManager.irb(Thread.current) end @CONF[:SINGLE_IRB_MODE] = false @JobManager.insert(@CONF[:MAIN_CONTEXT].irb) @JobManager.current_job = @CONF[:MAIN_CONTEXT].irb class Irb def signal_handle unless @context.ignore_sigint? print "\nabort!!\n" if @context.verbose? exit end case @signal_status when :IN_INPUT print "^C\n" IRB.JobManager.thread(self).raise RubyLex::TerminateLineInput when :IN_EVAL IRB.irb_abort(self) when :IN_LOAD IRB.irb_abort(self, LoadAbort) when :IN_IRB # ignore else # ignore other cases as well end end end trap("SIGINT") do @JobManager.current_job.signal_handle Thread.stop end end