ruber 0.0.9 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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