scriptty 0.5.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/.gitattributes +1 -0
  2. data/.gitignore +3 -0
  3. data/COPYING +674 -0
  4. data/COPYING.LESSER +165 -0
  5. data/README.rdoc +31 -0
  6. data/Rakefile +49 -0
  7. data/VERSION +1 -0
  8. data/bin/scriptty-capture +5 -0
  9. data/bin/scriptty-dump-screens +4 -0
  10. data/bin/scriptty-replay +5 -0
  11. data/bin/scriptty-term-test +4 -0
  12. data/bin/scriptty-transcript-parse +4 -0
  13. data/examples/captures/xterm-overlong-line-prompt.bin +9 -0
  14. data/examples/captures/xterm-vim-session.bin +262 -0
  15. data/examples/demo-capture.rb +19 -0
  16. data/examples/telnet-nego.rb +55 -0
  17. data/lib/scriptty/apps/capture_app/console.rb +104 -0
  18. data/lib/scriptty/apps/capture_app/password_prompt.rb +65 -0
  19. data/lib/scriptty/apps/capture_app.rb +213 -0
  20. data/lib/scriptty/apps/dump_screens_app.rb +166 -0
  21. data/lib/scriptty/apps/replay_app.rb +229 -0
  22. data/lib/scriptty/apps/term_test_app.rb +124 -0
  23. data/lib/scriptty/apps/transcript_parse_app.rb +143 -0
  24. data/lib/scriptty/cursor.rb +39 -0
  25. data/lib/scriptty/exception.rb +38 -0
  26. data/lib/scriptty/expect.rb +392 -0
  27. data/lib/scriptty/multiline_buffer.rb +192 -0
  28. data/lib/scriptty/net/event_loop.rb +610 -0
  29. data/lib/scriptty/screen_pattern/generator.rb +398 -0
  30. data/lib/scriptty/screen_pattern/parser.rb +558 -0
  31. data/lib/scriptty/screen_pattern.rb +104 -0
  32. data/lib/scriptty/term/dg410/dg410-client-escapes.txt +37 -0
  33. data/lib/scriptty/term/dg410/dg410-escapes.txt +82 -0
  34. data/lib/scriptty/term/dg410/parser.rb +162 -0
  35. data/lib/scriptty/term/dg410.rb +489 -0
  36. data/lib/scriptty/term/xterm/xterm-escapes.txt +73 -0
  37. data/lib/scriptty/term/xterm.rb +661 -0
  38. data/lib/scriptty/term.rb +40 -0
  39. data/lib/scriptty/util/fsm/definition_parser.rb +111 -0
  40. data/lib/scriptty/util/fsm/scriptty_fsm_definition.treetop +189 -0
  41. data/lib/scriptty/util/fsm.rb +177 -0
  42. data/lib/scriptty/util/transcript/reader.rb +96 -0
  43. data/lib/scriptty/util/transcript/writer.rb +111 -0
  44. data/test/apps/capture_app_test.rb +123 -0
  45. data/test/apps/transcript_parse_app_test.rb +118 -0
  46. data/test/cursor_test.rb +51 -0
  47. data/test/fsm_definition_parser_test.rb +220 -0
  48. data/test/fsm_test.rb +322 -0
  49. data/test/multiline_buffer_test.rb +275 -0
  50. data/test/net/event_loop_test.rb +402 -0
  51. data/test/screen_pattern/generator_test.rb +408 -0
  52. data/test/screen_pattern/parser_test/explicit_cursor_pattern.txt +14 -0
  53. data/test/screen_pattern/parser_test/explicit_fields.txt +22 -0
  54. data/test/screen_pattern/parser_test/multiple_patterns.txt +42 -0
  55. data/test/screen_pattern/parser_test/simple_pattern.txt +14 -0
  56. data/test/screen_pattern/parser_test/truncated_heredoc.txt +12 -0
  57. data/test/screen_pattern/parser_test/utf16bebom_pattern.bin +0 -0
  58. data/test/screen_pattern/parser_test/utf16lebom_pattern.bin +0 -0
  59. data/test/screen_pattern/parser_test/utf8_pattern.bin +14 -0
  60. data/test/screen_pattern/parser_test/utf8_unix_pattern.bin +14 -0
  61. data/test/screen_pattern/parser_test/utf8bom_pattern.bin +14 -0
  62. data/test/screen_pattern/parser_test.rb +266 -0
  63. data/test/term/dg410/parser_test.rb +139 -0
  64. data/test/term/xterm_test.rb +327 -0
  65. data/test/test_helper.rb +3 -0
  66. data/test/util/transcript/reader_test.rb +131 -0
  67. data/test/util/transcript/writer_test.rb +126 -0
  68. data/test.watchr +29 -0
  69. metadata +175 -0
