1# 2# irb/multi-irb.rb - multiple irb module 3# $Release Version: 0.9.6$ 4# $Revision: 38515 $ 5# by Keiju ISHITSUKA(keiju@ruby-lang.org) 6# 7# -- 8# 9# 10# 11IRB.fail CantShiftToMultiIrbMode unless defined?(Thread) 12require "thread" 13 14module IRB 15 class JobManager 16 @RCS_ID='-$Id: multi-irb.rb 38515 2012-12-21 05:45:50Z zzak $-' 17 18 # Creates a new JobManager object 19 def initialize 20 # @jobs = [[thread, irb],...] 21 @jobs = [] 22 @current_job = nil 23 end 24 25 # The active irb session 26 attr_accessor :current_job 27 28 # The total number of irb sessions, used to set +irb_name+ of the current 29 # Context. 30 def n_jobs 31 @jobs.size 32 end 33 34 # Returns the thread for the given +key+ object, see #search for more 35 # information. 36 def thread(key) 37 th, = search(key) 38 th 39 end 40 41 # Returns the irb session for the given +key+ object, see #search for more 42 # information. 43 def irb(key) 44 _, irb = search(key) 45 irb 46 end 47 48 # Returns the top level thread. 49 def main_thread 50 @jobs[0][0] 51 end 52 53 # Returns the top level irb session. 54 def main_irb 55 @jobs[0][1] 56 end 57 58 # Add the given +irb+ session to the jobs Array. 59 def insert(irb) 60 @jobs.push [Thread.current, irb] 61 end 62 63 # Changes the current active irb session to the given +key+ in the jobs 64 # Array. 65 # 66 # Raises an IrbAlreadyDead exception if the given +key+ is no longer alive. 67 # 68 # If the given irb session is already active, an IrbSwitchedToCurrentThread 69 # exception is raised. 70 def switch(key) 71 th, irb = search(key) 72 IRB.fail IrbAlreadyDead unless th.alive? 73 IRB.fail IrbSwitchedToCurrentThread if th == Thread.current 74 @current_job = irb 75 th.run 76 Thread.stop 77 @current_job = irb(Thread.current) 78 end 79 80 # Terminates the irb sessions specified by the given +keys+. 81 # 82 # Raises an IrbAlreadyDead exception if one of the given +keys+ is already 83 # terminated. 84 # 85 # See Thread#exit for more information. 86 def kill(*keys) 87 for key in keys 88 th, _ = search(key) 89 IRB.fail IrbAlreadyDead unless th.alive? 90 th.exit 91 end 92 end 93 94 # Returns the associated job for the given +key+. 95 # 96 # If given an Integer, it will return the +key+ index for the jobs Array. 97 # 98 # When an instance of Irb is given, it will return the irb session 99 # associated with +key+. 100 # 101 # If given an instance of Thread, it will return the associated thread 102 # +key+ using Object#=== on the jobs Array. 103 # 104 # Otherwise returns the irb session with the same top-level binding as the 105 # given +key+. 106 # 107 # Raises a NoSuchJob exception if no job can be found with the given +key+. 108 def search(key) 109 job = case key 110 when Integer 111 @jobs[key] 112 when Irb 113 @jobs.find{|k, v| v.equal?(key)} 114 when Thread 115 @jobs.assoc(key) 116 else 117 @jobs.find{|k, v| v.context.main.equal?(key)} 118 end 119 IRB.fail NoSuchJob, key if job.nil? 120 job 121 end 122 123 # Deletes the job at the given +key+. 124 def delete(key) 125 case key 126 when Integer 127 IRB.fail NoSuchJob, key unless @jobs[key] 128 @jobs[key] = nil 129 else 130 catch(:EXISTS) do 131 @jobs.each_index do 132 |i| 133 if @jobs[i] and (@jobs[i][0] == key || 134 @jobs[i][1] == key || 135 @jobs[i][1].context.main.equal?(key)) 136 @jobs[i] = nil 137 throw :EXISTS 138 end 139 end 140 IRB.fail NoSuchJob, key 141 end 142 end 143 until assoc = @jobs.pop; end unless @jobs.empty? 144 @jobs.push assoc 145 end 146 147 # Outputs a list of jobs, see the irb command +irb_jobs+, or +jobs+. 148 def inspect 149 ary = [] 150 @jobs.each_index do 151 |i| 152 th, irb = @jobs[i] 153 next if th.nil? 154 155 if th.alive? 156 if th.stop? 157 t_status = "stop" 158 else 159 t_status = "running" 160 end 161 else 162 t_status = "exited" 163 end 164 ary.push format("#%d->%s on %s (%s: %s)", 165 i, 166 irb.context.irb_name, 167 irb.context.main, 168 th, 169 t_status) 170 end 171 ary.join("\n") 172 end 173 end 174 175 @JobManager = JobManager.new 176 177 # The current JobManager in the session 178 def IRB.JobManager 179 @JobManager 180 end 181 182 # The current Context in this session 183 def IRB.CurrentContext 184 IRB.JobManager.irb(Thread.current).context 185 end 186 187 # Creates a new IRB session, see Irb.new. 188 # 189 # The optional +file+ argument is given to Context.new, along with the 190 # workspace created with the remaining arguments, see WorkSpace.new 191 def IRB.irb(file = nil, *main) 192 workspace = WorkSpace.new(*main) 193 parent_thread = Thread.current 194 Thread.start do 195 begin 196 irb = Irb.new(workspace, file) 197 rescue 198 print "Subirb can't start with context(self): ", workspace.main.inspect, "\n" 199 print "return to main irb\n" 200 Thread.pass 201 Thread.main.wakeup 202 Thread.exit 203 end 204 @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC] 205 @JobManager.insert(irb) 206 @JobManager.current_job = irb 207 begin 208 system_exit = false 209 catch(:IRB_EXIT) do 210 irb.eval_input 211 end 212 rescue SystemExit 213 system_exit = true 214 raise 215 #fail 216 ensure 217 unless system_exit 218 @JobManager.delete(irb) 219 if @JobManager.current_job == irb 220 if parent_thread.alive? 221 @JobManager.current_job = @JobManager.irb(parent_thread) 222 parent_thread.run 223 else 224 @JobManager.current_job = @JobManager.main_irb 225 @JobManager.main_thread.run 226 end 227 end 228 end 229 end 230 end 231 Thread.stop 232 @JobManager.current_job = @JobManager.irb(Thread.current) 233 end 234 235# class Context 236# def set_last_value(value) 237# @last_value = value 238# @workspace.evaluate "_ = IRB.JobManager.irb(Thread.current).context.last_value" 239# if @eval_history #and !@__.equal?(@last_value) 240# @eval_history_values.push @line_no, @last_value 241# @workspace.evaluate "__ = IRB.JobManager.irb(Thread.current).context.instance_eval{@eval_history_values}" 242# end 243# @last_value 244# end 245# end 246 247# module ExtendCommand 248# def irb_context 249# IRB.JobManager.irb(Thread.current).context 250# end 251# # alias conf irb_context 252# end 253 254 @CONF[:SINGLE_IRB_MODE] = false 255 @JobManager.insert(@CONF[:MAIN_CONTEXT].irb) 256 @JobManager.current_job = @CONF[:MAIN_CONTEXT].irb 257 258 class Irb 259 def signal_handle 260 unless @context.ignore_sigint? 261 print "\nabort!!\n" if @context.verbose? 262 exit 263 end 264 265 case @signal_status 266 when :IN_INPUT 267 print "^C\n" 268 IRB.JobManager.thread(self).raise RubyLex::TerminateLineInput 269 when :IN_EVAL 270 IRB.irb_abort(self) 271 when :IN_LOAD 272 IRB.irb_abort(self, LoadAbort) 273 when :IN_IRB 274 # ignore 275 else 276 # ignore other cases as well 277 end 278 end 279 end 280 281 trap("SIGINT") do 282 @JobManager.current_job.signal_handle 283 Thread.stop 284 end 285 286end 287