Class: Ruber::IRB::IRBController

Inherits:
Qt::Object
  • Object
show all
Defined in:
plugins/irb/irb_controller.rb,
plugins/irb/irb_controller.rb,
plugins/irb/irb_controller.rb,
plugins/irb/irb_controller.rb

Constant Summary

TIMER_INTERVAL =
100
DEFAULT_OUTPUT_LINES =
100

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Signal Summary

Constructor Details

- (IRBController) initialize(irb, options, parent = nil)

A new instance of IRBController

Parameters:

  • irb (String)

    the path of the IRB program

  • options (<String>)

    the options to pass to the IRB program

  • parent (Qt::Object, nil) (defaults to: nil)

    the parent object



160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'plugins/irb/irb_controller.rb', line 160

def initialize irb, options, parent = nil
  super parent
  @irb_program = irb
  @irb_options = options.dup
  @timer = Qt::Timer.new self
  connect @timer, SIGNAL(:timeout), self, SLOT(:timer_ticked)
  @pending_prompt = nil
  @interrupting = false
  @evaluating = false
  @input = []
  @output = []
  @interval = 100
end

Instance Attribute Details

- (Integer?) interval

The minimum time interval (in milliseconds) between two #output_ready methods. If nil, the #output_ready signal will be emitted as soon as output is received from IRB. The default value is 100 milliseconds

Returns:

  • (Integer, nil)

    the minimum time interval (in milliseconds) between two #output_ready methods. If nil, the #output_ready signal will be emitted as soon as output is received from IRB. The default value is 100 milliseconds



154
155
156
# File 'plugins/irb/irb_controller.rb', line 154

def interval
  @interval
end

- (<String>) irb_options

The options to pass to IRB. Note that changing this after IRB has been started won’t have any effect until you restart IRB.

Returns:

  • (<String>)

    the options to pass to IRB. Note that changing this after IRB has been started won’t have any effect until you restart IRB.



145
146
147
# File 'plugins/irb/irb_controller.rb', line 145

def irb_options
  @irb_options
end

- (String) irb_program

The path if the IRB program. Note that changing this after IRB has been started won’t have any effect until you restart IRB.

Returns:

  • (String)

    the path if the IRB program. Note that changing this after IRB has been started won’t have any effect until you restart IRB.



149
150
151
# File 'plugins/irb/irb_controller.rb', line 149

def irb_program
  @irb_program
end

Instance Method Details

- (nil) change_irb_prompt (private)

Sends IRB the command to change the prompt

IRB should not be given input until this command has been executed

Returns:

  • (nil)


358
359
360
361
362
363
364
365
366
# File 'plugins/irb/irb_controller.rb', line 358

def change_irb_prompt
  prompts = @prompt.prompts.dup
  prompts[:RETURN] += "%s\n"
  disconnect @irb, SIGNAL(:readyReadStandardOutput), self, SLOT(:process_output)
  connect @irb, SIGNAL(:readyReadStandardOutput), self, SLOT(:wait_for_prompt_changed)
  cmd = "IRB.conf[:PROMPT][:QUIRB]=#{prompts.inspect}\nconf.prompt_mode = :QUIRB\n"
  @irb.write cmd
  nil
end

- (Boolean) has_output?

Whether or not there’s output ready to be read

Returns:

  • (Boolean)

    true if there’s output to be read using #output and false otherwise



177
178
179
# File 'plugins/irb/irb_controller.rb', line 177

def has_output?
  !@output.empty?
end

- (Object) interrupt

Interrupts IRB evaluation by sending IRB the SIGINT signal

This will also clear any pending input. The #output_ready signal won’t be emitted until IRB sends an empty prompt. The #interrupting_evaluation signal is emitted before sending IRB the SIGINT signal

Returns:

  • nil



180
181
182
183
184
185
186
187
# File 'plugins/irb/irb_controller.rb', line 180

def interrupt
  @interrupting = true
  @timer.stop
  @input.clear
  emit interrupting_evaluation
  Process.kill :INT, @irb.pid
  nil
end

- (nil) irb_finished (private)

Restarts IRB after it has been terminated

This method is connected to the IRB process’s finished(int, QProcess::ExitStatus) signal, so that a new IRB process can be automatically started. This doesn’t happen when #stop_irb is used to kill the IRB process

Returns:

  • (nil)


439
440
441
442
443
# File 'plugins/irb/irb_controller.rb', line 439

def irb_finished
  @irb.delete_later
  start_irb
  nil
end

Slot Signature:

irb_finished()

