term-vt102 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 42642e1555fefd4cc8829b4e4000c7daf8d24b43
4
+ data.tar.gz: 673456148028019d84d0a7de65c399d5dc2e5082
5
+ SHA512:
6
+ metadata.gz: 16e9bc770fcc90f0f292d0c35096dcc48743fbc629f0ecae524ca9db457df801d51fab6702b0ec19e66dcf231d1805655d190c09e1747705630aa5ea18d2514a
7
+ data.tar.gz: c4a16753bbc634516b7ee92815dcc51f0de1082af6fb17274bafc93c609b0e9219edbe5b4591087213283958ce315faa082c4396ceec2dc56b942ea7fac47bea
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.0
@@ -0,0 +1,25 @@
1
+ ## Credits
2
+
3
+ Mike Owens <mike@filespanker.com>
4
+ - Ruby Port.
5
+
6
+ The following people have contributed to the original Term::VT102 Perl module.
7
+
8
+ Andrew Wood <andrew dot wood at ivarch dot com>.
9
+ - Author of the Term::VT102 Perl module, without which
10
+ this project would not exist.
11
+
12
+ Charles Harker <CHarker at interland dot com>
13
+ - reported and helped to diagnose a bug in the handling of TABs
14
+
15
+ Steve van der Burg <steve dot vanderburg at lhsc dot on dot ca>
16
+ - supplied basis for an example script using Net::Telnet
17
+
18
+ Chris R. Donnelly <cdonnelly at digitalmotorworks dot com>
19
+ - added support for DECTCEM, partial support for SM/RM
20
+
21
+ Paul L. Stoddard
22
+ - reported a possible bug in cursor movement handling
23
+
24
+ Joerg Walter
25
+ - provided a patch for Unicode handling
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in term-vt102.gemspec
4
+ gemspec
@@ -0,0 +1,27 @@
1
+ Andrew Wood has granted permission for this port to be released under the
2
+ MIT License, which follows:
3
+ =====================================================================
4
+
5
+ Copyright (c) 2014 Mike Owens
6
+ Copyright (c) 2001-2008 Andrew Wood
7
+
8
+ MIT License
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining
11
+ a copy of this software and associated documentation files (the
12
+ "Software"), to deal in the Software without restriction, including
13
+ without limitation the rights to use, copy, modify, merge, publish,
14
+ distribute, sublicense, and/or sell copies of the Software, and to
15
+ permit persons to whom the Software is furnished to do so, subject to
16
+ the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be
19
+ included in all copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,50 @@
1
+ # Term::VT102
2
+
3
+ Term::VT102 provides emulation of a VT102 terminal, in Ruby. It's a great way
4
+ to automate interactions with remote systems, particularly ones that only
5
+ provide interactive/curses style interfaces. It can tell you what's on the
6
+ screen at any time, and notify you of changes.
7
+
8
+ A lot of terrible legacy applications fall into this category.
9
+
10
+ This gem is a port of Andrew Wood's Perl module, Term::VT102. Permission has
11
+ been granted to release this derived work under the MIT license.
12
+
13
+ Term::VT102 aims to be fairly literal port of the Perl module, and higher-level
14
+ features will most likely show up in other gems instead of being integrated
15
+ here.
16
+
17
+ ## Installation
18
+
19
+ Add this line to your application's Gemfile:
20
+
21
+ gem 'term-vt102'
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install term-vt102
30
+
31
+ ## Usage
32
+
33
+ ```ruby
34
+
35
+ require 'term/vt102'
36
+
37
+ vt = Term::VT102.new(cols: 80, rows: 25)
38
+
39
+ # Await patiently at your editor for me to write documentation, or check out
40
+ # the tests.
41
+
42
+ ```
43
+
44
+ ## Contributing
45
+
46
+ 1. Fork it ( http://github.com/mieko/term-vt102/fork )
47
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
48
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
49
+ 4. Push to the branch (`git push origin my-new-feature`)
50
+ 5. Create new Pull Request
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList['test/test_*.rb'].sort
7
+ end
8
+
9
+ task :default => :test
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # This example just pulls a cool little tmux trick.
4
+ #
5
+ # Steps:
6
+ # 1. Start a tmux session
7
+ # 2. Make sure you're not doing anything important
8
+ # 3. Run this file
9
+ # 4. Watch either (or both) terminals
10
+ #
11
+ # This program creates a pty, and connects to it as the VT102.
12
+ # It then executes "tmux attach", and prints a ghost to the screen.
13
+ #
14
+ # The emulator will stay attached and refresh its screen every
15
+ # half second until you ^C it out of there.
16
+ #
17
+ # For a moment there, you're gonna have a terminal emulator (VT102) in a
18
+ # terminal emulator (tmux) in a terminal emulator (xterm, Konsole,
19
+ # Terminal.app, whatever). Let that sink in, dawg.
20
+
21
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
22
+
23
+ # Unix-y enough require list for you?
24
+ require 'term/vt102'
25
+ require 'pty'
26
+ require 'io/console'
27
+
28
+ # Prints the entire VT102 screen to an IO
29
+ def dump(vt, to: $stderr)
30
+ to.puts " ." + ('-' * (vt.cols)) + "."
31
+ (1 .. vt.rows).each do |row|
32
+ to.puts " |#{vt.row_plaintext(row)}|"
33
+ end
34
+ to.puts " '" + ('-' * (vt.cols)) + "'"
35
+ end
36
+
37
+ # Does the cool ghost thing.
38
+ def spooky(to:)
39
+ @ghost ||= DATA.read
40
+ @ghost.each_line do |line|
41
+ to.puts "\# #{line}"
42
+ sleep 0.2
43
+ end
44
+ end
45
+
46
+ vt = Term::VT102.new
47
+ rd, wr, pid = PTY.spawn("tmux attach")
48
+
49
+ loop do
50
+ s = rd.readpartial(1024)
51
+ if s && !s.empty?
52
+ vt.process(s)
53
+ dump(vt, to: $stderr)
54
+
55
+ unless defined?(@greeting)
56
+ @greeting = true
57
+ spooky(to: wr)
58
+ end
59
+ end
60
+
61
+ sleep 0.5
62
+ end
63
+
64
+ __END__
65
+ ___
66
+ _/ ..\
67
+ ( \ 0/__
68
+ \ \__)
69
+ / \
70
+ jgs / _\
71
+ `"""""``
72
+ BOO FROM TEH GHOST IN TEH MACHINE
@@ -0,0 +1,1389 @@
1
+ # Term::VT102 - module for VT102 emulation in Ruby
2
+ #
3
+ # Ported from Andrew Wood's Perl module, 'Term::VT102'.
4
+ #
5
+ # Copyright (C) Mike Owens
6
+ # Copyright (C) Andrew Wood
7
+ # NO WARRANTY - see LICENSE.txt
8
+ #
9
+
10
+ require 'term/vt102/version'
11
+
12
+ module Term
13
+ class VT102
14
+ # Return the packed version of a set of attributes fg, bg, bo, fa, st, ul,
15
+ # bl, rv.
16
+ #
17
+ def self.attr_pack(fg, bg, bo, fa, st, ul, bl, rv)
18
+ num = (fg & 7) |
19
+ ((bg & 7) << 4) |
20
+ (bo << 8) |
21
+ (fa << 9) |
22
+ (st << 10) |
23
+ (ul << 11) |
24
+ (bl << 12) |
25
+ (rv << 13)
26
+ [num].pack('S')
27
+ end
28
+
29
+ def attr_pack(*args)
30
+ self.class.attr_pack(*args)
31
+ end
32
+
33
+ # Return the unpacked version of a packed attribute.
34
+ #
35
+ def attr_unpack(data)
36
+ num = data.unpack('S').first
37
+
38
+ fg = num & 7
39
+ bg = (num >> 4) & 7
40
+ bo = (num >> 8) & 1
41
+ fa = (num >> 9) & 1
42
+ st = (num >> 10) & 1
43
+ ul = (num >> 11) & 1
44
+ bl = (num >> 12) & 1
45
+ rv = (num >> 13) & 1
46
+
47
+ [fg, bg, bo, fa, st, ul, bl, rv]
48
+ end
49
+
50
+ DEFAULT_ATTR = [7, 0, 0, 0, 0, 0, 0, 0].freeze
51
+ DEFAULT_ATTR_PACKED = attr_pack(*DEFAULT_ATTR).freeze
52
+
53
+ # Constructor function.
54
+ #
55
+ def initialize(cols: 80, rows: 24)
56
+ # control characters
57
+ @_ctlseq = {
58
+ "\000" => 'NUL', # ignored
59
+ "\005" => 'ENQ', # trigger answerback message
60
+ "\007" => 'BEL', # beep
61
+ "\010" => 'BS', # backspace one column
62
+ "\011" => 'HT', # horizontal tab to next tab stop
63
+ "\012" => 'LF', # line feed
64
+ "\013" => 'VT', # line feed
65
+ "\014" => 'FF', # line feed
66
+ "\015" => 'CR', # carriage return
67
+ "\016" => 'SO', # activate G1 character set & newline
68
+ "\017" => 'SI', # activate G0 character set
69
+ "\021" => 'XON', # resume transmission
70
+ "\023" => 'XOFF', # stop transmission, ignore characters
71
+ "\030" => 'CAN', # interrupt escape sequence
72
+ "\032" => 'SUB', # interrupt escape sequence
73
+ "\033" => 'ESC', # start escape sequence
74
+ "\177" => 'DEL', # ignored
75
+ "\233" => 'CSI' # equivalent to ESC [
76
+ }
77
+
78
+ # escape sequences
79
+ @_escseq = {
80
+ 'c' => 'RIS', # reset
81
+ 'D' => 'IND', # line feed
82
+ 'E' => 'NEL', # newline
83
+ 'H' => 'HTS', # set tab stop at current column
84
+ 'M' => 'RI', # reverse line feed
85
+ 'Z' => 'DECID', # DEC private ID; return ESC [ ? 6 c (VT102)
86
+ '7' => 'DECSC', # save state (position, charset, attributes)
87
+ '8' => 'DECRC', # restore most recently saved state
88
+ '[' => 'CSI', # control sequence introducer
89
+ '[[' => 'IGN', # ignored control sequence
90
+ '%@' => 'CSDFL', # select default charset (ISO646/8859-1)
91
+ '%G' => 'CSUTF8', # select UTF-8
92
+ '%8' => 'CSUTF8', # select UTF-8 (obsolete)
93
+ '#8' => 'DECALN', # DEC alignment test - fill screen with E's
94
+ '(8' => 'G0DFL', # G0 charset = default mapping (ISO8859-1)
95
+ '(0' => 'G0GFX', # G0 charset = VT100 graphics mapping
96
+ '(U' => 'G0ROM', # G0 charset = null mapping (straight to ROM)
97
+ '(K' => 'G0USR', # G0 charset = user defined mapping
98
+ '(B' => 'G0TXT', # G0 charset = ASCII mapping
99
+ ')8' => 'G1DFL', # G1 charset = default mapping (ISO8859-1)
100
+ ')0' => 'G1GFX', # G1 charset = VT100 graphics mapping
101
+ ')U' => 'G1ROM', # G1 charset = null mapping (straight to ROM)
102
+ ')K' => 'G1USR', # G1 charset = user defined mapping
103
+ ')B' => 'G1TXT', # G1 charset = ASCII mapping
104
+ '*8' => 'G2DFL', # G2 charset = default mapping (ISO8859-1)
105
+ '*0' => 'G2GFX', # G2 charset = VT100 graphics mapping
106
+ '*U' => 'G2ROM', # G2 charset = null mapping (straight to ROM)
107
+ '*K' => 'G2USR', # G2 charset = user defined mapping
108
+ '+8' => 'G3DFL', # G3 charset = default mapping (ISO8859-1)
109
+ '+0' => 'G3GFX', # G3 charset = VT100 graphics mapping
110
+ '+U' => 'G3ROM', # G3 charset = null mapping (straight to ROM)
111
+ '+K' => 'G3USR', # G3 charset = user defined mapping
112
+ '>' => 'DECPNM', # set numeric keypad mode
113
+ '=' => 'DECPAM', # set application keypad mode
114
+ 'N' => 'SS2', # select G2 charset for next char only
115
+ 'O' => 'SS3', # select G3 charset for next char only
116
+ 'P' => 'DCS', # device control string (ended by ST)
117
+ 'X' => 'SOS', # start of string
118
+ '^' => 'PM', # privacy message (ended by ST)
119
+ '_' => 'APC', # application program command (ended by ST)
120
+ "\\" => 'ST', # string terminator
121
+ 'n' => 'LS2', # invoke G2 charset
122
+ 'o' => 'LS3', # invoke G3 charset
123
+ '|' => 'LS3R', # invoke G3 charset as GR
124
+ '}' => 'LS2R', # invoke G2 charset as GR
125
+ '~' => 'LS1R', # invoke G1 charset as GR
126
+ ']' => 'OSC', # operating system command
127
+ 'g' => 'BEL', # alternate BEL
128
+ }
129
+
130
+ # ECMA-48 CSI sequences
131
+ @_csiseq = {
132
+ '[' => 'IGN', # ignored control sequence
133
+ '@' => 'ICH', # insert blank characters
134
+ 'A' => 'CUU', # move cursor up
135
+ 'B' => 'CUD', # move cursor down
136
+ 'C' => 'CUF', # move cursor right
137
+ 'D' => 'CUB', # move cursor left
138
+ 'E' => 'CNL', # move cursor down and to column 1
139
+ 'F' => 'CPL', # move cursor up and to column 1
140
+ 'G' => 'CHA', # move cursor to column in current row
141
+ 'H' => 'CUP', # move cursor to row, column
142
+ 'J' => 'ED', # erase display
143
+ 'K' => 'EL', # erase line
144
+ 'L' => 'IL', # insert blank lines
145
+ 'M' => 'DL', # delete lines
146
+ 'P' => 'DCH', # delete characters on current line
147
+ 'X' => 'ECH', # erase characters on current line
148
+ 'a' => 'HPR', # move cursor right
149
+ 'c' => 'DA', # return ESC [ ? 6 c (VT102)
150
+ 'd' => 'VPA', # move to row (current column)
151
+ 'e' => 'VPR', # move cursor down
152
+ 'f' => 'HVP', # move cursor to row, column
153
+ 'g' => 'TBC', # clear tab stop (CSI 3 g = clear all stops)
154
+ 'h' => 'SM', # set mode
155
+ 'l' => 'RM', # reset mode
156
+ 'm' => 'SGR', # set graphic rendition
157
+ 'n' => 'DSR', # device status report
158
+ 'q' => 'DECLL', # set keyboard LEDs
159
+ 'r' => 'DECSTBM', # set scrolling region to (top, bottom) rows
160
+ 's' => 'CUPSV', # save cursor position
161
+ 'u' => 'CUPRS', # restore cursor position
162
+ '`' => 'HPA' # move cursor to column in current row
163
+ }
164
+
165
+ # ANSI/DEC specified modes for SM/RM
166
+ @_modeseq = {
167
+ # ANSI Specified Modes
168
+ '0' => 'IGN', # Error (Ignored)
169
+ '1' => 'GATM', # guarded-area transfer mode (ignored)
170
+ '2' => 'KAM', # keyboard action mode (always reset)
171
+ '3' => 'CRM', # control representation mode (always reset)
172
+ '4' => 'IRM', # insertion/replacement mode (always reset)
173
+ '5' => 'SRTM', # status-reporting transfer mode
174
+ '6' => 'ERM', # erasure mode (always set)
175
+ '7' => 'VEM', # vertical editing mode (ignored)
176
+ '10' => 'HEM', # horizontal editing mode
177
+ '11' => 'PUM', # positioning unit mode
178
+ '12' => 'SRM', # send/receive mode (echo on/off)
179
+ '13' => 'FEAM', # format effector action mode
180
+ '14' => 'FETM', # format effector transfer mode
181
+ '15' => 'MATM', # multiple area transfer mode
182
+ '16' => 'TTM', # transfer termination mode
183
+ '17' => 'SATM', # selected area transfer mode
184
+ '18' => 'TSM', # tabulation stop mode
185
+ '19' => 'EBM', # editing boundary mode
186
+ '20' => 'LNM', # Line Feed / New Line Mode
187
+ # DEC Private Modes
188
+ '?0' => 'IGN', # Error (Ignored)
189
+ '?1' => 'DECCKM', # Cursorkeys application (set); Cursorkeys normal (reset)
190
+ '?2' => 'DECANM', # ANSI (set); VT52 (reset)
191
+ '?3' => 'DECCOLM', # 132 columns (set); 80 columns (reset)
192
+ '?4' => 'DECSCLM', # Jump scroll (set); Smooth scroll (reset)
193
+ '?5' => 'DECSCNM', # Reverse screen (set); Normal screen (reset)
194
+ '?6' => 'DECOM', # Sets relative coordinates (set); Sets absolute coordinates (reset)
195
+ '?7' => 'DECAWM', # Auto Wrap
196
+ '?8' => 'DECARM', # Auto Repeat
197
+ '?9' => 'DECINLM', # Interlace
198
+ '?18' => 'DECPFF', # Send FF to printer after print screen (set); No char after PS (reset)
199
+ '?19' => 'DECPEX', # Print screen: prints full screen (set); prints scroll region (reset)
200
+ '?25' => 'DECTCEM', # Cursor on (set); Cursor off (reset)
201
+ }
202
+
203
+ # supported character sequences
204
+ @_funcs = {
205
+ 'BS' => :_code_BS, # backspace one column
206
+ 'CR' => :_code_CR, # carriage return
207
+ 'DA' => :_code_DA, # return ESC [ ? 6 c (VT102)
208
+ 'DL' => :_code_DL, # delete lines
209
+ 'ED' => :_code_ED, # erase display
210
+ 'EL' => :_code_EL, # erase line
211
+ 'FF' => :_code_LF, # line feed
212
+ 'HT' => :_code_HT, # horizontal tab to next tab stop
213
+ 'IL' => :_code_IL, # insert blank lines
214
+ 'LF' => :_code_LF, # line feed
215
+ 'PM' => :_code_PM, # privacy message (ended by ST)
216
+ 'RI' => :_code_RI, # reverse line feed
217
+ 'RM' => :_code_RM, # reset mode
218
+ 'SI' => nil, # activate G0 character set
219
+ 'SM' => :_code_SM, # set mode
220
+ 'SO' => nil, # activate G1 character set & CR
221
+ 'ST' => nil, # string terminator
222
+ 'VT' => :_code_LF, # line feed
223
+ 'APC' => :_code_APC, # application program command (ended by ST)
224
+ 'BEL' => :_code_BEL, # beep
225
+ 'CAN' => :_code_CAN, # interrupt escape sequence
226
+ 'CHA' => :_code_CHA, # move cursor to column in current row
227
+ 'CNL' => :_code_CNL, # move cursor down and to column 1
228
+ 'CPL' => :_code_CPL, # move cursor up and to column 1
229
+ 'CRM' => nil, # control representation mode
230
+ 'CSI' => :_code_CSI, # equivalent to ESC [
231
+ 'CUB' => :_code_CUB, # move cursor left
232
+ 'CUD' => :_code_CUD, # move cursor down
233
+ 'CUF' => :_code_CUF, # move cursor right
234
+ 'CUP' => :_code_CUP, # move cursor to row, column
235
+ 'CUU' => :_code_CUU, # move cursor up
236
+ 'DCH' => :_code_DCH, # delete characters on current line
237
+ 'DCS' => :_code_DCS, # device control string (ended by ST)
238
+ 'DEL' => :_code_IGN, # ignored
239
+ 'DSR' => :_code_DSR, # device status report
240
+ 'EBM' => nil, # editing boundary mode
241
+ 'ECH' => :_code_ECH, # erase characters on current line
242
+ 'ENQ' => nil, # trigger answerback message
243
+ 'ERM' => nil, # erasure mode
244
+ 'ESC' => :_code_ESC, # start escape sequence
245
+ 'HEM' => nil, # horizontal editing mode
246
+ 'HPA' => :_code_CHA, # move cursor to column in current row
247
+ 'HPR' => :_code_CUF, # move cursor right
248
+ 'HTS' => :_code_HTS, # set tab stop at current column
249
+ 'HVP' => :_code_CUP, # move cursor to row, column
250
+ 'ICH' => :_code_ICH, # insert blank characters
251
+ 'IGN' => :_code_IGN, # ignored control sequence
252
+ 'IND' => :_code_LF, # line feed
253
+ 'IRM' => nil, # insert/replace mode
254
+ 'KAM' => nil, # keyboard action mode
255
+ 'LNM' => nil, # line feed / newline mode
256
+ 'LS2' => nil, # invoke G2 charset
257
+ 'LS3' => nil, # invoke G3 charset
258
+ 'NEL' => :_code_NEL, # newline
259
+ 'NUL' => :_code_IGN, # ignored
260
+ 'OSC' => :_code_OSC, # operating system command
261
+ 'PUM' => nil, # positioning unit mode
262
+ 'RIS' => :_code_RIS, # reset
263
+ 'SGR' => :_code_SGR, # set graphic rendition
264
+ 'SOS' => nil, # start of string
265
+ 'SRM' => nil, # send/receive mode (echo on/off)
266
+ 'SS2' => nil, # select G2 charset for next char only
267
+ 'SS3' => nil, # select G3 charset for next char only
268
+ 'SUB' => :_code_CAN, # interrupt escape sequence
269
+ 'TBC' => :_code_TBC, # clear tab stop (CSI 3 g = clear all stops)
270
+ 'TSM' => nil, # tabulation stop mode
271
+ 'TTM' => nil, # transfer termination mode
272
+ 'VEM' => nil, # vertical editing mode
273
+ 'VPA' => :_code_VPA, # move to row (current column)
274
+ 'VPR' => :_code_CUD, # move cursor down
275
+ 'XON' => :_code_XON, # resume transmission
276
+ 'FEAM' => nil, # format effector action mode
277
+ 'FETM' => nil, # format effector transfer mode
278
+ 'GATM' => nil, # guarded-area transfer mode
279
+ 'LS1R' => nil, # invoke G1 charset as GR
280
+ 'LS2R' => nil, # invoke G2 charset as GR
281
+ 'LS3R' => nil, # invoke G3 charset as GR
282
+ 'MATM' => nil, # multiple area transfer mode
283
+ 'SATM' => nil, # selected area transfer mode
284
+ 'SRTM' => nil, # status-reporting transfer mode
285
+ 'XOFF' => :_code_XOFF, # stop transmission, ignore characters
286
+ 'CSDFL' => nil, # select default charset (ISO646/8859-1)
287
+ 'CUPRS' => :_code_CUPRS, # restore cursor position
288
+ 'CUPSV' => :_code_CUPSV, # save cursor position
289
+ 'DECID' => :_code_DA, # DEC private ID; return ESC [ ? 6 c (VT102)
290
+ 'DECLL' => nil, # set keyboard LEDs
291
+ 'DECOM' => nil, # relative/absolute coordinate mode
292
+ 'DECRC' => :_code_DECRC, # restore most recently saved state
293
+ 'DECSC' => :_code_DECSC, # save state (position, charset, attributes)
294
+ 'G0DFL' => nil, # G0 charset = default mapping (ISO8859-1)
295
+ 'G0GFX' => nil, # G0 charset = VT100 graphics mapping
296
+ 'G0ROM' => nil, # G0 charset = null mapping (straight to ROM)
297
+ 'G0TXT' => nil, # G0 charset = ASCII mapping
298
+ 'G0USR' => nil, # G0 charset = user defined mapping
299
+ 'G1DFL' => nil, # G1 charset = default mapping (ISO8859-1)
300
+ 'G1GFX' => nil, # G1 charset = VT100 graphics mapping
301
+ 'G1ROM' => nil, # G1 charset = null mapping (straight to ROM)
302
+ 'G1TXT' => nil, # G1 charset = ASCII mapping
303
+ 'G1USR' => nil, # G1 charset = user defined mapping
304
+ 'G2DFL' => nil, # G2 charset = default mapping (ISO8859-1)
305
+ 'G2GFX' => nil, # G2 charset = VT100 graphics mapping
306
+ 'G2ROM' => nil, # G2 charset = null mapping (straight to ROM)
307
+ 'G2USR' => nil, # G2 charset = user defined mapping
308
+ 'G3DFL' => nil, # G3 charset = default mapping (ISO8859-1)
309
+ 'G3GFX' => nil, # G3 charset = VT100 graphics mapping
310
+ 'G3ROM' => nil, # G3 charset = null mapping (straight to ROM)
311
+ 'G3USR' => nil, # G3 charset = user defined mapping
312
+ 'CSUTF8' => nil, # select UTF-8 (obsolete)
313
+ 'DECALN' => :_code_DECALN, # DEC alignment test - fill screen with E's
314
+ 'DECANM' => nil, # ANSI/VT52 mode
315
+ 'DECARM' => nil, # auto repeat mode
316
+ 'DECAWM' => nil, # auto wrap mode
317
+ 'DECCKM' => nil, # cursor key mode
318
+ 'DECPAM' => nil, # set application keypad mode
319
+ 'DECPEX' => nil, # print screen / scrolling region
320
+ 'DECPFF' => nil, # sent FF after print screen, or not
321
+ 'DECPNM' => nil, # set numeric keypad mode
322
+ 'DECCOLM' => nil, # 132 column mode
323
+ 'DECINLM' => nil, # interlace mode
324
+ 'DECSCLM' => nil, # jump/smooth scroll mode
325
+ 'DECSCNM' => nil, # reverse/normal screen mode
326
+ 'DECSTBM' => :_code_DECSTBM, # set scrolling region
327
+ 'DECTCEM' => :_code_DECTCEM, # Cursor on (set); Cursor off (reset)
328
+ }
329
+
330
+ @_callbacks = {
331
+ 'BELL' => nil, # bell character received
332
+ 'CLEAR' => nil, # screen cleared
333
+ 'OUTPUT' => nil, # data to be sent back to originator
334
+ 'ROWCHANGE' => nil, # screen row changed
335
+ 'SCROLL_DOWN' => nil, # text about to move up (par=top row)
336
+ 'SCROLL_UP' => nil, # text about to move down (par=bott.)
337
+ 'UNKNOWN' => nil, # unknown character / sequence
338
+ 'STRING' => nil, # string received
339
+ 'XICONNAME' => nil, # xterm icon name changed
340
+ 'XWINTITLE' => nil, # xterm window title changed
341
+ 'LINEFEED' => nil, # line feed about to be processed
342
+ }
343
+
344
+ # stored arguments for callbacks
345
+ @_callbackarg = {}
346
+
347
+ # saved state for DECSC/DECRC
348
+ @_decsc = []
349
+
350
+ # saved state for CUPSV/CUPRS
351
+ @_cupsv = []
352
+
353
+ # state is XON (characters accepted)
354
+ @_xon = 1
355
+
356
+ # tab stops
357
+ @_tabstops = []
358
+
359
+ @cols, @rows = (cols > 0 ? cols : 80),
360
+ (rows > 0 ? rows : 24)
361
+
362
+ reset
363
+ end
364
+
365
+ # Call a callback function with the given parameters.
366
+ #
367
+ def callback_call(callback, arg1 = 0, arg2 = 0)
368
+ if (func = @_callbacks[callback])
369
+ priv = @_callbackarg[callback]
370
+ func.call(self, callback, arg1, arg2, priv)
371
+ end
372
+ end
373
+
374
+ # Set a callback function.
375
+ #
376
+ def callback_set(callback, ref, arg = nil)
377
+ @_callbacks[callback] = ref
378
+ @_callbackarg[callback] = arg
379
+ end
380
+
381
+ def on(callback_type, extra: nil, &proc)
382
+ callback_set(callback_type.to_s.upcase, proc, extra)
383
+ end
384
+
385
+ # Reset the terminal to "power-on" values.
386
+ #
387
+ def reset
388
+ @x = 1 # default X position: 1
389
+ @y = 1 # default Y position: 1
390
+
391
+ @attr = DEFAULT_ATTR_PACKED
392
+
393
+ @ti = '' # default: blank window title
394
+ @ic = '' # default: blank icon title
395
+
396
+ @srt = 1 # scrolling region top: row 1
397
+ @srb = @rows # scrolling region bottom
398
+
399
+ @opts = {} # blank all options
400
+ @opts['LINEWRAP'] = 0 # line wrapping off
401
+ @opts['LFTOCRLF'] = 0 # don't map LF -> CRLF
402
+ @opts['IGNOREXOFF'] = 1 # ignore XON/XOFF by default
403
+
404
+ @scrt = [] # blank screen text
405
+ @scra = [] # blank screen attributes
406
+
407
+ (1 .. @rows).each do |i|
408
+ @scrt[i] = "\000" * @cols # set text to NUL
409
+ @scra[i] = @attr * @cols # set attributes to default
410
+ end
411
+
412
+ @_tabstops = [] # reset tab stops
413
+ i = 1
414
+ while i < @cols
415
+ @_tabstops[i] = 1
416
+ i += 8
417
+ end
418
+
419
+
420
+ @_buf = nil # blank the esc-sequence buffer
421
+ @_inesc = '' # not in any escape sequence
422
+ @_xon = 1 # state is XON (chars accepted)
423
+
424
+ @cursor = 1 # turn cursor on
425
+ end
426
+
427
+ # Resize the terminal.
428
+ #
429
+ def resize(cols, rows)
430
+ callback_call 'CLEAR'
431
+ @cols, @rows = cols, rows
432
+ reset
433
+ end
434
+
435
+ # Return the current number of columns.
436
+ # Return the current number of rows.
437
+ attr_reader :cols, :rows
438
+
439
+ # Return the current terminal size.
440
+ #
441
+ def size
442
+ [cols, rows]
443
+ end
444
+
445
+ # Return the current cursor X/Y co-ordinates
446
+ #
447
+ attr_reader :x, :y
448
+
449
+ # Return the current cursor state (true = on, false = off).
450
+ #
451
+ def cursor?
452
+ @cursor == 1
453
+ end
454
+
455
+ # Return the current xterm title text.
456
+ #
457
+ def xtitle
458
+ @ti
459
+ end
460
+
461
+ # Return the current xterm icon text.
462
+ #
463
+ def xicon
464
+ @ic
465
+ end
466
+
467
+ # Return the current terminal status.
468
+ #
469
+ def status
470
+ [@x, @y, @attr, @ti, @ic]
471
+ end
472
+
473
+ # Process the given string, updating the terminal object and calling any
474
+ # necessary callbacks on the way.
475
+ #
476
+ def process(string)
477
+ while !string.empty?
478
+ if @_buf # in escape sequence
479
+ if string.sub!(/\A(.)/m, '')
480
+ ch = $1
481
+ if ch.match(/[\x00-\x1F]/mn)
482
+ _process_ctl(ch)
483
+ else
484
+ @_buf += ch
485
+ _process_escseq
486
+ end
487
+ end
488
+ else # not in escape sequence
489
+ if string.sub!(/\A([^\x00-\x1F\x7F\x9B]+)/mn, '')
490
+ _process_text($1)
491
+ elsif string.sub!(/\A(.)/m, '')
492
+ _process_ctl($1)
493
+ end
494
+ end
495
+ end
496
+ end
497
+
498
+ # Return the current value of the given option, or nil if it doesn't exist.
499
+ #
500
+ def option_read(option)
501
+ @opts[option]
502
+ end
503
+
504
+ # Set the value of the given option to the given value, returning the old
505
+ # value or undef if an invalid option was given.
506
+ #
507
+ def option_set(option, value)
508
+ return nil unless @opts.has_key?(option)
509
+
510
+ prev = @opts[option]
511
+ @opts[option] = value
512
+ prev
513
+ end
514
+
515
+ # Return the attributes of the given row, or undef if out of range.
516
+ #
517
+ def row_attr(row, startcol = nil, endcol = nil)
518
+ return nil unless (1 .. @rows).cover?(row)
519
+ data = @scra[row].dup
520
+
521
+ if startcol && endcol
522
+ data = data[(startcol - 1) * 2, ((endcol - startcol) + 1) * 2]
523
+ end
524
+
525
+ data
526
+ end
527
+
528
+ # Return the textual contents of the given row, or undef if out of range.
529
+ #
530
+ def row_text(row, startcol = nil, endcol = nil)
531
+ return nil if (row < 1) || (row > @rows)
532
+
533
+ text = @scrt[row].dup
534
+
535
+ if startcol && endcol
536
+ text = text[startcol - 1, (endcol - startcol) + 1]
537
+ end
538
+ text
539
+ end
540
+
541
+ # Return the textual contents of the given row, or undef if out of range,
542
+ # with unused characters represented as a space instead of \0.
543
+ #
544
+ def row_plaintext(row, startcol = nil, endcol = nil)
545
+ return nil if (row < 1) || (row > @rows)
546
+
547
+ text = @scrt[row].dup
548
+ text.gsub!(/\0/, ' ')
549
+
550
+ if startcol && endcol
551
+ text = text[startcol - 1, (endcol - startcol) + 1]
552
+ end
553
+
554
+ text
555
+ end
556
+
557
+ # Return a set of SGR escape sequences that will change colours and
558
+ # attributes from "source" to "dest" (packed attributes).
559
+ #
560
+ def sgr_change(source = DEFAULT_ATTR_PACKED, dest = DEFAULT_ATTR_PACKED)
561
+ out, off, on = '', {}, {}
562
+
563
+ return '' if source == dest
564
+ return "\e[m" if dest == DEFAULT_ATTR_PACKED
565
+
566
+ sfg, sbg, sbo, sfa, sst, sul, sbl, srv = attr_unpack(source)
567
+ dfg, dbg, dbo, dfa, _dst, dul, dbl, drv = attr_unpack(dest)
568
+
569
+ if sfg != dfg || sbg != dbg
570
+ out += sprintf("\e[m\e[3%d;4%dm", dfg, dbg)
571
+ sbo = sfa = sst = sul = sbl = srv = 0
572
+ end
573
+
574
+ if sbo > dbo || sfa > dfa
575
+ off['22'] = 1
576
+ sbo = sfa = 0
577
+ end
578
+
579
+ off['24'] = 1 if sul > dul
580
+ off['25'] = 1 if sbl > dbl
581
+ off['27'] = 1 if srv > drv
582
+
583
+ if off.size > 2
584
+ out += "\e[m"
585
+ sbo = sfa = sst = sul = sbl = srv = 0
586
+ elsif off.size > 0
587
+ out += "\e[" + off.keys.join(';') + "m"
588
+ end
589
+
590
+ on['1'] = 1 if dbo > sbo
591
+ on['2'] = 1 if (dfa > sfa) && !(dbo > sbo)
592
+ on['4'] = 1 if dul > sul
593
+ on['5'] = 1 if dbl > sbl
594
+ on['7'] = 1 if drv > srv
595
+
596
+ unless on.empty?
597
+ out += "\e[" + on.keys.join(';') + "m"
598
+ end
599
+
600
+ out
601
+ end
602
+
603
+ # Return the textual contents of the given row, or undef if out of range,
604
+ # with unused characters represented as a space instead of \0, and any
605
+ # colour or attribute changes expressed by the relevant SGR escape
606
+ # sequences.
607
+ #
608
+ def row_sgrtext(row, startcol = 1, endcol = nil)
609
+ return nil if row < 1 || row > @rows
610
+
611
+ endcol ||= @cols
612
+
613
+ return nil if endcol < startcol
614
+ return nil if startcol < 1 || endcol > @cols
615
+
616
+ row_text = @scrt[row]
617
+ row_attr = @scra[row]
618
+
619
+ text = ''
620
+ attr_cur = DEFAULT_ATTR_PACKED
621
+
622
+ while startcol <= endcol
623
+ char = row_text[startcol - 1, 1]
624
+ char.gsub!(/\0/, '')
625
+ char = ' ' unless char.match(/./)
626
+ attr_next = row_attr[(startcol - 1) * 2, 2]
627
+ text += sgr_change(attr_cur, attr_next) + char
628
+ attr_cur = attr_next
629
+
630
+ startcol += 1
631
+ end
632
+
633
+ attr_next = DEFAULT_ATTR_PACKED
634
+ text += sgr_change(attr_cur, attr_next)
635
+
636
+ text
637
+ end
638
+
639
+ # Process a string of plain text, with no special characters in it.
640
+ #
641
+ def _process_text(text)
642
+ return if @_xon == 0
643
+
644
+ width = (@cols + 1) - @x
645
+
646
+ if @opts['LINEWRAP'] == 0
647
+ return if width < 1
648
+ text = text[0, width]
649
+ @scrt[@y][@x - 1, text.size] = text
650
+ @scra[@y][2 * (@x - 1), 2 * text.size] = @attr * text.size
651
+ @x += text.size
652
+ @x = @cols if @x > @cols
653
+
654
+ callback_call('ROWCHANGE', @y)
655
+ return
656
+ end
657
+
658
+ while !text.empty? # line wrapping enabled
659
+ if width > 0
660
+ segment = text[0, width]
661
+ text[0, width] = ''
662
+ @scrt[@y][@x - 1, segment.size] = segment
663
+ @scra[@y][2 * (@x - 1), 2 * segment.size] = @attr * segment.size
664
+ @x += segment.size
665
+ else
666
+ if @x > @cols # wrap to next line
667
+ callback_call('ROWCHANGE', @y, 0)
668
+ callback_call('LINEFEED', @y, 0)
669
+ @x = 1
670
+ _move_down
671
+ end
672
+ end
673
+ width = (@cols + 1) - @x
674
+ end
675
+ callback_call('ROWCHANGE', @y, 0)
676
+ end
677
+
678
+ # Process a control character.
679
+ #
680
+ def _process_ctl(ctl)
681
+ name = @_ctlseq[ctl]
682
+ return if name.nil? # ignore unknown characters
683
+
684
+ #If we're in XOFF mode, ifgnore anything other than XON
685
+ if @_xon == 0
686
+ return if name != 'XON'
687
+ end
688
+
689
+
690
+ symbol = @_funcs[name]
691
+ if symbol.nil?
692
+ callback_call('UNKNOWN', name, ctl)
693
+ else
694
+ m = method(symbol)
695
+ send(symbol, *([name].first(m.arity)))
696
+ end
697
+ end
698
+
699
+ # Check the escape-sequence buffer, and process it if necessary.
700
+ #
701
+ def _process_escseq
702
+ params = []
703
+
704
+ return if @_buf.nil? || @_buf.empty?
705
+ return if @_xon == 0
706
+
707
+ if @_inesc == 'OSC'
708
+ if @_buf.match(/\A0;([^\007]*)(?:\007|\033\\)/m)
709
+ dat = $1 # icon & window
710
+ callback_call('XWINTITLE', dat)
711
+ callback_call('XICONNAME', dat)
712
+ @ic = dat
713
+ @ti = dat
714
+ @_buf = nil
715
+ @_inesc = ''
716
+ elsif @_buf.match(/\A1;([^\007]*)(?:\007|\033\\)/m)
717
+ dat = $1 # set icon name
718
+ callback_call('XICONNAME', dat)
719
+ @ic = dat
720
+ @_buf = nil
721
+ @_inesc = ''
722
+ elsif @_buf.match(/\A2;([^\007]*)(?:\007|\033\\)/m)
723
+ dat = $1 # set window title
724
+ callback_call('XWINTITLE', dat)
725
+ @ti = dat
726
+ @_buf = nil
727
+ @_inesc = ''
728
+ elsif @_buf.match(/\A\d+;([^\007]*)(?:\007|\033\\)/m)
729
+ # unknown OSC
730
+ callback_call('UNKNOWN', 'OSC', "\033]" + @_buf)
731
+ @_buf = nil
732
+ @_inesc = ''
733
+ elsif @_buf.size > 1024 # OSC too long
734
+ callback_call('UNKNOWN', 'OSC', "\033]" + @_buf)
735
+ @_buf = nil
736
+ @_inesc = ''
737
+ end
738
+ elsif @_inesc == 'CSI' # in CSI sequence
739
+ @_csiseq.keys.each do |suffix|
740
+ next if @_buf.size < suffix.size
741
+ next if @_buf[@_buf.size - suffix.size, suffix.size] != suffix
742
+
743
+ @_buf = @_buf[0, @_buf.size - suffix.size]
744
+
745
+ name = @_csiseq[suffix]
746
+ func = @_funcs[name]
747
+
748
+ if func.nil? # unsupported sequence
749
+ callback_call('UNKNOWN', name, "\033[" + @_buf + suffix)
750
+ @_buf = nil
751
+ @_inesc = ''
752
+ return
753
+ end
754
+
755
+ params = @_buf.split(';').map(&:to_i)
756
+ @_buf = nil
757
+ @_inesc = ''
758
+
759
+ send(func, *params)
760
+ return
761
+ end
762
+
763
+ if @_buf.size > 64 # abort CSI sequence if too long
764
+ callback_call('UNKNOWN', 'CSI', "\033[" + @_buf)
765
+ @_buf = nil
766
+ @_inesc = ''
767
+ end
768
+ elsif @_inesc =~ /_ST\z/m
769
+ if @_buf.sub!(/\033\\\z/m, '')
770
+ @_inesc.sub!(/_ST\z/m, '')
771
+ callback_call('STRING', @_inesc, @_buf)
772
+ @_buf = nil
773
+ @_inesc = ''
774
+ elsif @_buf.size > 1024 # string too long
775
+ @_inesc.sub!(/_ST\z/m, '')
776
+ callback_call('STRING', @_inesc, @_buf)
777
+ @_buf = nil
778
+ @_inesc = ''
779
+ end
780
+ else # in ESC sequence
781
+ @_escseq.keys.each do |prefix|
782
+ next if @_buf[0, prefix.size] != prefix
783
+
784
+ name = @_escseq[prefix]
785
+ func = @_funcs[name]
786
+ if func.nil? # unsupported sequence
787
+ callback_call('UNKNOWN', name, "\033" + @_buf)
788
+ @_buf = nil
789
+ @_inesc = ''
790
+ return
791
+ end
792
+ @_buf = nil
793
+ @_inesc = ''
794
+ send(func)
795
+ return
796
+ end
797
+
798
+ if @_buf.size > 8 # abort ESC sequence if too long
799
+ callback_call('UNKNOWN', 'ESC', "\033" + @_buf)
800
+ @_buf = nil
801
+ @_inesc = ''
802
+ end
803
+ end
804
+ end
805
+
806
+ # Scroll the scrolling region up such that the text in the scrolling region
807
+ # moves down, by the given number of lines.
808
+ #
809
+ def _scroll_up(lines)
810
+ return if lines < 1
811
+ callback_call('SCROLL_UP', @srb, lines)
812
+
813
+ i = @srb
814
+ while i >= @srt + lines
815
+ @scrt[i] = @scrt[i - lines]
816
+ @scra[i] = @scra[i - lines]
817
+ i -= 1
818
+ end
819
+
820
+ attr = DEFAULT_ATTR_PACKED
821
+
822
+ i = @srt
823
+ while (i <= @srb) && (i < (@srt + lines))
824
+ @scrt[i] = "\000" * @cols # blank new lines
825
+ @scra[i] = attr * @cols # wipe attributes of new lines
826
+ i += 1
827
+ end
828
+ end
829
+
830
+ # Scroll the scrolling region down such that the text in the scrolling region
831
+ # moves up, by the given number of lines.
832
+ #
833
+ def _scroll_down(lines)
834
+ callback_call('SCROLL_DOWN', @srt, lines)
835
+
836
+ i = @srt
837
+ while i <= (@srb - lines)
838
+ @scrt[i] = @scrt[i + lines]
839
+ @scra[i] = @scra[i + lines]
840
+ i += 1
841
+ end
842
+
843
+ attr = DEFAULT_ATTR_PACKED
844
+
845
+ i = @srb
846
+ while (i >= @srt) && (i > (@srb - lines))
847
+ @scrt[i] = "\000" * @cols # blank new lines
848
+ @scra[i] = attr * @cols # wipe attributes of new lines
849
+ i -= 1
850
+ end
851
+ end
852
+
853
+ # Move the cursor up the given number of lines, without triggering a GOTO
854
+ # callback, taking scrolling into account.
855
+ #
856
+ def _move_up(num = 1)
857
+ num = [num, 1].max
858
+
859
+ @y -= num
860
+ return if @y >= @srt
861
+
862
+ _scroll_up(@srt - @y) # scroll
863
+ @y = @srt
864
+ end
865
+
866
+ # Move the cursor down the given number of lines, without triggering a GOTO
867
+ # callback, taking scrolling into account.
868
+ #
869
+ def _move_down(num = 1)
870
+ num = [num, 1].max
871
+
872
+ @y += num
873
+ return if @y <= @srb
874
+
875
+ _scroll_down(@y - @srb) # scroll
876
+ @y = @srb
877
+ end
878
+
879
+ def _code_BEL # beep
880
+ if @_buf && @_inesc == 'OSC'
881
+ # CSI OSC can be terminated with a BEL
882
+ @_buf += "\007"
883
+ _process_escseq()
884
+ else
885
+ callback_call('BELL')
886
+ end
887
+ end
888
+
889
+ def _code_BS # move left 1 character
890
+ @x -= 1
891
+ @x = 1 if @x < 1
892
+ end
893
+
894
+ def _code_CAN # cancel escape sequence
895
+ @_buf = nil
896
+ @_inesc = ''
897
+ end
898
+
899
+ def _code_TBC(num = nil) # clear tab stop (CSI 3 g = clear all stops)
900
+ if num == 3
901
+ @_tabstops = []
902
+ else
903
+ @_tabstops[@x] = nil
904
+ end
905
+ end
906
+
907
+ def _code_CHA(col = 1) # move to column in current row
908
+ return if @x == col
909
+
910
+ callback_call('GOTO', col, @y)
911
+
912
+ @x = col
913
+ @x = 1 if @x < 1
914
+ @x = @cols if (@x > @cols)
915
+ end
916
+
917
+ def _code_CNL(num = 1) # move cursor down and to column 1
918
+ callback_call('GOTO', 1, @y + num)
919
+ @x = 1
920
+ _move_down(num)
921
+ end
922
+
923
+ def _code_CPL(num = 1) # move cursor up and to column 1
924
+ callback_call('GOTO', @x, @y - num)
925
+ @x = 1
926
+ _move_up(num)
927
+ end
928
+
929
+ def _code_CR # carriage return
930
+ @x = 1
931
+ end
932
+
933
+ def _code_CSI # ESC [
934
+ @_buf = '' # restart ESC buffering
935
+ @_inesc = 'CSI' # ...for a CSI, not an ESC
936
+ end
937
+
938
+ def _code_CUB(num = 1) # move cursor left
939
+ num = [num, 1].max
940
+
941
+ callback_call('GOTO', @x - num, @y)
942
+
943
+ @x -= num
944
+ @x = [@x, 1].max
945
+ end
946
+
947
+ def _code_CUD(num = 1) # move cursor down
948
+ num = [num, 1].max
949
+ callback_call('GOTO', @x, @y + num)
950
+ _move_down(num)
951
+ end
952
+
953
+ def _code_CUF(num = 1) # move cursor right
954
+ num = [num, 1].max
955
+ callback_call('GOTO', @x + num, @y)
956
+ @x += num
957
+ @x = @cols if (@x > @cols)
958
+ end
959
+
960
+ def _code_CUP(row = 1, col = 1) # move cursor to row, column
961
+ row = [row, 1].max
962
+ col = [col, 1].max
963
+
964
+ row = @rows if row > @rows
965
+ col = @cols if col > @cols
966
+
967
+ callback_call('GOTO', col, row)
968
+
969
+ @x, @y = col, row
970
+ end
971
+
972
+ def _code_RI # reverse line feed
973
+ callback_call('GOTO', @x, @y - 1)
974
+ _move_up
975
+ end
976
+
977
+ def _code_CUU(num = 1) # move cursor up
978
+ num = [num, 1].max
979
+ callback_call('GOTO', @x, @y - num)
980
+ _move_up(num)
981
+ end
982
+
983
+ def _code_DA # return ESC [ ? 6 c (VT102)
984
+ callback_call('OUTPUT', "\033[?6c", 0)
985
+ end
986
+
987
+ def _code_DCH(num = 1) # delete characters on current line
988
+ num = [num, 1].max
989
+
990
+ width = @cols + 1 - @x
991
+ todel = num
992
+ todel = width if todel > width
993
+
994
+ line = @scrt[@y]
995
+ lsub, rsub = '', ''
996
+ lsub = line[0, @x - 1] if @x > 1
997
+ rsub = line[(@x - 1 + todel) .. -1]
998
+ @scrt[@y] = lsub + rsub + ("\0" * todel)
999
+
1000
+ line = @scra[@y]
1001
+ lsub, rsub = '', ''
1002
+ lsub = line[0, 2 * (@x - 1)] if @x > 1
1003
+ rsub = line[(2 * (@x - 1 + todel)) .. -1]
1004
+ @scra[@y] = lsub + rsub + (DEFAULT_ATTR_PACKED * todel)
1005
+
1006
+ callback_call('ROWCHANGE', @y, 0)
1007
+ end
1008
+
1009
+ def _code_DCS # device control string (ignored)
1010
+ @_buf = ''
1011
+ @_inesc = 'DCS_ST'
1012
+ end
1013
+
1014
+ def _code_DECSTBM(top = 1, bottom = nil) # set scrolling region
1015
+ bottom ||= @rows
1016
+ top = [top, 1].max
1017
+ bottom = [bottom, 1].max
1018
+
1019
+ top = @rows if top > @rows
1020
+ bottom = @rows if bottom > @rows
1021
+
1022
+ (top, bottom = bottom, top) if bottom < top
1023
+
1024
+ @srb, @srt = bottom, top
1025
+ end
1026
+
1027
+ def _code_DECTCEM(cursor) # Cursor on (set); Cursor off (reset)
1028
+ @cursor = cursor
1029
+ end
1030
+
1031
+ def _code_IGN # ignored control sequence
1032
+ end
1033
+
1034
+ def _code_DL(lines = 1) # delete lines
1035
+ lines = [lines, 1].max
1036
+
1037
+ attr = DEFAULT_ATTR_PACKED
1038
+
1039
+ scrb = @srb
1040
+ scrb = @rows if @y > @srb
1041
+ scrb = @srt - 1 if @y < @srt
1042
+
1043
+ row = @y
1044
+ while row <= scrb - lines
1045
+ @scrt[row] = @scrt[row + lines]
1046
+ @scra[row] = @scra[row + lines]
1047
+ callback_call('ROWCHANGE', row, 0)
1048
+ row += 1
1049
+ end
1050
+
1051
+ row = scrb
1052
+ while row > (scrb - lines) && row >= @y
1053
+ @scrt[row] = "\000" * @cols
1054
+ @scra[row] = attr * @cols
1055
+ callback_call('ROWCHANGE', row, 0)
1056
+ row -= 1
1057
+ end
1058
+ end
1059
+
1060
+ def _code_DSR(num = 5) # device status report
1061
+ if num == 6 # CPR - cursor position report
1062
+ callback_call('OUTPUT', "\e[#{@y};#{@x}R", 0)
1063
+ elsif num == 5 # DSR - reply ESC [ 0 n
1064
+ callback_call('OUTPUT', "\e[0n", 0)
1065
+ end
1066
+ end
1067
+
1068
+ def _code_ECH(num = 1) # erase characters on current line
1069
+ num = [num, 1].max
1070
+
1071
+ width = @cols + 1 - @x
1072
+ todel = num
1073
+ todel = width if todel > width
1074
+
1075
+ line = @scrt[@y]
1076
+ lsub, rsub = '', ''
1077
+ lsub = line[0, @x - 1] if @x > 1
1078
+ rsub = line[(@x - 1 + todel) .. -1]
1079
+ @scrt[@y] = lsub + ("\0" * todel) + rsub
1080
+
1081
+
1082
+ line = @scra[@y]
1083
+ lsub, rsub = '', ''
1084
+ lsub = line[0, 2 * (@x - 1)] if @x > 1
1085
+ rsub = line[(2 * (@x - 1 + todel)) .. -1]
1086
+
1087
+ @scra[@y] = lsub + (DEFAULT_ATTR_PACKED * todel) + rsub
1088
+ callback_call('ROWCHANGE', @y, 0)
1089
+ end
1090
+
1091
+ def _code_ED(num = 0) # erase display
1092
+ attr = DEFAULT_ATTR_PACKED
1093
+
1094
+ # Wipe-cursor-to-end is the same as clear-whole-screen if cursor at top left
1095
+ #
1096
+ num = 2 if (num == 0) && (@x == 1) && (@y == 1)
1097
+
1098
+ if num == 0 # 0 = cursor to end
1099
+ @scrt[@y] = @scrt[@y][0, @x - 1] + ("\0" * (@cols + 1 - @x))
1100
+ @scra[@y] = @scra[@y][0, 2 * (@x - 1)] + (attr * (cols + 1 - @x))
1101
+ callback_call('ROWCHANGE', @y, 0)
1102
+
1103
+ row = @y + 1
1104
+ while row <= @rows
1105
+ @scrt[row] = "\0" * @cols
1106
+ @scra[row] = attr * @cols
1107
+ callback_call('ROWCHANGE', row, 0)
1108
+ row += 1
1109
+ end
1110
+ elsif num == 1 # 1 = start to cursor
1111
+ row = 1
1112
+ while row < @y
1113
+ @scrt[row] = "\0" * @cols
1114
+ @scra[row] = attr * @cols
1115
+ callback_call('ROWCHANGE', row, 0)
1116
+ row += 1
1117
+ end
1118
+
1119
+ @scrt[@y] = ("\0" * @x) + @scrt[@y][@x .. -1]
1120
+ @scra[@y] = (attr * @x) + @scra[@y][2 * @x .. -1]
1121
+ callback_call('ROWCHANGE', @y, 0)
1122
+ else # 2 = whole display
1123
+ callback_call('CLEAR', 0, 0)
1124
+ row = 1
1125
+ while row <= rows
1126
+ @scrt[row] = "\0" * @cols
1127
+ @scra[row] = attr * @cols
1128
+ row += 1
1129
+ end
1130
+ end
1131
+ end
1132
+
1133
+ def _code_EL(num = 0) # erase line
1134
+ attr = DEFAULT_ATTR_PACKED
1135
+
1136
+ if num == 0 # 0 = cursor to end of line
1137
+ @scrt[@y] = @scrt[@y][0, @x - 1] + ("\0" * (@cols + 1 - @x))
1138
+ @scra[@y] = @scra[@y][0, 2 * (@x - 1)] + (attr * (@cols + 1 - @x))
1139
+ callback_call('ROWCHANGE', @y, 0)
1140
+ elsif num == 1 # 1 = start of line to cursor
1141
+ @scrt[@y] = ("\0" * @x) + @scrt[@y][@x .. -1]
1142
+ @scra[@y] = (attr * @x) + @scra[@y][(2 * @x) .. -1]
1143
+ callback_call('ROWCHANGE', @y, 0)
1144
+ else # 2 = whole line
1145
+ @scrt[@y] = "\0" * @cols
1146
+ @scra[@y] = attr * @cols
1147
+ callback_call('ROWCHANGE', @y, 0)
1148
+ end
1149
+ end
1150
+
1151
+ def _code_ESC # start escape sequence
1152
+ if @_buf && @_inesc.match(/OSC|_ST/)
1153
+ # Some sequences are terminated with an ST
1154
+ @_buf += "\033"
1155
+ _process_escseq
1156
+ return
1157
+ end
1158
+
1159
+ @_buf = '' # set ESC buffer
1160
+ @_inesc = 'ESC' # ...for ESC, not CSI
1161
+ end
1162
+
1163
+ def _code_LF # line feed
1164
+ _code_CR if @opts['LFTOCRLF'] != 0
1165
+
1166
+ callback_call('LINEFEED', @y, 0)
1167
+ _move_down
1168
+ end
1169
+
1170
+ def _code_NEL # newline
1171
+ _code_CR # cursor always to start
1172
+ _code_LF # standard line feed
1173
+ end
1174
+
1175
+ def _code_HT # horizontal tab to next tab stop
1176
+ if @opts['LINEWRAP'] != 0 && @x >= @cols
1177
+ callback_call('LINEFEED', @y, 0)
1178
+ @x = 1
1179
+ _move_down
1180
+ end
1181
+
1182
+ newx = @x + 1
1183
+ while newx < @cols && @_tabstops[newx] != 1
1184
+ newx += 1
1185
+ end
1186
+
1187
+ width = (@cols + 1) - @x
1188
+ spaces = newx - @x
1189
+ spaces = width + 1 if spaces > width
1190
+
1191
+ if spaces > 0
1192
+ @x += spaces
1193
+ @x = @cols if @x > @cols
1194
+ end
1195
+ end
1196
+
1197
+ def _code_HTS # set tab stop at current column
1198
+ @_tabstops[@x] = 1
1199
+ end
1200
+
1201
+ def _code_ICH(num = 1) # insert blank characters
1202
+ num = [num, 1].max
1203
+
1204
+ width = @cols + 1 - @x
1205
+ toins = num
1206
+ toins = width if toins > width
1207
+
1208
+ line = @scrt[@y]
1209
+ lsub, rsub = '', ''
1210
+ lsub = line[0, @x - 1] if @x > 1
1211
+ rsub = line[@x - 1, width - toins]
1212
+ @scrt[@y] = lsub + ("\0" * toins) + rsub
1213
+
1214
+ attr = DEFAULT_ATTR_PACKED
1215
+ line = @scra[@y]
1216
+ lsub, rsub = '', ''
1217
+ lsub = line[0, 2 * (@x - 1)] if @x > 1
1218
+ rsub = line[2 * (@x - 1), 2 * (width - toins)]
1219
+ @scra[@y] = lsub + (attr * toins) + rsub
1220
+
1221
+ callback_call('ROWCHANGE', @y, 0)
1222
+ end
1223
+
1224
+ def _code_IL(lines = 1) # insert blank lines
1225
+ lines = [lines, 1].max
1226
+
1227
+ attr = DEFAULT_ATTR_PACKED
1228
+
1229
+ scrb = @srb
1230
+ scrb = @rows if @y > @srb
1231
+ scrb = @srt - 1 if @y < @srt
1232
+
1233
+ row = scrb
1234
+ while row >= y + lines
1235
+ @scrt[row] = @scrt[row - lines]
1236
+ @scra[row] = @scra[row - lines]
1237
+ callback_call('ROWCHANGE', row, 0)
1238
+
1239
+ row -= 1
1240
+ end
1241
+
1242
+ row = @y
1243
+ while (row <= scrb) && (row < (@y + lines))
1244
+ @scrt[row] = "\000" * @cols
1245
+ @scra[row] = attr * @cols
1246
+ callback_call('ROWCHANGE', row, 0)
1247
+ row += 1
1248
+ end
1249
+ end
1250
+
1251
+ def _code_PM # privacy message (ignored)
1252
+ @_buf = ''
1253
+ @_inesc = 'PM_ST'
1254
+ end
1255
+
1256
+ def _code_APC # application program command (ignored)
1257
+ @_buf = ''
1258
+ @_inesc = 'APC_ST'
1259
+ end
1260
+
1261
+ def _code_OSC # operating system command
1262
+ @_buf = '' # restart buffering
1263
+ @_inesc = 'OSC' # ...for OSC, not ESC or CSI
1264
+ end
1265
+
1266
+ def _code_RIS # reset
1267
+ reset
1268
+ end
1269
+
1270
+ def _toggle_mode(flag, modes) # set/reset modes
1271
+ # Transcription Note: This isn't really a loop
1272
+ fail ArgumentError, "only first mode applied" if modes.size > 1
1273
+
1274
+ modes.each do |mode|
1275
+ name = @_modeseq[mode]
1276
+ func = nil
1277
+ func = @_funcs[name] unless name.nil?
1278
+
1279
+ if func.nil?
1280
+ callback_call('UNKNOWN', name, "\033[#{mode}" + (flag ? "h" : "l"))
1281
+ @_buf = nil
1282
+ @_inesc = ''
1283
+ return
1284
+ end
1285
+
1286
+ @_buf = nil
1287
+ @_inesc = ''
1288
+ send(func, flag)
1289
+ return
1290
+ end
1291
+ end
1292
+
1293
+ def _code_RM(*args) # reset mode
1294
+ _toggle_mode(0, args)
1295
+ end
1296
+
1297
+ def _code_SM(*args) # set mode
1298
+ _toggle_mode(1, args)
1299
+ end
1300
+
1301
+ def _code_SGR(*parms) # set graphic rendition
1302
+ fg, bg, bo, fa, st, ul, bl, rv = attr_unpack(@attr)
1303
+
1304
+ parms = [0] if parms.empty? # ESC [ m = ESC [ 0 m
1305
+ parms.each do |val|
1306
+ case val
1307
+ when 0 # reset all attributes
1308
+ fg, bg, bo, fa, st, ul, bl, rv = DEFAULT_ATTR
1309
+ when 1 # bold ON
1310
+ bo, fa = 1, 0
1311
+ when 2 # faint ON
1312
+ bo, fa = 0, 1
1313
+ when 4 # underline ON
1314
+ ul = 1
1315
+ when 5 # blink ON
1316
+ bl = 1
1317
+ when 7 # reverse video ON
1318
+ rv = 1
1319
+ when 21..22 # normal intensity
1320
+ bo, fa = 0, 0
1321
+ when 24 # underline OFF
1322
+ ul = 0
1323
+ when 25 # blink OFF
1324
+ bl = 0
1325
+ when 27 # reverse video OFF
1326
+ rv = 0
1327
+ when 30..37 # set foreground colour
1328
+ fg = val - 30
1329
+ when 38 # underline on, default fg
1330
+ ul, fg = 1, 7
1331
+ when 39 # underline off, default fg
1332
+ ul, fg = 0, 7
1333
+ when 40..47 # set background colour
1334
+ bg = val - 40
1335
+ when 49 # default background
1336
+ bg = 0
1337
+ end
1338
+ end
1339
+
1340
+ @attr = attr_pack(fg, bg, bo, fa, st, ul, bl, rv)
1341
+ end
1342
+
1343
+ def _code_VPA(row = 1) # move to row (current column)
1344
+ return if @y == row
1345
+
1346
+ @y = [row, 1].max
1347
+ @y = @rows if @y > @rows
1348
+ end
1349
+
1350
+ def _code_DECALN # fill screen with E's
1351
+ attr = DEFAULT_ATTR_PACKED
1352
+
1353
+ (1 .. @rows).each do |row|
1354
+ @scrt[row] = 'E' * @cols
1355
+ @scra[row] = attr * @cols
1356
+ callback_call('ROWCHANGE', @y, 0)
1357
+ end
1358
+
1359
+ @x = @y = 1
1360
+ end
1361
+
1362
+ def _code_DECSC # save state
1363
+ @_decsc.push([@x, @y, @attr, @ti, @ic, @cursor])
1364
+ end
1365
+
1366
+ def _code_DECRC # restore most recently saved state
1367
+ return if @_decsc.empty?
1368
+ @x, @y, @attr, @ti, @ic, @cursor = @_decsc.pop
1369
+ end
1370
+
1371
+ def _code_CUPSV # save cursor position
1372
+ @_cupsv.push([@x, @y])
1373
+ end
1374
+
1375
+ def _code_CUPRS # restore cursor position
1376
+ return if @_cupsv.empty?
1377
+ @x, @y = @_cupsv.pop
1378
+ end
1379
+
1380
+ def _code_XON # resume character processing
1381
+ @_xon = 1
1382
+ end
1383
+
1384
+ def _code_XOFF # stop character processing
1385
+ return if @opts['IGNOREXOFF'] == 1
1386
+ @_xon = 0
1387
+ end
1388
+ end
1389
+ end