terminal_rb 0.9.1 → 0.9.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8fdd6ab56d007f6fa6e82bf8d557d0a27815ad789998da5583310dd62af2b551
4
- data.tar.gz: ef83edfd6c82bc9a2a056d148cd4da2af7b1a28acaa0cab9863901d608d502b7
3
+ metadata.gz: c9409becbf807905a0659436c9e0f5c3644208006e23c038dbf43a621410c7c7
4
+ data.tar.gz: 7a583438aa3b66faa19a9bbd035ad26d2c74f889b923e2f410d6468291006495
5
5
  SHA512:
6
- metadata.gz: d53c04af3d74273cf3111b9b7078846e0c33f0caf8501e9f0182427ed784d96b94444c1f6238427dd248cf300fb362d95a3a0f5dea2ead5c571ff2fd7d52fdf9
7
- data.tar.gz: 82d79cfab6b7d499bb5d6f9fb7d6ce9af14f69ed792f8f7eedf4485a4f26546576ecadd723cc67a48ffbb4bc00ab1cc83d04a15a1bceb37d8437ec825d0f38cc
6
+ metadata.gz: 3aa083acc102175661145c924b62d3cee717a9453bb73abc6415800d70193ef957abe998732592d1171244a2393767141254891db15a73b96ff2d76bca5c38d8
7
+ data.tar.gz: 3375ed736835332ed58078ca090922f483a81b3c7a4e6d10b87c9abb64fc6260a67649c5e4a06147e0fa89bea44d623f6613f803e24f02c171830647864a88f2
data/README.md CHANGED
@@ -4,7 +4,7 @@ Terminal access with support for ANSI control codes and [BBCode-like](https://en
4
4
 
5
5
  - Gem: [rubygems.org](https://rubygems.org/gems/terminal_rb)
6
6
  - Source: [codeberg.org](https://codeberg.org/mblumtritt/Terminal.rb)
7
- - Help: [rubydoc.info](https://rubydoc.info/gems/terminal_rb/0.9.0/Terminal)
7
+ - Help: [rubydoc.info](https://rubydoc.info/gems/terminal_rb/Terminal)
8
8
 
9
9
  ## Features
10
10
 
@@ -14,8 +14,8 @@ Terminal access with support for ANSI control codes and [BBCode-like](https://en
14
14
  - [NO_COLOR convention](https://no-color.org) support
15
15
  - markup of text attributes and colors using a [BBCode](https://en.wikipedia.org/wiki/BBCode)-like syntax
16
16
  - calculation for correct display width of strings containing Unicdode chars inclusive emojis
17
- - word-wise text wrapper
18
- - easy to use key codes
17
+ - generation of word-by-word wrapped text
18
+ - supports [CSIu protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol)
19
19
 
20
20
  ## Examples
21
21
 
@@ -43,7 +43,7 @@ Terminal::Text.width('ライブラリは中国語、日本語、韓国語のテ
43
43
  # => 64
44
44
  ```
45
45
 
46
- ### Split text word-wise and limit line width to 30 chars
46
+ ### Split text word-by-word and limit line width to 30 chars
47
47
 
48
48
  ```ruby
49
49
  Terminal::Text.each_line(<<~TEXT, limit: 30).to_a
data/examples/info.rb CHANGED
@@ -11,5 +11,6 @@ Terminal.puts <<~TEXT
11
11
  Supported Colors: [b]#{Terminal.colors}#{Terminal::Ansi.rainbow(' (truecolor)') if Terminal.true_color?}[/]
12
12
  Terminal Size: [b]#{Terminal.size.join(' x ')}[/]
13
13
  Cursor Position: [b]#{Terminal.pos&.join(', ')}[/]
14
+ Input Mode: [b]#{Terminal.input_mode}[/]
14
15
 
15
16
  TEXT
@@ -6,7 +6,7 @@ Terminal.puts <<~TEXT
6
6
 
7
7
  ✅ [b bright_green]Terminal.rb[/b] — Key Codes:[/]
8
8
  Press any key to display it's control code and name.
9
- [bright_black](Exit with Ctrl+C)[/]
9
+ [bright_black]([b]#{Terminal.input_mode}[/b] mode - exit with ESC)[/fg]
10
10
 
11
11
  TEXT
12
12
 
@@ -18,5 +18,5 @@ while true
18
18
  Terminal.puts(
19
19
  " [blue]: [yellow]#{raw.inspect} [bold bright_green]#{name}[/]"
20
20
  )
21
- break puts if name == 'Ctrl+C'
21
+ break puts if name == 'Esc'
22
22
  end
data/lib/terminal/ansi.rb CHANGED
@@ -482,6 +482,65 @@ module Terminal
482
482
  # @return (see cursor_up)
483
483
  def link(url, text) = "\e]8;;#{url}\a#{text}\e]8;;\a"
484
484
 
485
+ # Create scaled text.
486
+ # It uses the
487
+ # [text sizing protocol](https://sw.kovidgoyal.net/kitty/text-sizing-protocol).
488
+ # This is not widely supported.
489
+ #
490
+ # @example Double-height Greeting
491
+ # Terminal::Ansi.scale('Hello Ruby!', scale: 2)
492
+ #
493
+ # @example Half-height Greeting
494
+ # Terminal::Ansi.scale('Hello Ruby!', fracn: 1, fracd: 2, vertical: :centered)
495
+ #
496
+ # @param [#to_s] text text to scale
497
+ # @param [Integer, nil] scale
498
+ # overall scale size, range 1..7
499
+ # @param [Integer, nil] width
500
+ # with in cells, range 0..7
501
+ # @param [Integer, nil] fracn
502
+ # numerator for the fractional scale, range: 0..15
503
+ # @param [Integer, nil] fracd
504
+ # denominator for the fractional scale, range: 0..15, > fracn
505
+ # @param [:top, :bottom, :centered, nil] vertical
506
+ # vertical alignment to use for fractionally scaled text
507
+ # @param [:left, :right, :centered, nil] horizontal
508
+ # horizontal alignment to use for fractionally scaled text
509
+ # @return (see cursor_up)
510
+ def scale(
511
+ text,
512
+ scale: nil,
513
+ width: nil,
514
+ fracn: nil,
515
+ fracd: nil,
516
+ vertical: nil,
517
+ horizontal: nil
518
+ )
519
+ opts = scale ? ["s=#{scale.clamp(1, 7)}"] : []
520
+ opts << "w=#{width.clamp(0, 7)}" if width
521
+ if fracn
522
+ opts << "n=#{fracn = fracn.clamp(0, 15)}"
523
+ opts << "d=#{fracd.clamp(fracn + 1, 15)}" if fracd
524
+ case vertical
525
+ when 0, :top
526
+ opts << 'v=0'
527
+ when 1, :bottom
528
+ opts << 'v=1'
529
+ when 2, :centered, :center
530
+ opts << 'v=2'
531
+ end
532
+ case horizontal
533
+ when 0, :left
534
+ opts << 'h=0'
535
+ when 1, :right
536
+ opts << 'h=1'
537
+ when 2, :centered, :center
538
+ opts << 'h=2'
539
+ end
540
+ end
541
+ "\e]66;#{opts.join(':')};#{text}\a"
542
+ end
543
+
485
544
  #
486
545
  # @!endgroup
487
546
  #
@@ -559,13 +618,13 @@ module Terminal
559
618
  # @!visibility private
560
619
  CURSOR_HIDE = "\e[?25l"
561
620
 
562
- # CURSOR_POS_SAVE_SCO = "\e[s"
563
- # CURSOR_POS_SAVE_DEC = "\e7"
621
+ # @comment CURSOR_POS_SAVE_SCO = "\e[s"
622
+ # @comment CURSOR_POS_SAVE_DEC = "\e7"
564
623
  # @!visibility private
565
624
  CURSOR_POS_SAVE = "\e7"
566
625
 
567
- # CURSOR_POS_RESTORE_SCO = "\e[u"
568
- # CURSOR_POS_RESTORE_DEC = "\e8"
626
+ # @comment CURSOR_POS_RESTORE_SCO = "\e[u"
627
+ # @comment CURSOR_POS_RESTORE_DEC = "\e8"
569
628
  # @!visibility private
570
629
  CURSOR_POS_RESTORE = "\e8"
571
630
 
@@ -582,11 +641,18 @@ module Terminal
582
641
  SCREEN_SAVE = "\e[?47h"
583
642
  # @!visibility private
584
643
  SCREEN_RESTORE = "\e[?47l"
644
+
585
645
  # @!visibility private
586
- SCREEN_ALTERNATE = "\e[?1049h"
646
+ # @comment at least Kitty requires CURSOR_HOME too
647
+ SCREEN_ALTERNATE = "\e[?1049h#{CURSOR_HOME}".freeze
587
648
  # @!visibility private
588
649
  SCREEN_ALTERNATE_OFF = "\e[?1049l"
589
650
 
651
+ # @!visibility private
652
+ SCREEN_REVERSE_MODE_ON = "\e[?5h"
653
+ # @!visibility private
654
+ SCREEN_REVERSE_MODE_OFF = "\e[?5l"
655
+
590
656
  # @!visibility private
591
657
  LINE_ERASE = line_erase.freeze
592
658
  # @!visibility private
@@ -597,14 +663,22 @@ module Terminal
597
663
  LINE_ERASE_PREV = "#{cursor_prev_line(nil)}#{LINE_ERASE}".freeze
598
664
 
599
665
  # @comment seems not widely supported:
600
- # @comment doubled!? def cursor_column(column = 1) = "\e[#{column}`"
601
- # @comment doubled!? def cursor_row(row = 1) = "\e[#{row}d"
602
- # @comment def cursor_column_rel(columns = 1) = "\e[#{columns}a"
603
- # @comment def cursor_row_rel(rows = 1) = "\e[#{rows}e"
604
- # @comment def cursor_tab(count = 1) = "\e[#{column}I"
605
- # @comment def cursor_reverse_tab(count = 1) = "\e[#{count}Z"
606
- # @comment def chars_delete(count = 1) = "\e[#{count}P"
607
- # @comment def chars_erase(count = 1) = "\e[#{count}X"
608
- # @comment def notify(title) = "\e]9;#{title}\a"
666
+ # doubled!? def cursor_column(column = 1) = "\e[#{column}`"
667
+ # doubled!? def cursor_row(row = 1) = "\e[#{row}d"
668
+ # def cursor_column_rel(columns = 1) = "\e[#{columns}a"
669
+ # def cursor_row_rel(rows = 1) = "\e[#{rows}e"
670
+ # def cursor_tab(count = 1) = "\e[#{column}I"
671
+ # def cursor_reverse_tab(count = 1) = "\e[#{count}Z"
672
+ # def chars_delete(count = 1) = "\e[#{count}P"
673
+ # def chars_erase(count = 1) = "\e[#{count}X"
674
+ # def notify(title) = "\e]9;#{title}\a"
675
+
676
+ # @comment TODO:
677
+ # https://sw.kovidgoyal.net/kitty/desktop-notifications
678
+ # https://sw.kovidgoyal.net/kitty/pointer-shapes
679
+ # https://sw.kovidgoyal.net/kitty/unscroll
680
+ # https://sw.kovidgoyal.net/kitty/color-stack
681
+ # https://sw.kovidgoyal.net/kitty/deccara
682
+ # https://sw.kovidgoyal.net/kitty/clipboard
609
683
  end
610
684
  end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Terminal
4
+ module CSIUMode
5
+ class << self
6
+ def type = :csi_u
7
+
8
+ def key_name(key)
9
+ return unless key
10
+ return ORD[key.ord] if key.size == 1
11
+ return if key[0] != "\e"
12
+ return C0_LEGACY[key[1].ord] if key.size == 2 # ESC ?
13
+ return csi(key) if key[1] == '['
14
+ SS3[key[2].ord] if key[1] == 'O' && key.size == 3 # ESC O ?
15
+ end
16
+
17
+ private
18
+
19
+ def csi(key)
20
+ type = key[-1]
21
+ return unicode(key) if type == 'u' # ESC [ ? u
22
+ return legacy(key) if type == '~' # ESC [ ? ; <modifier> ~
23
+ if key.size == 3 # ESC [ ?
24
+ ord = type.ord
25
+ return ord == 0x5a ? 'Shift+Tab' : SS3[ord]
26
+ end
27
+ return if key.size < 5
28
+ return unless key.start_with?("\e[1;")
29
+ # ESC [ 1 ; <modifier> ?
30
+ name = SS3[type.ord] or return
31
+ mod = key[4..-2]
32
+ mod.empty? ? name : modifier(name, mod.to_i - 1)
33
+ end
34
+
35
+ def unicode(key)
36
+ with_modifier(key) { ORD[_1] || _1.chr(Encoding::UTF_8) }
37
+ end
38
+
39
+ def legacy(key)
40
+ with_modifier(key) { _1 == 13 ? 'F3' : ORD[_1] }
41
+ end
42
+
43
+ def with_modifier(key)
44
+ code, mod = key[2..-2].split(';', 2)
45
+ code = yield(code.to_i) or return
46
+ mod ? modifier(code, mod.to_i - 1) : code
47
+ end
48
+
49
+ def modifier(name, mod)
50
+ (MODS.filter_map { |b, n| n if mod.allbits?(b) } << name).join('+')
51
+ end
52
+ end
53
+
54
+ ORD = {
55
+ 0x02 => 'Ins',
56
+ 0x03 => 'Del',
57
+ 0x05 => 'PageUp',
58
+ 0x06 => 'PageDown',
59
+ 0x07 => 'Home',
60
+ 0x08 => 'End',
61
+ 0x09 => 'Tab',
62
+ 0x0b => 'F1',
63
+ 0x0c => 'F2',
64
+ 0x0d => 'Enter',
65
+ 0x0e => 'F4',
66
+ 0x0f => 'F5',
67
+ 0x11 => 'F6',
68
+ 0x12 => 'F7',
69
+ 0x13 => 'F8',
70
+ 0x14 => 'F9',
71
+ 0x15 => 'F10',
72
+ 0x17 => 'F11',
73
+ 0x18 => 'F12',
74
+ 0x1b => 'Esc',
75
+ 0x7f => 'Back'
76
+ }.compare_by_identity.freeze
77
+
78
+ # ESC ?
79
+ C0_LEGACY = {
80
+ 0x00 => 'Ctrl+Space',
81
+ 0x08 => 'Ctrl+Back',
82
+ 0x09 => 'Alt+Tab',
83
+ 0x0d => 'Alt+Enter',
84
+ 0x1b => 'Alt+Esc',
85
+ 0x20 => 'Alt+Space',
86
+ 0x64 => 'Alt+Del',
87
+ 0x7f => 'Alt+Back'
88
+ }.compare_by_identity.freeze
89
+
90
+ # ESC [ ?
91
+ # ESC [ O ?
92
+ SS3 = {
93
+ 0x41 => 'Up', # A
94
+ 0x42 => 'Down', # B
95
+ 0x43 => 'Right', # C
96
+ 0x44 => 'Left', # D
97
+ 0x46 => 'End', # F
98
+ 0x48 => 'Home', # H
99
+ 0x50 => 'F1', # P
100
+ 0x51 => 'F2', # Q
101
+ 0x52 => 'F3', # R
102
+ 0x53 => 'F4' # S
103
+ }.compare_by_identity.freeze
104
+
105
+ MODS = {
106
+ 1 => 'Shift',
107
+ 2 => 'Alt',
108
+ 4 => 'Ctrl',
109
+ 8 => 'Super',
110
+ 16 => 'Hyper',
111
+ 32 => 'Meta',
112
+ 64 => 'Caps',
113
+ 128 => 'Num'
114
+ }.freeze
115
+ end
116
+ private_constant :CSIUMode
117
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Terminal
4
+ module LegacyMode
5
+ class << self
6
+ def type = :legacy
7
+
8
+ def key_name(key)
9
+ return unless key
10
+ return ORD[key.ord] if key.size == 1
11
+ return if key[0] != "\e"
12
+ return csi(key) if key[1] == '['
13
+ SS3[key[2].ord] if key[1] == 'O' && key.size == 3 # ESC O ?
14
+ end
15
+
16
+ private
17
+
18
+ def csi(key)
19
+ return legacy(key) if key[-1] == '~' # ESC [ ? ; <modifier> ~
20
+ if key.size == 3 # ESC [ ?
21
+ ord = key[2].ord
22
+ return ord == 0x5a ? 'Shift+Tab' : SS3[ord]
23
+ end
24
+ # ESC [ 1 ; <modifier> ?
25
+ return if key.size < 5
26
+ return unless key.start_with?("\e[1;")
27
+ # ESC [ ? {ABCDEFHPQRS}
28
+ name = SS3[key[-1].ord] or return
29
+ mod = key[4..-2]
30
+ mod.empty? ? name : modifier(name, mod.to_i - 1)
31
+ end
32
+
33
+ def legacy(key)
34
+ with_modifier(key) { LEGACY[_1] }
35
+ end
36
+
37
+ def with_modifier(key)
38
+ code, mod = key[2..-2].split(';', 2)
39
+ code = yield(code.to_i) or return
40
+ mod ? modifier(code, mod.to_i - 1) : code
41
+ end
42
+
43
+ def modifier(name, mod)
44
+ (MODS.filter_map { |b, n| n if mod.allbits?(b) } << name).join('+')
45
+ end
46
+ end
47
+
48
+ ORD = {
49
+ 0x00 => 'Ctrl+2',
50
+ 0x01 => 'Ctrl+a',
51
+ 0x02 => 'Ctrl+b',
52
+ 0x03 => 'Ctrl+c',
53
+ 0x04 => 'Del',
54
+ 0x05 => 'Ctrl+e',
55
+ 0x06 => 'Ctrl+f',
56
+ 0x07 => 'Ctrl+g',
57
+ 0x08 => 'Ctrl+h',
58
+ 0x09 => 'Tab',
59
+ 0x0a => 'Ctrl+j',
60
+ 0x0b => 'Ctrl+k',
61
+ 0x0c => 'Ctrl+l',
62
+ 0x0d => 'Enter',
63
+ 0x0e => 'Ctrl+n',
64
+ 0x0f => 'Ctrl+o',
65
+ 0x10 => 'Ctrl+p',
66
+ 0x11 => 'Ctrl+q',
67
+ 0x12 => 'Ctrl+r',
68
+ 0x13 => 'Ctrl+s',
69
+ 0x14 => 'Ctrl+t',
70
+ 0x15 => 'Ctrl+u',
71
+ 0x16 => 'Ctrl+v',
72
+ 0x17 => 'Ctrl+w',
73
+ 0x18 => 'Ctrl+x',
74
+ 0x19 => 'Ctrl+y',
75
+ 0x1a => 'Ctrl+z',
76
+ 0x1d => 'Ctrl+ü', # FIND AM
77
+ 0x1e => 'Ctrl+^', # FIND AM
78
+ 0x1f => 'Ctrl+-',
79
+ 0x1b => 'Esc',
80
+ 0x7f => 'Back'
81
+ }.compare_by_identity.freeze
82
+
83
+ # ESC [ ?
84
+ # ESC [ O ?
85
+ SS3 = {
86
+ 0x41 => 'Up', # A
87
+ 0x42 => 'Down', # B
88
+ 0x43 => 'Right', # C
89
+ 0x44 => 'Left', # D
90
+ 0x46 => 'End', # F
91
+ 0x48 => 'Home', # H
92
+ 0x50 => 'F1', # P
93
+ 0x51 => 'F2', # Q
94
+ 0x52 => 'F3', # R
95
+ 0x53 => 'F4' # S
96
+ }.compare_by_identity.freeze
97
+
98
+ LEGACY = {
99
+ 0x03 => 'Del',
100
+ 0x0f => 'F5',
101
+ 0x11 => 'F6',
102
+ 0x12 => 'F7',
103
+ 0x13 => 'F8',
104
+ 0x14 => 'F9',
105
+ 0x15 => 'F10',
106
+ 0x17 => 'F11',
107
+ 0x18 => 'F12'
108
+ }.compare_by_identity.freeze
109
+
110
+ MODS = {
111
+ 1 => 'Shift',
112
+ 2 => 'Alt',
113
+ 4 => 'Ctrl',
114
+ 8 => 'Super',
115
+ 16 => 'Hyper',
116
+ 32 => 'Meta',
117
+ 64 => 'Caps',
118
+ 128 => 'Num'
119
+ }.freeze
120
+ end
121
+ private_constant :LegacyMode
122
+ end
@@ -4,9 +4,22 @@ require 'io/console'
4
4
 
5
5
  module Terminal
6
6
  class << self
7
+ # Supported input mode.
8
+ #
9
+ # @attribute [r] input_mode
10
+ # @return [:csi_u] when CSIu protocol support
11
+ # @return [:legacy] for standard terminal
12
+ # @return [:dumb] for non-interactive input (pipes etc.)
13
+ # @return [:error] when input device is closed
14
+ def input_mode
15
+ return @inp_mode.type if (@inp ||= find_inp) == READ_TTY_INP
16
+ return :dumb if @inp == READ_DUMB_INP
17
+ :error
18
+ end
19
+
7
20
  # Read next keyboard input.
8
21
  #
9
- # The input will be returned as named key codes like "Ctrl+C" by default.
22
+ # The input will be returned as named key codes like "Ctrl+c" by default.
10
23
  # This can be changed by `mode`.
11
24
  #
12
25
  # @param [:named, :raw, :both] mode modifies the result
@@ -14,175 +27,58 @@ module Terminal
14
27
  # @return [String] key name in `:named` mode
15
28
  # @return [[String, String]] key code and key name in `:both` mode
16
29
  def read_key(mode: :named)
17
- key = _raw_read_key or return
30
+ key = (@inp ||= find_inp).call or return
18
31
  return key if mode == :raw
19
- mode == :both ? [key, _key_name(key)] : _key_name(key) || key
32
+ return key, @inp_mode.key_name(key) if mode == :both
33
+ @inp_mode.key_name(key) || key
20
34
  end
21
35
 
22
36
  private
23
37
 
24
- def _raw_read_key
25
- return unless @in
26
- return @in.getc unless @in.tty?
27
- key = @in.getch
28
- while (nc = @in.read_nonblock(1, exception: false))
29
- String === nc ? key += nc : break
38
+ def find_inp
39
+ return READ_DUMB_INP unless STDIN.tty?
40
+ return READ_TTY_INP if !ansi? || _write("\e[>1u\e[?u\e[c").nil?
41
+ while true
42
+ result = READ_TTY_INP.call
43
+ if result.include?("\e[?1u")
44
+ @inp_mode = CSIUMode
45
+ at_exit { _write("\e[<u") } if ENV['ENV'] != 'test'
46
+ end
47
+ return READ_TTY_INP if result[-1] == 'c'
30
48
  end
31
- key
32
- rescue Interrupt
33
- key
34
49
  rescue IOError, SystemCallError
35
- @in = nil
36
- end
37
-
38
- def _key_name(key)
39
- return unless key
40
- return KEY_MAP[key]&.dup if key.size != 1
41
- KEY_MAP[key]&.dup if (ord = key.ord) == 127 || (ord > 0 && ord < 28)
50
+ -> {}
51
+ ensure
52
+ @inp_mode ||= LegacyMode
42
53
  end
43
54
  end
44
55
 
45
- @in = STDIN
46
-
47
- KEY_MAP =
48
- Module
49
- .new do
50
- def self.add_modifiers(keys)
51
- @mods.each_pair do |mod, pref|
52
- keys.each_pair do |name, code|
53
- @map["\e[1;#{mod}#{code}"] = "#{pref}+#{name}"
54
- end
55
- end
56
- end
57
-
58
- def self.add_keys(keys)
59
- keys.each_pair { |name, code| @map["\e[#{code}"] = name }
60
- add_modifiers(keys)
61
- end
62
-
63
- def self.add_fkeys(keys)
64
- keys.each_pair { |name, code| @map["\e[#{code}~"] = name }
65
- @mods.each_pair do |mod, prefix|
66
- keys.each_pair do |name, code|
67
- @map["\e[#{code};#{mod}~"] = "#{prefix}+#{name}"
68
- end
69
- end
70
- end
71
-
72
- def self.to_hash
73
- num = 0
74
- @map = ('A'..'Z').to_h { [(num += 1).chr, "Ctrl+#{_1}"] }
75
-
76
- add_keys(
77
- 'F1' => 'P',
78
- 'F2' => 'Q',
79
- 'F3' => 'R',
80
- 'F4' => 'S',
81
- 'Up' => 'A',
82
- 'Down' => 'B',
83
- 'Right' => 'C',
84
- 'Left' => 'D',
85
- 'End' => 'F',
86
- 'Home' => 'H'
87
- )
88
-
89
- add_fkeys(
90
- 'Del' => '3',
91
- 'PgUp' => '5',
92
- 'PgDown' => '6',
93
- # -
94
- 'F1' => 'F1',
95
- 'F2' => 'F2',
96
- 'F3' => 'F3',
97
- 'F4' => 'F4',
98
- # -
99
- 'F5' => '15',
100
- 'F6' => '17',
101
- 'F7' => '18',
102
- 'F8' => '19',
103
- 'F9' => '20',
104
- 'F10' => '21',
105
- 'F11' => '23',
106
- 'F12' => '24',
107
- 'F13' => '25',
108
- 'F14' => '26',
109
- 'F15' => '28',
110
- 'F16' => '29',
111
- 'F17' => '31',
112
- 'F18' => '32',
113
- 'F19' => '33',
114
- 'F20' => '34'
115
- )
116
-
117
- add_fkeys('F3' => '13') # Kitty
118
-
119
- # Kitty:
120
- @mods.each_pair do |mod, pref|
121
- ('a'..'z').each do |char|
122
- @map["\e[#{char.ord};#{mod}u"] = "#{pref}+#{char.upcase}"
123
- end
124
- end
125
-
126
- {
127
- 'Esc' => "\e",
128
- 'Enter' => "\r",
129
- 'Tab' => "\t",
130
- 'Back' => "\u007F",
131
- 'Shift+Tab' => "\e[Z"
132
- }.each_pair do |name, code|
133
- @map[code] = name
134
- @map["\e#{code}"] = "Alt+#{name}" # Kitty
135
- end
136
-
137
- # overrides and additional keys
138
- @map.merge!(
139
- "\b" => 'Ctrl+Back',
140
- "\e\b" => 'Ctrl+Alt+Back', # Kitty
141
- "\4" => 'Del',
142
- "\e[5" => 'PgUp',
143
- "\e[6" => 'PgDown',
144
- # SS3 control (VT 100 etc)
145
- "\eOA" => 'Up',
146
- "\eOB" => 'Down',
147
- "\eOC" => 'Right',
148
- "\eOD" => 'Left',
149
- "\eOP" => 'F1',
150
- "\eOQ" => 'F2',
151
- "\eOR" => 'F3',
152
- "\eOS" => 'F4',
153
- "\eO2P" => 'Shift+F1',
154
- "\eO2Q" => 'Shift+F2',
155
- "\eO2R" => 'Shift+F3',
156
- "\eO2S" => 'Shift+F4',
157
- "\eOt" => 'F5',
158
- "\eOu" => 'F6',
159
- "\eOv" => 'F7',
160
- "\eOw" => 'F8',
161
- "\eOl" => 'F9',
162
- "\eOx" => 'F10'
163
- )
164
- end
56
+ READ_DUMB_INP =
57
+ lambda do
58
+ STDIN.getc
59
+ rescue Interrupt
60
+ nil
61
+ rescue IOError, SystemCallError
62
+ @inp = -> {}
63
+ nil
64
+ end
165
65
 
166
- @mods = {
167
- 2 => 'Shift',
168
- 3 => 'Alt',
169
- 4 => 'Alt-Shift',
170
- 5 => 'Ctrl',
171
- 6 => 'Ctrl-Shift',
172
- 7 => 'Ctrl-Alt',
173
- 8 => 'Ctrl-Alt-Shift',
174
- 9 => 'Meta',
175
- 10 => 'Meta-Shift',
176
- 11 => 'Meta-Alt',
177
- 12 => 'Meta-Alt-Shift',
178
- 13 => 'Ctrl-Meta',
179
- 14 => 'Ctrl-Meta-Shift',
180
- 15 => 'Ctrl-Meta-Alt',
181
- 16 => 'Ctrl-Meta-Alt-Shift'
182
- }.compare_by_identity
66
+ READ_TTY_INP =
67
+ lambda do
68
+ key = STDIN.getch
69
+ while (nc = STDIN.read_nonblock(1, exception: false))
70
+ String === nc ? key += nc : break
183
71
  end
184
- .to_hash
185
- .freeze
72
+ key
73
+ rescue Interrupt
74
+ key
75
+ rescue IOError, SystemCallError
76
+ @inp = READ_DUMB_INP
77
+ nil
78
+ end
186
79
 
187
- private_constant :KEY_MAP
80
+ dir = __dir__
81
+ autoload :LegacyMode, "#{dir}/input/legacy_mode.rb"
82
+ autoload :CSIUMode, "#{dir}/input/csiu_mode.rb"
83
+ private_constant :READ_DUMB_INP, :READ_TTY_INP, :LegacyMode, :CSIUMode
188
84
  end
@@ -7,6 +7,7 @@ RSpec.shared_context 'with Terminal' do |mod, ansi: true, winsize: [25, 80]|
7
7
  let(:terminal) do
8
8
  load('terminal/ansi/attributes.rb', wrapper_module)
9
9
  load('terminal/ansi.rb', wrapper_module)
10
+ load('terminal/input/legacy_mode.rb', wrapper_module)
10
11
  load('terminal/input.rb', wrapper_module)
11
12
  load('terminal/detect.rb', wrapper_module)
12
13
  load('terminal.rb', wrapper_module)