terminal_rb 0.9.1 → 0.9.3

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: 673e2c03057d8f9c6c73d8dbe3f464a70ab796e67b272dd6de58ec44ab82bde8
4
+ data.tar.gz: 3fa3b10c9370c7ab9113d8f7a961ebd74437fdba76dc34a64716f955df1fae6a
5
5
  SHA512:
6
- metadata.gz: d53c04af3d74273cf3111b9b7078846e0c33f0caf8501e9f0182427ed784d96b94444c1f6238427dd248cf300fb362d95a3a0f5dea2ead5c571ff2fd7d52fdf9
7
- data.tar.gz: 82d79cfab6b7d499bb5d6f9fb7d6ce9af14f69ed792f8f7eedf4485a4f26546576ecadd723cc67a48ffbb4bc00ab1cc83d04a15a1bceb37d8437ec825d0f38cc
6
+ metadata.gz: 91ba6fb64a85db7d30f5a390b8d3b5544ad5080428d832582a5a1ee655dac23e7bdebee185784dccdc5e2ac20eddfb33c64808a93893297769e994522dbdd696
7
+ data.tar.gz: 435c3f4369383850562d5b95f355bdb45fb4fb235ba9bba7eb451404657552c75e54b6b181c975903f4e6041a73206e08023c1e3fd77bedb3d28fb10f33e5612
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
 
@@ -15,7 +15,7 @@ Terminal access with support for ANSI control codes and [BBCode-like](https://en
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
17
  - word-wise text wrapper
18
- - easy to use key codes
18
+ - supports [CSIu protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol)
19
19
 
20
20
  ## Examples
21
21
 
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,63 @@ 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 on 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
+ # @return (see cursor_up)
508
+ def scale(
509
+ text,
510
+ scale: nil,
511
+ width: nil,
512
+ fracn: nil,
513
+ fracd: nil,
514
+ vertical: nil,
515
+ horizontal: nil
516
+ )
517
+ opts = scale ? ["s=#{scale.clamp(1, 7)}"] : []
518
+ opts << "w=#{width.clamp(0, 7)}" if width
519
+ if fracn
520
+ opts << "n=#{fracn = fracn.clamp(0, 15)}"
521
+ opts << "d=#{fracd.clamp(fracn + 1, 15)}" if fracd
522
+ case vertical
523
+ when 0, :top
524
+ opts << 'v=0'
525
+ when 1, :bottom
526
+ opts << 'v=1'
527
+ when 2, :centered, :center
528
+ opts << 'v=2'
529
+ end
530
+ case horizontal
531
+ when 0, :left
532
+ opts << 'h=0'
533
+ when 1, :right
534
+ opts << 'h=1'
535
+ when 2, :centered, :center
536
+ opts << 'h=2'
537
+ end
538
+ end
539
+ "\e]66;#{opts.join(':')};#{text}\a"
540
+ end
541
+
485
542
  #
486
543
  # @!endgroup
487
544
  #
@@ -559,13 +616,13 @@ module Terminal
559
616
  # @!visibility private
560
617
  CURSOR_HIDE = "\e[?25l"
561
618
 
562
- # CURSOR_POS_SAVE_SCO = "\e[s"
563
- # CURSOR_POS_SAVE_DEC = "\e7"
619
+ # @comment CURSOR_POS_SAVE_SCO = "\e[s"
620
+ # @comment CURSOR_POS_SAVE_DEC = "\e7"
564
621
  # @!visibility private
565
622
  CURSOR_POS_SAVE = "\e7"
566
623
 
567
- # CURSOR_POS_RESTORE_SCO = "\e[u"
568
- # CURSOR_POS_RESTORE_DEC = "\e8"
624
+ # @comment CURSOR_POS_RESTORE_SCO = "\e[u"
625
+ # @comment CURSOR_POS_RESTORE_DEC = "\e8"
569
626
  # @!visibility private
570
627
  CURSOR_POS_RESTORE = "\e8"
571
628
 
@@ -582,11 +639,18 @@ module Terminal
582
639
  SCREEN_SAVE = "\e[?47h"
583
640
  # @!visibility private
584
641
  SCREEN_RESTORE = "\e[?47l"
642
+
585
643
  # @!visibility private
586
- SCREEN_ALTERNATE = "\e[?1049h"
644
+ # @comment at least Kitty requires CURSOR_HOME too
645
+ SCREEN_ALTERNATE = "\e[?1049h#{CURSOR_HOME}".freeze
587
646
  # @!visibility private
588
647
  SCREEN_ALTERNATE_OFF = "\e[?1049l"
589
648
 
649
+ # @!visibility private
650
+ SCREEN_REVERSE_MODE_ON = "\e[?5h"
651
+ # @!visibility private
652
+ SCREEN_REVERSE_MODE_OFF = "\e[?5l"
653
+
590
654
  # @!visibility private
591
655
  LINE_ERASE = line_erase.freeze
592
656
  # @!visibility private
@@ -597,14 +661,22 @@ module Terminal
597
661
  LINE_ERASE_PREV = "#{cursor_prev_line(nil)}#{LINE_ERASE}".freeze
598
662
 
599
663
  # @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"
664
+ # doubled!? def cursor_column(column = 1) = "\e[#{column}`"
665
+ # doubled!? def cursor_row(row = 1) = "\e[#{row}d"
666
+ # def cursor_column_rel(columns = 1) = "\e[#{columns}a"
667
+ # def cursor_row_rel(rows = 1) = "\e[#{rows}e"
668
+ # def cursor_tab(count = 1) = "\e[#{column}I"
669
+ # def cursor_reverse_tab(count = 1) = "\e[#{count}Z"
670
+ # def chars_delete(count = 1) = "\e[#{count}P"
671
+ # def chars_erase(count = 1) = "\e[#{count}X"
672
+ # def notify(title) = "\e]9;#{title}\a"
673
+
674
+ # @comment TODO:
675
+ # https://sw.kovidgoyal.net/kitty/desktop-notifications
676
+ # https://sw.kovidgoyal.net/kitty/pointer-shapes
677
+ # https://sw.kovidgoyal.net/kitty/unscroll
678
+ # https://sw.kovidgoyal.net/kitty/color-stack
679
+ # https://sw.kovidgoyal.net/kitty/deccara
680
+ # https://sw.kovidgoyal.net/kitty/clipboard
609
681
  end
610
682
  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)
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Terminal
4
4
  # The version number of the gem.
