ruber 0.0.9 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/CHANGES +42 -1
  2. data/lib/ruber/application/application.rb +25 -5
  3. data/lib/ruber/application/plugin.yaml +2 -10
  4. data/lib/ruber/component_manager.rb +2 -2
  5. data/lib/ruber/document_project.rb +5 -3
  6. data/lib/ruber/editor/document.rb +5 -4
  7. data/lib/ruber/editor/ktexteditor_wrapper.rb +1 -1
  8. data/lib/ruber/exception_widgets.rb +1 -1
  9. data/lib/ruber/main_window/main_window.rb +4 -3
  10. data/lib/ruber/main_window/main_window_actions.rb +2 -2
  11. data/lib/ruber/main_window/main_window_internal.rb +1 -1
  12. data/lib/ruber/output_widget.rb +17 -5
  13. data/lib/ruber/project.rb +34 -3
  14. data/lib/ruber/project_dir_scanner.rb +171 -0
  15. data/lib/ruber/settings_container.rb +7 -7
  16. data/lib/ruber/settings_dialog.rb +24 -24
  17. data/lib/ruber/version.rb +1 -1
  18. data/lib/ruber/world/environment.rb +1 -0
  19. data/lib/ruber/world/plugin.yaml +7 -2
  20. data/lib/ruber/{application → world}/project_files_widget.rb +0 -0
  21. data/lib/ruber/{application → world}/ui/project_files_rule_chooser_widget.rb +2 -2
  22. data/lib/ruber/{application → world}/ui/project_files_rule_chooser_widget.ui +0 -0
  23. data/lib/ruber/{application → world}/ui/project_files_widget.rb +2 -2
  24. data/lib/ruber/{application → world}/ui/project_files_widget.ui +0 -0
  25. data/plugins/auto_end/auto_end.rb +21 -18
  26. data/plugins/autosave/autosave.rb +1 -1
  27. data/plugins/find_in_files/find_in_files.rb +1 -1
  28. data/plugins/irb/irb.png +0 -0
  29. data/plugins/irb/irb.rb +142 -0
  30. data/plugins/irb/irb.svg +240 -0
  31. data/plugins/irb/irb_controller.rb +541 -0
  32. data/plugins/irb/irbrc.rb +21 -0
  33. data/plugins/irb/plugin.yaml +19 -0
  34. data/plugins/irb/ui/irb_config_widget.rb +151 -0
  35. data/plugins/irb/ui/irb_config_widget.ui +123 -0
  36. data/plugins/irb/ui/irb_tool_widget.rb +97 -0
  37. data/plugins/irb/ui/irb_tool_widget.ui +86 -0
  38. data/plugins/project_browser/project_browser.rb +1 -1
  39. data/plugins/rspec/plugin.yaml +6 -3
  40. data/plugins/rspec/rspec.rb +172 -473
  41. data/plugins/rspec/tool_widget.rb +462 -0
  42. data/plugins/rspec/ui/rspec_project_widget.rb +58 -38
  43. data/plugins/rspec/ui/rspec_project_widget.ui +68 -64
  44. data/plugins/ruberri/class_formatter.rb +126 -0
  45. data/plugins/ruberri/method_formatter.rb +90 -0
  46. data/plugins/ruberri/plugin.yaml +13 -0
  47. data/plugins/ruberri/ruberri.rb +226 -0
  48. data/plugins/ruberri/search.rb +111 -0
  49. data/plugins/ruberri/ui/tool_widget.rb +73 -0
  50. data/plugins/ruberri/ui/tool_widget.ui +49 -0
  51. data/plugins/ruby_runner/ruby_runner.rb +2 -2
  52. data/plugins/ruby_syntax_checker/plugin.yaml +11 -0
  53. data/plugins/ruby_syntax_checker/ruby_syntax_checker.rb +147 -0
  54. data/plugins/syntax_checker/plugin.yaml +10 -6
  55. data/plugins/syntax_checker/syntax_checker.rb +216 -520
  56. data/plugins/syntax_checker/ui/config_widget.rb +61 -0
  57. data/plugins/syntax_checker/ui/config_widget.ui +44 -0
  58. data/plugins/yaml_syntax_checker/plugin.yaml +11 -0
  59. data/plugins/yaml_syntax_checker/yaml_syntax_checker.rb +62 -0
  60. data/ruber.desktop +0 -0
  61. data/spec/auto_end_spec.rb +224 -186
  62. data/spec/document_project_spec.rb +9 -1
  63. data/spec/document_spec.rb +9 -0
  64. data/spec/environment_spec.rb +12 -0
  65. data/spec/output_widget_spec.rb +69 -2
  66. data/spec/project_dir_scanner_spec.rb +195 -0
  67. data/spec/project_spec.rb +43 -73
  68. data/spec/ruby_syntax_checker_spec.rb +361 -0
  69. data/spec/syntax_checker_spec.rb +1132 -0
  70. data/spec/yaml_syntax_checker_spec.rb +130 -0
  71. metadata +232 -225
  72. data/lib/ruber/application/project_files_list.rb +0 -320
  73. data/spec/project_files_list_spec.rb +0 -411