@@ -0,0 +1,661 @@
1
+ # = XTerm terminal emulation
2
+ # Copyright (C) 2010 Infonium Inc.
3
+ #
4
+ # This file is part of ScripTTY.
5
+ #
6
+ # ScripTTY is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ScripTTY is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with ScripTTY. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ # TODO - This is incomplete
20
+
21
+ require 'scriptty/multiline_buffer'
22
+ require 'scriptty/cursor'
23
+ require 'scriptty/util/fsm'
24
+ require 'set'
25
+
26
+ module ScripTTY # :nodoc:
27
+ module Term
28
+ class XTerm
29
+
30
+ PARSER_DEFINITION = File.read(File.join(File.dirname(__FILE__), "xterm/xterm-escapes.txt"))
31
+ DEFAULT_FLAGS = {
32
+ :insert_mode => false,
33
+ :wraparound_mode => false,
34
+ }.freeze
35
+
36
+ # width and height of the display buffer
37
+ attr_reader :width, :height
38
+
39
+ def initialize(height=24, width=80)
40
+ @parser_fsm = Util::FSM.new(:definition => PARSER_DEFINITION,
41
+ :callback => self, :callback_method => :send)
42
+
43
+ @height = height
44
+ @width = width
45
+
46
+ on_unknown_sequence :error
47
+ reset_to_initial_state!
48
+ end
49
+
50
+ # Set the behaviour of the terminal when an unknown escape sequence is
51
+ # found.
52
+ #
53
+ # This method takes either a symbol or a block.
54
+ #
55
+ # When a block is given, it is executed whenever an unknown escape
56
+ # sequence is received. The block is passed the escape sequence as a
57
+ # single string.
58
+ #
59
+ # When a symbol is given, it may be one of the following:
60
+ # [:error]
61
+ # (default) Raise a ScripTTY::Util::FSM::NoMatch exception.
62
+ # [:ignore]
63
+ # Ignore the unknown escape sequence.
64
+ def on_unknown_sequence(mode=nil, &block)
65
+ if !block and !mode
66
+ raise ArgumentError.new("No mode specified and no block given")
67
+ elsif block and mode
68
+ raise ArgumentError.new("Block and mode are mutually exclusive, but both were given")
69
+ elsif block
70
+ @on_unknown_sequence = block
71
+ elsif [:error, :ignore].include?(mode)
72
+ @on_unknown_sequence = mode
73
+ else
74
+ raise ArgumentError.new("Invalid mode #{mode.inspect}")
75
+ end
76
+ end
77
+
78
+ def inspect # :nodoc:
79
+ # The default inspect method shows way too much information. Simplify it.
80
+ "#<#{self.class.name}:#{sprintf('0x%0x', object_id)} h=#{@height.inspect} w=#{@width.inspect} cursor=#{cursor_pos.inspect}>"
81
+ end
82
+
83
+ # Feed the specified byte to the terminal. Returns a string of
84
+ # bytes that should be transmitted (e.g. for TELNET negotiation).
85
+ def feed_byte(byte)
86
+ raise ArgumentError.new("input should be single byte") unless byte.is_a?(String) and byte.length == 1
87
+ begin
88
+ @parser_fsm.process(byte)
89
+ rescue Util::FSM::NoMatch => e
90
+ @parser_fsm.reset!
91
+ if @on_unknown_sequence == :error
92
+ raise
93
+ elsif @on_unknown_sequence == :ignore
94
+ # do nothing
95
+ elsif !@on_unknown_sequence.is_a?(Symbol) # @on_unknown_sequence is a Proc
96
+ @on_unknown_sequence.call(e.input_sequence.join)
97
+ else
98
+ raise "BUG"
99
+ end
100
+ end
101
+ ""
102
+ end
103
+
104
+ # Convenience method: Feeds several bytes to the terminal. Returns a
105
+ # string of bytes that should be transmitted (e.g. for TELNET
106
+ # negotiation).
107
+ def feed_bytes(bytes)
108
+ retvals = []
109
+ bytes.split(//n).each do |byte|
110
+ retvals << feed_byte(byte)
111
+ end
112
+ retvals.join
113
+ end
114
+
115
+
116
+ # Return an array of strings representing the lines of text on the screen
117
+ #
118
+ # NOTE: If passing copy=false, do not modify the return value or the strings inside it.
119
+ def text(copy=true)
120
+ if copy
121
+ @glyphs.content.map{|line| line.dup}
122
+ else
123
+ @glyphs.content
124
+ end
125
+ end
126
+
127
+ # Return the cursor position, as an array of [row, column].
128
+ #
129
+ # [0,0] represents the topmost, leftmost position.
130
+ def cursor_pos
131
+ [@cursor.row, @cursor.column]
132
+ end
133
+
134
+ # Set the cursor position to [row, column].
135
+ #
136
+ # [0,0] represents the topmost, leftmost position.
137
+ def cursor_pos=(v)
138
+ @cursor.pos = v
139
+ end
140
+
141
+ # Replace the text on the screen with the specified text.
142
+ #
143
+ # NOTE: This is API is very likely to change in the future.
144
+ def text=(a)
145
+ @glyphs.clear!
146
+ @glyphs.replace_at(0, 0, a)
147
+ a
148
+ end
149
+
150
+ protected
151
+
152
+ # Reset to the initial state. Return true.
153
+ def reset_to_initial_state!
154
+ @flags = DEFAULT_FLAGS.dup
155
+
156
+ # current cursor position
157
+ @cursor = Cursor.new
158
+ @cursor.row = @cursor.column = 0
159
+ @saved_cursor_position = [0,0]
160
+
161
+ # Screen buffer
162
+ @glyphs = MultilineBuffer.new(@height, @width) # the displayable characters (as bytes)
163
+ @attrs = MultilineBuffer.new(@height, @width) # character attributes (as bytes)
164
+
165
+ # Vertical scrolling region. An array of [start_row, end_row]. Defaults to [0, height-1].
166
+ @scrolling_region = [0, @height-1]
167
+ true
168
+ end
169
+
170
+ # Replace the character under the cursor with the specified character.
171
+ #
172
+ # If curfwd is true, the cursor is also moved forward.
173
+ #
174
+ # Returns true.
175
+ def put_char!(input, curfwd=false)
176
+ raise TypeError.new("input must be single-character string") unless input.is_a?(String) and input.length == 1
177
+ @glyphs.replace_at(@cursor.row, @cursor.column, input)
178
+ @attrs.replace_at(@cursor.row, @cursor.column, " ")
179
+ cursor_forward! if curfwd
180
+ true
181
+ end
182
+
183
+ # Move the cursor to the leftmost column in the current row, then return true.
184
+ def carriage_return!
185
+ @cursor.column = 0
186
+ true
187
+ end
188
+
189
+ # Move the cursor down one row and return true.
190
+ #
191
+ # If the cursor is on the bottom row of the vertical scrolling region,
192
+ # the region is scrolled. If bot, but the cursor is on the bottom of
193
+ # the screen, this command has no effect.
194
+ def line_feed!
195
+ if @cursor.row == @scrolling_region[1] # cursor is on the bottom row of the scrolling region
196
+ scroll_up!
197
+ elsif @cursor.row >= @height-1
198
+ # do nothing
199
+ else
200
+ cursor_down!
201
+ end
202
+ true
203
+ end
204
+
205
+ # Save the cursor position. Return true.
206
+ def save_cursor!
207
+ @saved_cursor_position = [@cursor.row, @cursor.column]
208
+ true
209
+ end
210
+
211
+ # Restore the saved cursor position. If nothing has been saved, then go to the home position. Return true.
212
+ def restore_cursor!
213
+ @cursor.row, @cursor.column = @saved_cursor_position
214
+ true
215
+ end
216
+
217
+ # Move the cursor down one row and return true.
218
+ # If the cursor is on the bottom row, return false without moving the cursor.
219
+ def cursor_down!
220
+ if @cursor.row >= @height-1
221
+ false
222
+ else
223
+ @cursor.row += 1
224
+ true
225
+ end
226
+ end
227
+
228
+ # Move the cursor up one row and return true.
229
+ # If the cursor is on the top row, return false without moving the cursor.
230
+ def cursor_up!
231
+ if @cursor.row <= 0
232
+ false
233
+ else
234
+ @cursor.row -= 1
235
+ true
236
+ end
237
+ end
238
+
239
+ # Move the cursor right one column and return true.
240
+ # If the cursor is on the right-most column, return false without moving the cursor.
241
+ def cursor_right!
242
+ if @cursor.column >= @width-1
243
+ false
244
+ else
245
+ @cursor.column += 1
246
+ true
247
+ end
248
+ end
249
+
250
+ # Move the cursor to the right. Wrap around if we reach the end of the screen.
251
+ #
252
+ # Return true.
253
+ def cursor_forward!(options={})
254
+ if @cursor.column >= @width-1
255
+ line_feed!
256
+ carriage_return!
257
+ else
258
+ cursor_right!
259
+ end
260
+ end
261
+
262
+ # Move the cursor left one column and return true.
263
+ # If the cursor is on the left-most column, return false without moving the cursor.
264
+ def cursor_left!
265
+ if @cursor.column <= 0
266
+ false
267
+ else
268
+ @cursor.column -= 1
269
+ true
270
+ end
271
+ end
272
+
273
+ alias cursor_back! cursor_left! # In the future, this might not be an alias
274
+
275
+ # Scroll the contents of the screen up by one row and return true.
276
+ # The position of the cursor does not change.
277
+ def scroll_up!
278
+ @glyphs.scroll_up_region(@scrolling_region[0], 0, @scrolling_region[1], @width-1, 1)
279
+ @attrs.scroll_up_region(@scrolling_region[0], 0, @scrolling_region[1], @width-1, 1)
280
+ true
281
+ end
282
+
283
+ # Scroll the contents of the screen down by one row and return true.
284
+ # The position of the cursor does not change.
285
+ def scroll_down!
286
+ @glyphs.scroll_down_region(@scrolling_region[0], 0, @scrolling_region[1], @width-1, 1)
287
+ @attrs.scroll_down_region(@scrolling_region[0], 0, @scrolling_region[1], @width-1, 1)
288
+ true
289
+ end
290
+
291
+ # Erase, starting with the character under the cursor and extending to the end of the line.
292
+ # Return true.
293
+ def erase_to_end_of_line!
294
+ @glyphs.replace_at(@cursor.row, @cursor.column, " "*(@width-@cursor.column))
295
+ @attrs.replace_at(@cursor.row, @cursor.column, " "*(@width-@cursor.column))
296
+ true
297
+ end
298
+
299
+ # Erase, starting with the beginning of the line and extending to the character under the cursor.
300
+ # Return true.
301
+ def erase_to_start_of_line!
302
+ @glyphs.replace_at(@cursor.row, 0, " "*(@cursor.column+1))
303
+ @attrs.replace_at(@cursor.row, 0, " "*(@cursor.column+1))
304
+ true
305
+ end
306
+
307
+ # Erase the current line. The cursor position is unchanged.
308
+ # Return true.
309
+ def erase_line!
310
+ @glyphs.replace_at(@cursor.row, 0, " "*@width)
311
+ @attrs.replace_at(@cursor.row, 0, " "*@width)
312
+ true
313
+ end
314
+
315
+ # Erase the window. Return true.
316
+ def erase_window!
317
+ empty_line = " "*@width
318
+ @height.times do |row|
319
+ @glyphs.replace_at(row, 0, empty_line)
320
+ @attrs.replace_at(row, 0, empty_line)
321
+ end
322
+ true
323
+ end
324
+
325
+ # Delete the specified number of lines, starting at the cursor position
326
+ # extending downwards. The lines below the deleted lines are scrolled up,
327
+ # and blank lines are inserted below them.
328
+ # Return true.
329
+ def delete_lines!(count=1)
330
+ @glyphs.scroll_up_region(@cursor.row, 0, @height-1, @width-1, count)
331
+ @attrs.scroll_up_region(@cursor.row, 0, @height-1, @width-1, count)
332
+ true
333
+ end
334
+
335
+ # Delete the specified number of characters, starting at the cursor position
336
+ # extending to the end of the line. The characters to the right of the
337
+ # cursor are scrolled left, and blanks are inserted after them.
338
+ # Return true.
339
+ def delete_characters!(count=1)
340
+ @glyphs.scroll_left_region(@cursor.row, @cursor.column, @cursor.row, @width-1, count)
341
+ @attrs.scroll_left_region(@cursor.row, @cursor.column, @cursor.row, @width-1, count)
342
+ true
343
+ end
344
+
345
+ # Insert the specified number of blank characters at the cursor position.
346
+ # The characters to the right of the cursor are scrolled right, and blanks
347
+ # are inserted in their place.
348
+ # Return true.
349
+ def insert_blank_characters!(count=1)
350
+ @glyphs.scroll_right_region(@cursor.row, @cursor.column, @cursor.row, @width-1, count)
351
+ @attrs.scroll_right_region(@cursor.row, @cursor.column, @cursor.row, @width-1, count)
352
+ true
353
+ end
354
+
355
+ # Insert the specified number of lines characters at the cursor position.
356
+ # The characters to the below the cursor are scrolled down, and blank
357
+ # lines are inserted in their place.
358
+ # Return true.
359
+ def insert_blank_lines!(count=1)
360
+ @glyphs.scroll_down_region(@cursor.row, 0, @height-1, @width-1, count)
361
+ @attrs.scroll_down_region(@cursor.row, 0, @height-1, @width-1, count)
362
+ true
363
+ end
364
+
365
+ private
366
+
367
+ # Set the vertical scrolling region.
368
+ #
369
+ # Values will be clipped.
370
+ def set_scrolling_region!(top, bottom)
371
+ @scrolling_region[0] = [0, [@height-1, top].min].max
372
+ @scrolling_region[1] = [0, [@width-1, bottom].min].max
373
+ nil
374
+ end
375
+
376
+ def error(message) # XXX - This sucks
377
+ raise ArgumentError.new(message)
378
+ #puts message # DEBUG FIXME
379
+ end
380
+
381
+ def t_reset(fsm)
382
+ reset_to_initial_state!
383
+ end
384
+
385
+ # Printable character
386
+ def t_printable(fsm) # :nodoc:
387
+ insert_blank_characters! if @flags[:insert_mode] # TODO
388
+ put_char!(fsm.input)
389
+ cursor_forward!
390
+ end
391
+
392
+ # NUL character
393
+ def t_nul(fsm) end # TODO
394
+
395
+ # Beep
396
+ def t_bell(fsm) end # TODO
397
+
398
+ # Backspace
399
+ def t_bs(fsm)
400
+ cursor_back!
401
+ put_char!(" ")
402
+ end
403
+
404
+ def t_carriage_return(fsm)
405
+ carriage_return!
406
+ end
407
+
408
+ def t_new_line(fsm)
409
+ carriage_return!
410
+ line_feed!
411
+ end
412
+
413
+ def t_save_cursor(fsm)
414
+ save_cursor!
415
+ end
416
+
417
+ def t_restore_cursor(fsm)
418
+ restore_cursor!
419
+ end
420
+
421
+ # ESC [
422
+ def t_parse_csi(fsm)
423
+ fsm.redirect = lambda {|fsm| fsm.input =~ /[\d;]/n}
424
+ end
425
+
426
+ # Operating system controls
427
+ # ESC ] Ps ; Pt BEL
428
+ # "ESC ]", followed by a number and a semicolon, followed by printable text, followed by a non-printable character
429
+ def t_parse_osc(fsm)
430
+ fsm.redirect = lambda {|fsm| fsm.input =~ /[\x20-\x7e]/n}
431
+ end
432
+
433
+ # IAC SB ... SE
434
+ def t_parse_telnet_sb(fsm)
435
+ # limit subnegotiation to 100 chars
436
+ count = 0
437
+ fsm.redirect = lambda {|fsm| count += 1; count < 100 && fsm.input_sequence[-2..-1] != ["\377", "\360"]}
438
+ end
439
+
440
+ # ESC [ Ps J
441
+ def t_erase_in_display(fsm)
442
+ (mode,) = parse_csi_params(fsm.input_sequence)
443
+ mode ||= 0 # default is mode 0
444
+ case mode
445
+ when 0
446
+ # Erase from the cursor to the end of the window. Cursor position is unaffected.
447
+ erase_to_end_of_line!
448
+ when 1
449
+ # Erase the window. Cursor position is unaffected.
450
+ erase_window!
451
+ when 2
452
+ # Erase the window. Cursor moves to the home position.
453
+ erase_window!
454
+ @cursor.pos = [0,0]
455
+ else
456
+ # ignored
457
+ end
458
+ end
459
+
460
+ # ESC [ ? ... h
461
+ def t_dec_private_mode_set(fsm)
462
+ parse_csi_params(fsm.input_sequence).each do |mode|
463
+ case mode
464
+ when 1 # Application cursor keys
465
+ when 7 # Wraparound mode
466
+ @flags[:wraparound_mode] = true
467
+ when 47 # Use alternate screen buffer
468
+ else
469
+ return error("unknown DEC private mode set (escape sequence: #{fsm.input_sequence.inspect})")
470
+ end
471
+ end
472
+ end
473
+
474
+ # ESC [ ? ... l
475
+ def t_dec_private_mode_reset(fsm)
476
+ parse_csi_params(fsm.input_sequence).each do |mode|
477
+ case mode
478
+ when 1 # Normal cursor keys
479
+ when 7 # No wraparound mode
480
+ @flags[:wraparound_mode] = false
481
+ when 47 # Use normal screen buffer
482
+ else
483
+ return error("unknown DEC private mode reset (escape sequence: #{fsm.input_sequence.inspect})")
484
+ end
485
+ end
486
+ end
487
+
488
+ # ESC [ Ps; Ps r
489
+ def t_set_scrolling_region(fsm)
490
+ top, bottom = parse_csi_params(fsm.input_sequence)
491
+ top ||= 1
492
+ bottom ||= @height
493
+ @scrolling_region = [top-1, bottom-1]
494
+ end
495
+
496
+ # ESC [ Ps K
497
+ def t_erase_in_line(fsm)
498
+ (mode,) = parse_csi_params(fsm.input_sequence)
499
+ mode ||= 0
500
+ case mode
501
+ when 0 # Erase to right
502
+ erase_to_end_of_line!
503
+ when 1 # Erase to left
504
+ erase_to_start_of_line!
505
+ when 2 # Erase all
506
+ erase_line!
507
+ end
508
+ end
509
+
510
+ # ESC [ Ps A
511
+ def t_cursor_up(fsm)
512
+ count = parse_csi_params(fsm.input_sequence)[0] || 0
513
+ count = 1 if count < 1
514
+ count.times { cursor_up! }
515
+ end
516
+
517
+ # ESC [ Ps B
518
+ def t_cursor_down(fsm)
519
+ count = parse_csi_params(fsm.input_sequence)[0] || 0
520
+ count = 1 if count < 1
521
+ count.times { cursor_down! }
522
+ end
523
+
524
+ # ESC [ Ps C
525
+ def t_cursor_right(fsm)
526
+ count = parse_csi_params(fsm.input_sequence)[0] || 0
527
+ count = 1 if count < 1
528
+ count.times { cursor_right! }
529
+ end
530
+
531
+ # ESC [ Ps D
532
+ def t_cursor_left(fsm)
533
+ count = parse_csi_params(fsm.input_sequence)[0] || 0
534
+ count = 1 if count < 1
535
+ count.times { cursor_left! }
536
+ end
537
+
538
+ # ESC [ Ps ; Ps H
539
+ def t_cursor_position(fsm)
540
+ row, column = parse_csi_params(fsm.input_sequence)
541
+ row ||= 0; column ||= 0 # missing params set to 0
542
+ row -= 1; column -= 1
543
+ row = 0 if row < 0
544
+ column = 0 if column < 0
545
+ row = @height-1 if row >= @height
546
+ column = @width-1 if column >= @width
547
+ @cursor.pos = [row, column]
548
+ end
549
+
550
+ # Select graphic rendition
551
+ # ESC [ Pm m
552
+ def t_sgr(fsm)
553
+ params = parse_csi_params(fsm.input_sequence)
554
+ params.each do |param|
555
+ if param.nil?
556
+ # ignore
557
+ elsif param >= 30 and param <= 39
558
+ # TODO - Set foreground colour
559
+ elsif param >= 40 and param <= 49
560
+ # TODO - Set background colour
561
+ else
562
+ # ignore
563
+ end
564
+ end
565
+ end
566
+
567
+ # ESC [ Ps c
568
+ def t_send_device_attributes_primary(fsm) end # XXX TODO - respond with ESC [ ? ...
569
+
570
+ # ESC [ > Ps c
571
+ def t_send_device_attributes_secondary(fsm) end # XXX TODO - respond with ESC [ ? ...
572
+
573
+ # ESC [ Ps L
574
+ def t_insert_lines(fsm)
575
+ count = parse_csi_params(fsm.input_sequence)[0] || 1
576
+ insert_blank_lines!(count)
577
+ end
578
+
579
+ # ESC [ Ps M
580
+ def t_delete_lines(fsm)
581
+ count = parse_csi_params(fsm.input_sequence)[0] || 1
582
+ delete_lines!(count)
583
+ end
584
+
585
+ # ESC [ Ps P
586
+ def t_delete_characters(fsm)
587
+ count = parse_csi_params(fsm.input_sequence)[0] || 1
588
+ delete_characters!(count)
589
+ end
590
+
591
+ # ESC [ Ps g
592
+ def t_tab_clear(fsm) end # TODO
593
+
594
+ # ESC H
595
+ def t_tab_set(fsm) end # TODO
596
+
597
+ # ESC =
598
+ def t_application_keypad(fsm) end # TODO
599
+
600
+ # ESC >
601
+ def t_normal_keypad(fsm) end # TODO
602
+
603
+ def t_telnet_will(fsm) end # TODO
604
+ def t_telnet_wont(fsm) end # TODO
605
+ def t_telnet_do(fsm) end # TODO
606
+ def t_telnet_dont(fsm) end # TODO
607
+ def t_telnet_subnegotiation(fsm) end # TODO
608
+
609
+ def t_osc_set_text_params(fsm) end # TODO - used for setting window title, etc.
610
+
611
+ # ESC [ ... h
612
+ def t_set_mode(fsm)
613
+ parse_csi_params(fsm.input_sequence).each do |mode|
614
+ case mode
615
+ when 4 # Insert mode
616
+ @flags[:insert_mode] = true
617
+ else
618
+ return error("unknown set mode (escape sequence: #{fsm.input_sequence.inspect})")
619
+ end
620
+ end
621
+ end
622
+
623
+ # ESC >
624
+ def t_reset_mode(fsm)
625
+ parse_csi_params(fsm.input_sequence).each do |mode|
626
+ case mode
627
+ when 4 # Replace mode
628
+ @flags[:insert_mode] = false
629
+ else
630
+ return error("unknown reset mode (escape sequence: #{fsm.input_sequence.inspect})")
631
+ end
632
+ end
633
+ end
634
+
635
+ # Parse ANSI/DEC CSI escape sequence parameters. Pass in fsm.input_sequence
636
+ #
637
+ # Example:
638
+ # parse_csi_params("\e[H") # returns []
639
+ # parse_csi_params("\e[;H") # returns []
640
+ # parse_csi_params("\e[2J") # returns [2]
641
+ # parse_csi_params("\e[33;42;0m") # returns [33, 42, 0]
642
+ # parse_csi_params(["\e", "[", "3", "3", ";", "4" "2", ";" "0", "m"]) # same as above, but takes an array
643
+ #
644
+ # This also works with DEC escape sequences:
645
+ # parse_csi_params("\e[?1;2J") # returns [1,2]
646
+ def parse_csi_params(input_seq) # TODO - test this
647
+ seq = input_seq.join if input_seq.respond_to?(:join) # Convert array to string
648
+ unless seq =~ /\A\e\[\??([\d;]*)[^\d]\Z/n
649
+ raise "BUG"
650
+ end
651
+ $1.split(/;/n).map{|p|
652
+ if p.empty?
653
+ nil
654
+ else
655
+ p.to_i
656
+ end
657
+ }
658
+ end
659
+ end
660
+ end
661
+ end
@@ -0,0 +1,40 @@
1
+ # = Generic interface to terminal emulators
2
+ # Copyright (C) 2010 Infonium Inc.
3
+ #
4
+ # This file is part of ScripTTY.
5
+ #
6
+ # ScripTTY is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Lesser General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # ScripTTY is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public License
17
+ # along with ScripTTY. If not, see <http://www.gnu.org/licenses/>.
18
+
19
+ module ScripTTY
20
+ module Term
21
+ TERMINAL_TYPES = {
22
+ "dg410" => {:require => "scriptty/term/dg410", :class_name => "::ScripTTY::Term::DG410"},
23
+ "xterm" => {:require => "scriptty/term/xterm", :class_name => "::ScripTTY::Term::XTerm"},
24
+ }
25
+
26
+ # Load and instantiate the specified terminal by name
27
+ def self.new(name, *args, &block)
28
+ self.class_by_name(name).new(*args, &block)
29
+ end
30
+
31
+ # Load the specified terminal class by name
32
+ def self.class_by_name(name)
33
+ tt = TERMINAL_TYPES[name]
34
+ return nil unless tt
35
+ require tt[:require]
36
+ eval(tt[:class_name])
37
+ end
38
+ end
39
+ end
40
+