5
- VERSION = '0.9.1'
5
+ VERSION = '0.9.3'
6
6
  end
data/lib/terminal.rb CHANGED
@@ -6,16 +6,21 @@ require_relative 'terminal/input'
6
6
  #
7
7
  # Terminal access with support for ANSI control codes and
8
8
  # [BBCode-like](https://en.wikipedia.org/wiki/BBCode) embedded text attribute
9
- # syntax.
9
+ # syntax (see {Ansi.bbcode}).
10
10
  #
11
- # It automagically detects whether your terminal supports ANSI features.
11
+ # It automagically detects whether your terminal supports ANSI features, like
12
+ # coloring (see {colors}) or the
13
+ # [CSIu protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol) support
14
+ # (see {read_key} and {input_mode}). It calculates the display width for Unicode
15
+ # chars (see {Text.width}) and help you to display text with line formatting
16
+ # (see {Text.each_line}).
12
17
  #
13
18
  module Terminal
14
19
  class << self
15
20
  # Return true when the current terminal supports ANSI control codes.
16
21
  # When the terminal does not support it, {colors} will return `2` as color
17
22
  # count and all output methods ({<<}, {print}, {puts}) will not forward
18
- # ANSI control codes to the terminal.
23
+ # ANSI control codes to the terminal, {read_key} will not support CSIu.
19
24
  #
20
25
  # @attribute [r] ansi?
21
26
  # @return [Boolean] whether ANSI control codes are supported
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: terminal_rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
@@ -34,6 +34,8 @@ files:
34
34
  - lib/terminal/ansi/named_colors.rb
35
35
  - lib/terminal/detect.rb
36
36
  - lib/terminal/input.rb
37
+ - lib/terminal/input/csiu_mode.rb
38
+ - lib/terminal/input/legacy_mode.rb
37
39
  - lib/terminal/preload.rb
38
40
  - lib/terminal/rspec/helper.rb
39
41
  - lib/terminal/text.rb
@@ -47,7 +49,7 @@ metadata:
47
49
  rubygems_mfa_required: 'true'
48
50
  source_code_uri: https://codeberg.org/mblumtritt/Terminal.rb
49
51
  bug_tracker_uri: https://codeberg.org/mblumtritt/Terminal.rb/issues
50
- documentation_uri: https://rubydoc.info/gems/terminal_rb/0.9.1/Terminal
52
+ documentation_uri: https://rubydoc.info/gems/terminal_rb/0.9.3/Terminal
51
53
  rdoc_options: []
52
54
  require_paths:
53
55
  - lib