- (<IrbLine>) output(n = 100)

Reads a number of lines of output

Lines which have been read are removed from the output buffer

Parameters:

  • n (Integer, nil) (defaults to: 100)

    the number of lines to read

Returns:

  • (<IrbLine>)

    an array containing the first n lines of output. If there are less than n lines of output, all of them are returned. If there’s no output, an empty array is returned. If n is nil, the whole content of the output buffer is returned



324
325
326
327
328
329
# File 'plugins/irb/irb_controller.rb', line 324

def output n = 100
  if n then n = [n, @output.size].min
  else n = @output.count
  end
  @output.shift n
end

- (Object) output_ready (private)



261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'plugins/irb/irb_controller.rb', line 261

def output_ready
  new_lines = @irb.read_all_standard_output.to_s.split "\n"
  lines = process_output new_lines, false
  return unless lines
  @output.concat lines
  if @prompt and !@interrupting
    if lines.last and lines.last.category == :input and lines.last.text.empty?
      is_ready = true
      @pending_prompt = @output.pop
    end
    emit output_received if @output.count <= 100
    @timer.start(TIMER_INTERVAL) if !@output.empty? and !@timer.active?
    send_next_line if (@in_evaluation and is_ready) or !@in_evaluation
  elsif @interrupting
    prompt_line = lines.find{|l| l.category == :input and l.text.empty?}
    if prompt_line
      @interrupting = false
      idx = lines.index prompt_line
      lines = @output.pop lines.size - idx + 1
      emit evaluation_interrupted
      @output = lines
      emit output_received
      send_next_line
    end
  end
end

Slot Signature:

output_ready()

- (<IrbLine>?) parse_output(lines) (private)

Parses output lines from IRB, converting them to Ruber::IRB::IrbLine objects

If the prompt has been set up, the lines will be processed using PromptMatcher#match, otherwise they’ll all be considered output lines with no prompt.

If the controller is sending input to IRB and there’s a pending prompt, that prompt will be added to the beginning of the first line, then removed

Parameters:

  • lines (<String>)

    the lines to process

Returns:



516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
# File 'plugins/irb/irb_controller.rb', line 516

def parse_output lines
  return if lines.empty?
  if @prompt
    parsed_lines = lines.map do |l| 
      @prompt.match(l) || IrbLine.new( :output, l, '')
    end
  else
    parsed_lines = lines.map{|l| IrbLine.new :output, l, ''}
  end
  if @evaluating and @pending_prompt and parsed_lines[0].type == :output
    parsed_lines[0] = IrbLine.new :normal, lines[0], @pending_prompt.prompt
    @pending_prompt = nil
  end
  parsed_lines
end

Slot Signature:

parse_output()

- (nil) process_output (private)

Slot called whenever IRB sends output after the prompt has correctly been set up

It adds the output lines to the output buffer, after converting them to Ruber::IRB::IrbLine objects. If the last line is a prompt line and the controller is sending input to IRB, it calls #send_next_line.

If the controller is interrupting IRB and a prompt is found, the #evaluation_interrupted signal is emitted, followed by the #ready signal

Returns:

  • (nil)


468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
# File 'plugins/irb/irb_controller.rb', line 468

def process_output
  new_lines = @irb.read_all_standard_output.to_s.split "\n"
  lines = parse_output new_lines
  return unless @prompt
  return unless lines
  @output.concat lines
  if lines.last and lines.last.category == :input and lines.last.text.empty?
    is_ready = true
    @pending_prompt = @output.pop
  end
  if @prompt and !@interrupting
    emit output_ready if !@interval or @output.count <= 100
    @timer.start(@interval) if @interval and !@output.empty? and !@timer.active?
    send_next_line if (@evaluating and is_ready) or !@evaluating
  elsif @interrupting
    if is_ready
      @interrupting = false
      emit evaluation_interrupted
      @output.clear
      send_next_line
    end
  end
  nil
end

Slot Signature:

process_output()

- (nil) prompts=(prompts)

Note:

in the return prompt, don’t add the ending %s\n, as it will be added automatically

Note:

users must not use IRB.conf[:PROMPT] or IRB.conf.prompt_mode= to change IRB’s prompt, but always call this method. This is because Ruber::IRB::IRBController, behind the scenes, needs to add a special string to the prompt chosen by the user to work correctly

Note:

changing the prompt takes effect immediately, even if IRB has already been started

Changes the prompt

Calling this method is the same as adding a new entry to IRB.conf[:PROMPT] and changing the current prompt using IRB.conf.prompt_mode=.

