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 +4 -4
- data/README.md +2 -2
- data/examples/info.rb +1 -0
- data/examples/key-codes.rb +2 -2
- data/lib/terminal/ansi.rb +86 -14
- data/lib/terminal/input/csiu_mode.rb +117 -0
- data/lib/terminal/input/legacy_mode.rb +122 -0
- data/lib/terminal/input.rb +55 -159
- data/lib/terminal/rspec/helper.rb +1 -0
- data/lib/terminal/version.rb +1 -1
- data/lib/terminal.rb +8 -3
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 673e2c03057d8f9c6c73d8dbe3f464a70ab796e67b272dd6de58ec44ab82bde8
|
4
|
+
data.tar.gz: 3fa3b10c9370c7ab9113d8f7a961ebd74437fdba76dc34a64716f955df1fae6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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/
|
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
|
-
-
|
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
|
data/examples/key-codes.rb
CHANGED
@@ -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](
|
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 == '
|
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
|
-
|
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
|
-
#
|
601
|
-
#
|
602
|
-
#
|
603
|
-
#
|
604
|
-
#
|
605
|
-
#
|
606
|
-
#
|
607
|
-
#
|
608
|
-
#
|
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
|
data/lib/terminal/input.rb
CHANGED
@@ -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+
|
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 =
|
30
|
+
key = (@inp ||= find_inp).call or return
|
18
31
|
return key if mode == :raw
|
19
|
-
|
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
|
25
|
-
return unless
|
26
|
-
return
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
36
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
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
|
-
|
185
|
-
|
72
|
+
key
|
73
|
+
rescue Interrupt
|
74
|
+
key
|
75
|
+
rescue IOError, SystemCallError
|
76
|
+
@inp = READ_DUMB_INP
|
77
|
+
nil
|
78
|
+
end
|
186
79
|
|
187
|
-
|
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)
|
data/lib/terminal/version.rb
CHANGED
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.
|
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.
|
52
|
+
documentation_uri: https://rubydoc.info/gems/terminal_rb/0.9.3/Terminal
|
51
53
|
rdoc_options: []
|
52
54
|
require_paths:
|
53
55
|
- lib
|