term-vt102 0.9.1

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.
@@ -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