Parameters:

  • the ({Symbol=>String})

    new prompt. The recognized keys are: :PROMPT_I, :PROMPT_N, :PROMPT_S, :PROMPT_C, :RETURN. They have the same meaninig as the keys of any entry in IRB.conf[:PROMPT]

Returns:

  • (nil)


284
285
286
287
288
# File 'plugins/irb/irb_controller.rb', line 284

def prompts= prompts
  id = Array.new(5){rand(10)}.join ''
  @prompt = PromptMatcher.new id, prompts
  change_irb_prompt if running?
end

- (nil) restart_irb

Stops and immediately restarts the IRB process The #about_to_stop_irb is emitted before stopping IRB. After emitting this signal, all output will be cleared, so you won’t be able to access it anymore.

Returns:

  • (nil)


239
240
241
242
# File 'plugins/irb/irb_controller.rb', line 239

def restart_irb
  stop :terminate
  nil
end

Slot Signature:

restart_irb()

- (Boolean) running?

Whether IRB is running or not

Returns:

  • (Boolean)

    whether IRB is running or not



325
326
327
# File 'plugins/irb/irb_controller.rb', line 325

def running?
  @irb and (@irb.state == Qt::Process::Running)
end

- (nil) send_next_line (private)

Sends the next line of input to the IRB process

If there’s no queued input line, the #ready signal will be emitted

Returns:

  • (nil)


450
451
452
453
454
455
456
# File 'plugins/irb/irb_controller.rb', line 450

def send_next_line
  if @input.empty? 
    @evaluating = false
    emit ready
  else @irb.write @input.shift + "\n"
  end
end

- (nil) send_signal(signal)

Note:

if you want to stop evaluation (for example, to exit and endless loop), do not use this method to send a SIGINT, but use #interrupt. Simply sending a SIGINT signal may not work very well in that case, as, if IRB keeps sending output, #output_ready signals will kept being emitted

Sends IRB a signal

Parameters:

  • signal (String, Symbol, Integer)

    the name or number of the signal. Signal names may be with or without the SIG prefix

Returns:

  • (nil)

Raises:

  • (ArgumentError)

    if the signal name or number is not valid

  • (RuntimeError)

    if it wasn’t possible to send the signal



256
257
258
259
260
261
262
263
264
265
# File 'plugins/irb/irb_controller.rb', line 256

def send_signal signal
  pid = @irb.pid
  begin Process.kill signal, pid
  rescue Errno::EINVAL, RangeError, ArgumentError
    raise ArgumentError, "Invalid signal #{signal}"
  rescue Errno::ESRCH, Errno::EPERM
    raise RuntimeError, "It wasn't possible to send IRB a signal"
  end
  nil
end

- (nil) send_to_irb(input)

Sends input to IRB

The input lines are sent one by one, expecting a prompt before sending a new one. The #ready signal is emitted at the first prompt after the last line has been sent. If there are lines waiting to be sent, the new ones will be added at the end of the queue.

Parameters:

  • input (<String>)

    the lines to send IRB

Returns:

  • (nil)


197
198
199
200
201
202
203
204
# File 'plugins/irb/irb_controller.rb', line 197

def send_to_irb input
  @input.concat input
  unless @evaluating
    @evaluating = true
    send_next_line
  end
  nil
end

Slot Signature:

send_to_irb()

- (nil) set_irb_env (private)

Changes the environment associated with the IRB process

The environment is changed by adding an IRBRC variable which points to the irbrc.rb file in the same directory as irb_controller.rb.

Returns:

  • (nil)


389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'plugins/irb/irb_controller.rb', line 389

def set_irb_env
  dir = File.dirname(__FILE__)
  vars = ['IRBRC', File.join(dir, 'irbrc.rb')]
  if defined? Qt::ProcessEnvironment
    env = Qt::ProcessEnvironment.system_environment
    env.insert *vars
    @irb.process_environment = env
  else 
    env = @irb.system_environment
    env << vars.join('=')
    @irb.environment = env
  end
  nil
end

- (Boolean) start_irb

Starts IRB

If IRB is already running, it’ll be stopped. The #ready signal will be emitted when IRB is ready to accept output.

This method will wait for IRB to start for up to two seconds before returning.

Returns:

  • (Boolean)

    true if IRB was started successfully and false if it didn’t start within two seconds



301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'plugins/irb/irb_controller.rb', line 301