@@ -0,0 +1,541 @@
1
+ =begin
2
+ Copyright (C) 2011 by Stefano Crocco
3
+ stefano.crocco@alice.it
4
+
5
+ This program is free software; you can redistribute it andor modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation; either version 2 of the License, or
8
+ (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ GNU General Public License for more details.
14
+
15
+ You should have received a copy of the GNU General Public License
16
+ along with this program; if not, write to the
17
+ Free Software Foundation, Inc.,
18
+ 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
+ =end
20
+
21
+ module Ruber
22
+
23
+ module IRB
24
+
25
+ # Incapsulates the information about the special prompt used by the plugin
26
+ class PromptMatcher
27
+
28
+ # Mapping between prompt types and characters used to represent them in the prompt
29
+ TYPES = {'n' => :normal, 's' => :string, 'c' => :statement, 'i' => :indent, 'r' => :return }
30
+
31
+ # Mapping between prompt names used by IRB and characters used to represent them in the prompt
32
+ PROMPTS = {:PROMPT_I => 'n', :PROMPT_N => 'i', :PROMPT_S => 's', :PROMPT_C => 'c', :RETURN => 'r'}
33
+
34
+ # @return [{Symbol=>String}] the prompt to be used in IRB
35
+ attr_reader :prompts
36
+
37
+ # @param [String] id the identifier used to mark the beginning and end of a prompt
38
+ # @param [{Symbol=>String}] prompts the prompts to use. The recognized keys are:
39
+ # @:PROMPT_I@, @:PROMPT_N@, @:PROMPT_S@, @:PROMPT_C@, @:RETURN@. They have the
40
+ # same meaninig as the keys of any entry in @IRB.conf[:PROMPT]@
41
+ def initialize id, prompts
42
+ @id = id
43
+ @prompts = {}
44
+ prompts.each_pair do |k, v|
45
+ @prompts[k] = "quirb:#{@id}:#{v}:quirb:#{@id}:#{PROMPTS[k]}:"
46
+ end
47
+ end
48
+
49
+ # Checks whether a string begins with a prompt
50
+ #
51
+ # @param [String] str the string to check
52
+ # @return [IrbLine, nil] an {IrbLine} with the information about the prompt and the
53
+ # string if the string starts with a prompt or *nil* if the string doesn't start
54
+ # with a prompt
55
+ def match str
56
+ res = str.split "quirb:#{@id}:", 3
57
+ return if !res or !res[0].empty? or res.size < 3
58
+ res[1].slice! -1 #removes an ending :
59
+ letter, sep, line = res[2].partition ':'
60
+ type = TYPES[letter]
61
+ if type then IrbLine.new type, line, res[1]
62
+ else nil
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ # A parsed line from IRB
69
+ #
70
+ # It contains information about the prompt for the line, the text of the line and the
71
+ # type and category of prompt
72
+ class IrbLine
73
+
74
+ # @return [Symbol] the type of line. It can be:
75
+ # * @:output@ if there's no prompt
76
+ # * @:return@ if the line starts with a @:RETURN@ prompt
77
+ # * @:normal@ if the line starts with a @:PROMPT_I@ prompt
78
+ # * @:string@ if the line starts with a @:PROMPT_S@ prompt
79
+ # * @:statement@ if the line starts with a @:PROMPT_C@ prompt
80
+ # * @:indent@ if the line starts with a @:PROMPT_N@ prompt
81
+ attr_reader :type
82
+
83
+ # @return [String] the content of the line
84
+ attr_reader :text
85
+
86
+ # @return [Symbol] the category of the line (basing on the prompt {#type}). It is
87
+ # @:output@ if the prompt type is @:output@ or @:return@ and @:input@ otherwise
88
+ attr_reader :category
89
+
90
+ # @return [String] the text of the prompt
91
+ attr_reader :prompt
92
+
93
+ # @return [String] the full text of the line, made from the IRB prompt and the line
94
+ # contents
95
+ attr_reader :full_text
96
+
97
+ # The category corresponding to each line type
98
+ CATEGORIES = {
99
+ :output => :output,
100
+ :return => :output,
101
+ :normal => :input,
102
+ :string => :input,
103
+ :statement => :input,
104
+ :indent => :input
105
+ }
106
+
107
+ # @param [Symbol] type the type of the line. It can be: @:output@, @:return@, @:normal@,
108
+ # @:string@, @:statement@ or @:indent@
109
+ # @param [String] text the text of the line
110
+ # @param [String] the text of the prompt
111
+ def initialize type, text, prompt
112
+ @text = text
113
+ @type = type
114
+ @prompt = prompt
115
+ @full_text = @prompt + @text
116
+ @category = (type == :return or type == :output) ? :output : :input
117
+ end
118
+
119
+ end
120
+
121
+ class IRBController < Qt::Object
122
+
123
+ # Signal emitted when there's output from irb to be read
124
+ #
125
+ # This signal can be emitted either as soon as IRB writes the output or at regular
126
+ # intervals (provided there's actually output availlable) according to the
127
+ # value of {#interval}
128
+ signals :output_ready
129
+
130
+ # Signal emitted just before IRB is stopped, for whatever reason. Connecting
131
+ # to this signal is the last opportunity to read the output from it.
132
+ signals :about_to_stop_irb
133
+
134
+ # Signal emitted just before interrupting IRB by sending a @SIGINT@ signal
135
+ signals :interrupting_evaluation
136
+
137
+ # Signal emitted after IRB has been interrupted by sending a @SIGINT@ signal
138
+ signals :evaluation_interrupted
139
+
140
+ # Signal emitted when IRB is ready to receive input (that is, when it gives
141
+ # a prompt when there aren't lines yet to be sent)
142
+ signals :ready
143
+
144
+ # @return [<String>] the options to pass to IRB. Note that changing this
145
+ # after IRB has been started won't have any effect until you restart IRB.
146
+ attr_accessor :irb_options
147
+
148
+ # @return [String] the path if the IRB program. Note that changing this
149
+ # after IRB has been started won't have any effect until you restart IRB.
150
+ attr_accessor :irb_program
151
+
152
+ # @return [Integer,nil] the minimum time interval (in milliseconds) between
153
+ # two {#output_ready} methods. If *nil*, the {#output_ready} signal will
154
+ # be emitted as soon as output is received from IRB. The default value is
155
+ # 100 milliseconds
156
+ attr_accessor :interval
157
+
158
+ # @param [String] irb the path of the IRB program
159
+ # @param [<String>] options the options to pass to the IRB program
160
+ # @param [Qt::Object,nil] parent the parent object
161
+ def initialize irb, options, parent = nil
162
+ super parent
163
+ @irb_program = irb
164
+ @irb_options = options.dup
165
+ @timer = Qt::Timer.new self
166
+ connect @timer, SIGNAL(:timeout), self, SLOT(:timer_ticked)
167
+ @pending_prompt = nil
168
+ @interrupting = false
169
+ @evaluating = false
170
+ @input = []
171
+ @output = []
172
+ @interval = 100
173
+ end
174
+
175
+ # Whether or not there's output ready to be read
176
+ # @return [Boolean] *true* if there's output to be read using {#output} and
177
+ # *false* otherwise
178
+ def has_output?
179
+ !@output.empty?
180
+ end
181
+
182
+ # Interrupts IRB evaluation by sending IRB the @SIGINT@ signal
183
+ #
184
+ # This will also clear any pending input. The {#output_ready} signal won't
185
+ # be emitted until IRB sends an empty prompt. The {#interrupting_evaluation}
186
+ # signal is emitted before sending IRB the @SIGINT@ signal
187
+ # @return nil
188
+ def interrupt
189
+ @interrupting = true
190
+ @timer.stop
191
+ @input.clear
192
+ emit interrupting_evaluation
193
+ Process.kill :INT, @irb.pid
194
+ nil
195
+ end
196
+
197
+ # Sends input to IRB
198
+ #
199
+ # The input lines are sent one by one, expecting a prompt before sending a
200
+ # new one. The {#ready} signal is emitted at the first prompt after the last
201
+ # line has been sent. If there are lines waiting to be sent, the new ones
202
+ # will be added at the end of the queue.
203
+ # @param [<String>] input the lines to send IRB
204
+ # @return [nil]
205
+ def send_to_irb input
206
+ @input.concat input
207
+ unless @evaluating
208
+ @evaluating = true
209
+ send_next_line
210
+ end
211
+ nil
212
+ end
213
+ slots :send_to_irb
214
+
215
+ # Stops the IRB process
216
+ #
217
+ # The {#about_to_stop_irb} is emitted before stopping IRB. No further
218
+ # {#output_ready} signals are emitted.
219
+ #
220
+ # You'll need to call {#start_irb} if you want IRB to be restarted after
221
+ # calling this method. If all you need is to stop IRB and immediately restart
222
+ # it, however, use {#restart_irb} rather than {#stop}
223
+ #
224
+ # @return [nil]
225
+ # @note this method will wait for up to two seconds for IRB to stop. If it
226
+ # doesn't stop by that time, it will return all the same.
227
+ def stop_irb
228
+ disconnect @irb, SIGNAL('finished(int, QProcess::ExitStatus)'), self, SLOT(:irb_finished)
229
+ stop :kill
230
+ # It seems it takes some time before irb is killed.
231
+ @irb.wait_for_finished 2000
232
+ end
233
+
234
+ # Stops and immediately restarts the IRB process
235
+ # The {#about_to_stop_irb} is emitted before stopping IRB. After emitting
236
+ # this signal, all output will be cleared, so you won't be able to access it
237
+ # anymore.
238
+ #
239
+ # @return [nil]
240
+ def restart_irb
241
+ stop :terminate
242
+ nil
243
+ end
244
+ slots :restart_irb
245
+
246
+ # Sends IRB a signal
247
+ #
248
+ # @param [String,Symbol,Integer] signal the name or number of the signal.
249
+ # Signal names may be with or without the @SIG@ prefix
250
+ # @raise [ArgumentError] if the signal name or number is not valid
251
+ # @raise [RuntimeError] if it wasn't possible to send the signal
252
+ # @return [nil]
253
+ # @note if you want to stop evaluation (for example, to exit and endless loop),
254
+ # do not use this method to send a @SIGINT@, but use {#interrupt}. Simply
255
+ # sending a @SIGINT@ signal may not work very well in that case, as, if IRB
256
+ # keeps sending output, {#output_ready} signals will kept being emitted
257
+ def send_signal signal
258
+ pid = @irb.pid
259
+ begin Process.kill signal, pid
260
+ rescue Errno::EINVAL, RangeError, ArgumentError
261
+ raise ArgumentError, "Invalid signal #{signal}"
262
+ rescue Errno::ESRCH, Errno::EPERM
263
+ raise RuntimeError, "It wasn't possible to send IRB a signal"
264
+ end
265
+ nil
266
+ end
267
+
268
+ # Changes the prompt
269
+ #
270
+ # Calling this method is the same as adding a new entry to @IRB.conf[:PROMPT]@
271
+ # and changing the current prompt using @IRB.conf.prompt_mode=@.
272
+ #
273
+ # @param [{Symbol=>String}] the new prompt. The recognized keys are:
274
+ # @:PROMPT_I@, @:PROMPT_N@, @:PROMPT_S@, @:PROMPT_C@, @:RETURN@. They have the
275
+ # same meaninig as the keys of any entry in @IRB.conf[:PROMPT]@
276
+ # @return [nil]
277
+ # @note in the return prompt, don't add the ending @%s\n@, as it will be
278
+ # added automatically
279
+ # @note users must not use @IRB.conf[:PROMPT]@ or @IRB.conf.prompt_mode=@
280
+ # to change IRB's prompt, but always call this method. This is
281
+ # because {IRBController}, behind the scenes, needs to add a special string to the
282
+ # prompt chosen by the user to work correctly
283
+ # @note changing the prompt takes effect immediately, even if IRB has already
284
+ # been started
285
+ def prompts= prompts
286
+ id = Array.new(5){rand(10)}.join ''
287
+ @prompt = PromptMatcher.new id, prompts
288
+ change_irb_prompt if running?
289
+ end
290
+
291
+ # Starts IRB
292
+ #
293
+ # If IRB is already running, it'll be stopped. The {#ready} signal will be
294
+ # emitted when IRB is ready to accept output.
295
+ #
296
+ # This method will wait for IRB to start for up to two seconds before returning.
297
+ # @return [Boolean] *true* if IRB was started successfully and *false* if
298
+ # it didn't start within two seconds
299
+ def start_irb
300
+ if @irb
301
+ @irb.kill
302
+ @irb.delete_later
303
+ end
304
+ @irb = Qt::Process.new self
305
+ @irb.process_channel_mode = Qt::Process::MergedChannels
306
+ set_irb_env
307
+ connect @irb, SIGNAL('finished(int, QProcess::ExitStatus)'), self, SLOT(:irb_finished)
308
+ options = @irb_options + ['--noreadline']
309
+ @irb.start @irb_program, options
310
+ @irb.wait_for_started 2000
311
+ if @irb.state == Qt::Process::Running
312
+ change_irb_prompt
313
+ true
314
+ else false
315
+ end
316
+ end
317
+
318
+ # @return [Boolean] whether IRB is running or not
319
+ def running?
320
+ @irb and (@irb.state == Qt::Process::Running)
321
+ end
322
+
323
+ # The state of the IRB process
324
+ #
325
+ # @return [Symbol] @:running@ if the IRB process is running, @:starting@ if
326
+ # an attempt to start the IRB process was made but IRB hasn't started yet
327
+ # and @:not_running@ if either IRB has not yet been started or if the attempt
328
+ # to start it has failed
329
+ def state
330
+ if @irb
331
+ case @irb.state
332
+ when Qt::Process::Running then :running
333
+ when Qt::Process::Starting then :starting
334
+ else :not_running
335
+ end
336
+ else :not_running
337
+ end
338
+ end
339
+
340
+ # Reads a number of lines of output
341
+ #
342
+ # Lines which have been read are removed from the output buffer
343
+ # @param [Integer,nil] n the number of lines to read
344
+ # @return [<IrbLine>] an array containing the first _n_ lines of output. If
345
+ # there are less than _n_ lines of output, all of them are returned. If
346
+ # there's no output, an empty array is returned. If _n_ is *nil*, the whole
347
+ # content of the output buffer is returned
348
+ def output n = 100
349
+ if n then n = [n, @output.size].min
350
+ else n = @output.count
351
+ end
352
+ @output.shift n
353
+ end
354
+
355
+ private
356
+
357
+ # Sends IRB the command to change the prompt
358
+ #
359
+ # IRB should not be given input until this command has been executed
360
+ # @return [nil]
361
+ def change_irb_prompt
362
+ prompts = @prompt.prompts.dup
363
+ prompts[:RETURN] += "%s\n"
364
+ disconnect @irb, SIGNAL(:readyReadStandardOutput), self, SLOT(:process_output)
365
+ connect @irb, SIGNAL(:readyReadStandardOutput), self, SLOT(:wait_for_prompt_changed)
366
+ cmd = "IRB.conf[:PROMPT][:QUIRB]=#{prompts.inspect}\nconf.prompt_mode = :QUIRB\n"
367
+ @irb.write cmd
368
+ nil
369
+ end
370
+
371
+ # Helper method to stop IRB
372
+ #
373
+ # It's used by both {#stop_irb} and {#restart_irb}. It takes care of reading
374
+ # any unread output from IRB, emitting the {#about_to_stop_irb} signal,
375
+ # stopping the timer and so on.
376
+ # @param [Symbol] meth the method to call to stop IRB. It can be either
377
+ # :kill or :terminate, which will call respectively @Qt::Process#kill@ or
378
+ # @Qt::Process#terminate@
379
+ # @return [nil]
380
+ def stop meth
381
+ parse_output @irb.read_all_standard_output.to_s.split("\n")
382
+ emit about_to_stop_irb
383
+ @timer.stop
384
+ @irb.disconnect SIGNAL(:readyReadStandardOutput)
385
+ @irb.send meth
386
+ nil
387
+ end
388
+
389
+ # Changes the environment associated with the IRB process
390
+ #
391
+ # The environment is changed by adding an @IRBRC@ variable which points to
392
+ # the @irbrc.rb@ file in the same directory as @irb_controller.rb@.
393
+ # @return [nil]
394
+ def set_irb_env
395
+ dir = File.dirname(__FILE__)
396
+ vars = ['IRBRC', File.join(dir, 'irbrc.rb')]
397
+ if defined? Qt::ProcessEnvironment
398
+ env = Qt::ProcessEnvironment.system_environment
399
+ env.insert *vars
400
+ @irb.process_environment = env
401
+ else
402
+ env = @irb.system_environment
403
+ env << vars.join('=')
404
+ @irb.environment = env
405
+ end
406
+ nil
407
+ end
408
+
409
+ # Slot called whenever IRB sends output before changes to the prompt have
410
+ # taken effect
411
+ #
412
+ # If the lines sent by IRB contain the correct prompt, the @readyReadStandardOutput@
413
+ # signal from the IRB process will be connected to the {#process_output}
414
+ # signal and the {#ready} signal will be emitted
415
+ def wait_for_prompt_changed
416
+ lines = @irb.read_all_standard_output.to_s.split "\n"
417
+ found = false
418
+ lines.each do |l|
419
+ if @prompt.match l
420
+ disconnect @irb, SIGNAL(:readyReadStandardOutput), self, SLOT(:wait_for_prompt_changed)
421
+ connect @irb, SIGNAL(:readyReadStandardOutput), self, SLOT(:process_output)
422
+ found = true
423
+ break
424
+ end
425
+ end
426
+ if found
427
+ empty_prompt = lines.reverse_each.find do |l|
428
+ res = @prompt.match l
429
+ res and res.category == :input and res.text.empty?
430
+ end
431
+ @pending_prompt = @prompt.match empty_prompt if empty_prompt
432
+ end
433
+ emit ready
434
+ end
435
+ slots :wait_for_prompt_changed
436
+
437
+ # Restarts IRB after it has been terminated
438
+ #
439
+ # This method is connected to the IRB process's @finished(int, QProcess::ExitStatus)@
440
+ # signal, so that a new IRB process can be automatically started. This doesn't
441
+ # happen when {#stop_irb} is used to kill the IRB process
442
+ # @return [nil]
443
+ def irb_finished
444
+ @irb.delete_later
445
+ start_irb
446
+ nil
447
+ end
448
+ slots :irb_finished
449
+
450
+ # Sends the next line of input to the IRB process
451
+ #
452
+ # If there's no queued input line, the {#ready} signal will be emitted
453
+ # @return [nil]
454
+ def send_next_line
455
+ if @input.empty?
456
+ @evaluating = false
457
+ emit ready
458
+ else @irb.write @input.shift + "\n"
459
+ end
460
+ end
461
+
462
+ # Slot called whenever IRB sends output after the prompt has correctly
463
+ # been set up
464
+ #
465
+ # It adds the output lines to the output buffer, after converting them to
466
+ # {IrbLine} objects. If the last line is a prompt line and the controller
467
+ # is sending input to IRB, it calls {#send_next_line}.
468
+ #
469
+ # If the controller is interrupting IRB and a prompt is found, the {#evaluation_interrupted}
470
+ # signal is emitted, followed by the {#ready} signal
471
+ # @return [nil]
472
+ def process_output
473
+ new_lines = @irb.read_all_standard_output.to_s.split "\n"
474
+ lines = parse_output new_lines
475
+ return unless @prompt
476
+ return unless lines
477
+ @output.concat lines
478
+ if lines.last and lines.last.category == :input and lines.last.text.empty?
479
+ is_ready = true
480
+ @pending_prompt = @output.pop
481
+ end
482
+ if @prompt and !@interrupting
483
+ emit output_ready if !@interval or @output.count <= 100
484
+ @timer.start(@interval) if @interval and !@output.empty? and !@timer.active?
485
+ send_next_line if (@evaluating and is_ready) or !@evaluating
486
+ elsif @interrupting
487
+ if is_ready
488
+ @interrupting = false
489
+ emit evaluation_interrupted
490
+ @output.clear
491
+ send_next_line
492
+ end
493
+ end
494
+ nil
495
+ end
496
+ slots :process_output
497
+
498
+ # Slot called in response to the timer timing out
499
+ #
500
+ # It emits the {#output_ready} signal and stops the timer if there's no
501
+ # more output
502
+ # @return [nil]
503
+ def timer_ticked
504
+ emit output_ready
505
+ @timer.stop if @output.empty?
506
+ nil
507
+ end
508
+ slots :timer_ticked
509
+
510
+ # Parses output lines from IRB, converting them to {IrbLine} objects
511
+ #
512
+ # If the prompt has been set up, the lines will be processed using {PromptMatcher#match},
513
+ # otherwise they'll all be considered output lines with no prompt.
514
+ #
515
+ # If the controller is sending input to IRB and there's a pending prompt,
516
+ # that prompt will be added to the beginning of the first line, then removed
517
+ # @param [<String>] lines the lines to process
518
+ # @return [<IrbLine>, nil] an array of {IrbLine} objects corresponding to
519
+ # the processed lines or *nil* if _lines_ is empty
520
+ def parse_output lines
521
+ return if lines.empty?
522
+ if @prompt
523
+ parsed_lines = lines.map do |l|
524
+ @prompt.match(l) || IrbLine.new( :output, l, '')
525
+ end
526
+ else
527
+ parsed_lines = lines.map{|l| IrbLine.new :output, l, ''}
528
+ end
529
+ if @evaluating and @pending_prompt and parsed_lines[0].type == :output
530
+ parsed_lines[0] = IrbLine.new :normal, lines[0], @pending_prompt.prompt
531
+ @pending_prompt = nil
532
+ end
533
+ parsed_lines
534
+ end
535
+ slots :parse_output
536
+
537
+ end
538
+
539
+ end
540
+
541
+ end