def start_irb
  if @irb
    @irb.kill
    @irb.delete_later 
  end
  @irb = Qt::Process.new self
  @irb.process_channel_mode = Qt::Process::MergedChannels
  set_irb_env
  connect @irb, SIGNAL('finished(int, QProcess::ExitStatus)'), self, SLOT(:irb_finished)
  options = @irb_options + ['--noreadline']
  @irb.start @irb_program, options
  @irb.wait_for_started 2000
  if @irb.state == Qt::Process::Running
    change_irb_prompt
    true
  else false
  end
end

- (Symbol) state

The state of the IRB process

Returns:

  • (Symbol)

    :running if the IRB process is running, :starting if an attempt to start the IRB process was made but IRB hasn’t started yet and :not_running if either IRB has not yet been started or if the attempt to start it has failed



333
334
335
336
337
338
339
340
341
342
# File 'plugins/irb/irb_controller.rb', line 333

def state
  if @irb
    case @irb.state
    when Qt::Process::Running then :running
    when Qt::Process::Starting then :starting
    else :not_running
    end
  else :not_running
  end
end

- (nil) stop(meth) (private)

Helper method to stop IRB

It’s used by both #stop_irb and #restart_irb. It takes care of reading any unread output from IRB, emitting the #about_to_stop_irb signal, stopping the timer and so on.

Parameters:

  • meth (Symbol)

    the method to call to stop IRB. It can be either :kill or :terminate, which will call respectively Qt::Process#kill or Qt::Process#terminate

Returns:

  • (nil)


219
220
221
222
223
224
225
226
# File 'plugins/irb/irb_controller.rb', line 219

def stop meth
  parse_output @irb.read_all_standard_output.to_s.split("\n")
  emit about_to_stop_irb
  @timer.stop
  @irb.disconnect SIGNAL(:readyReadStandardOutput)
  @irb.send meth
  nil
end

- (nil) stop_irb

Note:

this method will wait for up to two seconds for IRB to stop. If it

Stops the IRB process

The #about_to_stop_irb is emitted before stopping IRB. No further #output_ready signals are emitted.

You’ll need to call #start_irb if you want IRB to be restarted after calling this method. If all you need is to stop IRB and immediately restart it, however, use #restart_irb rather than #stop

doesn’t stop by that time, it will return all the same.

Returns:

  • (nil)


226
227
228
229
230
231
# File 'plugins/irb/irb_controller.rb', line 226

def stop_irb
  disconnect @irb, SIGNAL('finished(int, QProcess::ExitStatus)'), self, SLOT(:irb_finished)
  stop :kill
  # It seems it takes some time before irb is killed.
  @irb.wait_for_finished 2000
end

- (nil) timer_ticked (private)

Slot called in response to the timer timing out

It emits the #output_ready signal and stops the timer if there’s no more output

Returns:

  • (nil)


499
500
501
502
503
# File 'plugins/irb/irb_controller.rb', line 499

def timer_ticked
  emit output_ready
  @timer.stop if @output.empty?
  nil
end

Slot Signature:

timer_ticked()

- (Object) wait_for_prompt_changed (private)

Slot called whenever IRB sends output before changes to the prompt have taken effect

If the lines sent by IRB contain the correct prompt, the readyReadStandardOutput signal from the IRB process will be connected to the #process_output signal and the #ready signal will be emitted



410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'plugins/irb/irb_controller.rb', line 410

def wait_for_prompt_changed
  lines = @irb.read_all_standard_output.to_s.split "\n"
  found = false
  lines.each do |l|
    if @prompt.match l
      disconnect @irb, SIGNAL(:readyReadStandardOutput), self, SLOT(:wait_for_prompt_changed)
      connect @irb, SIGNAL(:readyReadStandardOutput), self, SLOT(:process_output)
      found = true
      break
    end
  end
  if found
    empty_prompt = lines.reverse_each.find do |l|
      res = @prompt.match l
      res and res.category == :input and res.text.empty?
    end
    @pending_prompt = @prompt.match empty_prompt if empty_prompt
  end
  emit ready
end

Slot Signature:

wait_for_prompt_changed()

Signal Details

- output_received

- about_to_stop_irb

Signal emitted just before IRB is stopped, for whatever reason. Connecting to this signal is the last opportunity to read the output from it.

- interrupting_evaluation

Signal emitted just before interrupting IRB by sending a SIGINT signal

- evaluation_interrupted

Signal emitted after IRB has been interrupted by sending a SIGINT signal

- ready

Signal emitted when IRB is ready to receive input (that is, when it gives a prompt when there aren’t lines yet to be sent)