terminal_rb 0.15.0 → 0.16.0

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: 23d41a32cd285a8ec05dde44ac26131e207f567b4137ff4c1b144171abb72dd6
4
- data.tar.gz: 28325ab102a8ecd1ec6e9f2b9196aa85f8bf8d59898b6105a26d6afa2cc1a065
3
+ metadata.gz: 05eda4c596ad7060b576cb01f6fe7dafd38e114274b44c14c4c90e1b3a2b322a
4
+ data.tar.gz: '0479860569041d6fb874d38de0d183a2cc778a6803d3eed302425cd795133e06'
5
5
  SHA512:
6
- metadata.gz: ff88211e6ac2b869db965ef8861ddf312699c4cef971deea0f2b75bfac0f77df8f23936aa2a6870adfd60167391a720e76a089918b1e8b2bb35842d6fd32cb0a
7
- data.tar.gz: '091504b7dfebe143f8df736f52db891a08a27d4bac9763706af06def5a9c2e413b620300c4188bd700c529b2446cb1ecb351a4425772bdd051b46332b96ce4bd'
6
+ metadata.gz: ab038b9b4cc9ed41ba09673be9a2e2d25aa2edc2ffbf1fa0cad826ca7f55c8fef3aab734648f13f63750ea7799bce638479839e5f609e597eeb0bf4508b0f0e7
7
+ data.tar.gz: b501721dfd7d6d79ceb8821fa5dd9d877e764e235435f3e6441e9b02b424a5873aba1318365a4b064a9430c135882be39470361d0042b56726be3dd2505e9db5
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Terminal.rb ![version](https://img.shields.io/gem/v/terminal_rb?label=)
2
2
 
3
- Terminal.rb supports you with input and output on your terminal. Simple [BBCode](https://en.wikipedia.org/wiki/BBCode)-like markup for attributes and coloring, word-wise line breaks, and correct special key recognition enable you to implement your CLI app quickly and easily.
3
+ Terminal.rb supports you with input and output on your terminal. Simple [BBCode](https://en.wikipedia.org/wiki/BBCode)-like markup for attributes and coloring, word-wise line breaks, correct special key recognition and mouse event reporting enable you to implement your CLI app quickly and easily.
4
4
 
5
5
  - Gem: [rubygems.org](https://rubygems.org/gems/terminal_rb)
6
6
  - Source: [codeberg.org](https://codeberg.org/mblumtritt/Terminal.rb)
@@ -16,6 +16,7 @@ Terminal.rb supports you with input and output on your terminal. Simple [BBCode]
16
16
  - calculation for correct display width of strings containing Unicdode chars inclusive emojis
17
17
  - word-wise line break generator
18
18
  - supports [CSIu protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol)
19
+ - mouse events support
19
20
 
20
21
  ## Examples
21
22
 
@@ -13,10 +13,13 @@ TEXT
13
13
  Terminal.hide_cursor
14
14
  at_exit { Terminal.show_cursor } # required for some terminals :/
15
15
 
16
- while true
17
- event = Terminal.read_key_event or break puts
16
+ # if you like to have mouse position changes reported then use
17
+ # 'mouse_move: true' in next line
18
+ Terminal.on_key_event(mouse: true, focus: true, mouse_move: false) do |event|
18
19
  str = "[blue]: [yellow]#{event.raw.inspect}"
19
20
  str << " [bold bright_green]#{event.name}[/]" unless event.simple?
21
+ str << " [dim]#{event.position.inspect}[/]" if event.position
20
22
  Terminal.puts(str)
21
- break puts if event.name == 'Esc'
23
+ event.name != 'Esc'
22
24
  end
25
+ puts
data/lib/terminal/ansi.rb CHANGED
@@ -34,8 +34,8 @@ module Terminal
34
34
  # @!group ANSI control code generator functions
35
35
  #
36
36
 
37
- # Combine given ANSI {attributes}, {colors}, {named_colors} and color
38
- # codes.
37
+ # Combine given {attributes}, {colors}, {named_colors} and color codes to
38
+ # an ANSI control code sequence.
39
39
  #
40
40
  # Colors can specified by their name for ANSI 3-bit and 4-bit colors.
41
41
  # For 8-bit ANSI colors use 2-digit hexadecimal values `00`...`ff`.
@@ -96,7 +96,7 @@ module Terminal
96
96
  return +'' if attributes.empty?
97
97
  "\e[#{
98
98
  attributes
99
- .map do |arg|
99
+ .map! do |arg|
100
100
  case arg
101
101
  when String
102
102
  @attr_map[arg] || _invalid(arg)
@@ -154,8 +154,7 @@ module Terminal
154
154
  # @param str [#to_s] string to be modified
155
155
  # @return [String] string without ANSI attributes
156
156
  def undecorate(str)
157
- str = str.to_s
158
- str.index("\e") ? str.gsub(@re_test, '') : str.dup
157
+ (str = str.to_s).index("\e") ? str.gsub(@re_test, '') : str.dup
159
158
  end
160
159
 
161
160
  # Try to combine given ANSI attributes and colors.
@@ -263,8 +262,7 @@ module Terminal
263
262
  # @param str [#to_s] string to be modified
264
263
  # @return [String] string without BBCode and ANSI control codes.
265
264
  def plain(str)
266
- str = str.to_s
267
- unless str.index('[')
265
+ unless (str = str.to_s).index('[')
268
266
  return str.index("\e") ? str.gsub(@re_test, '') : str.dup
269
267
  end
270
268
  str =
@@ -289,16 +287,14 @@ module Terminal
289
287
  pos = -1
290
288
  @pi2_third ||= 2.0 * Math::PI / 3.0
291
289
  @pi4_third ||= 4.0 * Math::PI / 3.0
292
- str
293
- .to_s
294
- .chars
295
- .map! do |char|
290
+ (
291
+ str.to_s.chars.map! do |char|
296
292
  i = (seed + ((pos += 1) / spread)) * frequency
297
293
  "\e[38;2;#{(Math.sin(i) * 255).to_i.abs};" \
298
294
  "#{(Math.sin(i + @pi2_third) * 255).to_i.abs};" \
299
295
  "#{(Math.sin(i + @pi4_third) * 255).to_i.abs}m#{char}"
300
- end
301
- .join << RESET
296
+ end << RESET
297
+ ).join
302
298
  end
303
299
 
304
300
  #
@@ -349,6 +345,21 @@ module Terminal
349
345
  # @return (see cursor_up)
350
346
  def cursor_column(column = 1) = "\e[#{column}G"
351
347
 
348
+ # Move cursor to given column in the current row relative to the current
349
+ # position.
350
+ # (Skip some columns.)
351
+ #
352
+ # @param (see cursor_column)
353
+ # @return (see cursor_up)
354
+ def cursor_column_rel(column = 1) = "\e[#{column}a"
355
+
356
+ # Move cursor to given row relative to the current position.
357
+ # (Skip some rows.)
358
+ #
359
+ # @param row [Integer] row index
360
+ # @return (see cursor_up)
361
+ def cursor_row_rel(row = 1) = "\e[#{row}e"
362
+
352
363
  # Move to given row and column.
353
364
  #
354
365
  # @param row [Integer] row index
@@ -389,20 +400,7 @@ module Terminal
389
400
  #
390
401
  # @param part [:below, :above, :all, :scrollback] screen part to erase
391
402
  # @return (see cursor_up)
392
- def screen_erase(part = :all)
393
- "\e[#{
394
- case part
395
- when :below
396
- # nop
397
- when :above
398
- '1'
399
- when :scrollback
400
- '3'
401
- else # all
402
- '2'
403
- end
404
- }J"
405
- end
403
+ def screen_erase(part = :all) = "\e[#{@screen_erase[part]}J"
406
404
 
407
405
  # Safe current screen.
408
406
  #
@@ -442,22 +440,17 @@ module Terminal
442
440
  # @!group Other ANSI control functions
443
441
  #
444
442
 
443
+ # Repeat last char.
444
+ #
445
+ # @param count [Integer] repeat count
446
+ # @return (see cursor_up)
447
+ def char_repeat(count = 1) = "\e[#{count}b"
448
+
445
449
  # Erase part of line.
446
450
  #
447
451
  # @param part [:to_end, :to_start, :all] line part to erase
448
452
  # @return (see cursor_up)
449
- def line_erase(part = :all)
450
- "\e[#{
451
- case part
452
- when :to_end
453
- # nop
454
- when :to_start
455
- '1'
456
- else # :all
457
- '2'
458
- end
459
- }K"
460
- end
453
+ def line_erase(part = :all) = "\e[#{@line_erase[part]}K"
461
454
 
462
455
  # Set (tab) title.
463
456
  # This is not widely supported; works for
@@ -570,27 +563,6 @@ module Terminal
570
563
  caller(1)
571
564
  )
572
565
  end
573
-
574
- def _color(str)
575
- b, v = /\A(fg|bg|on|ul)?_?#?([[:xdigit:]]{1,6})\z/.match(str)&.captures
576
- if v
577
- return(
578
- case v.size
579
- when 1, 2
580
- "#{@cbase[b]};5;#{v.hex}"
581
- when 3
582
- "#{@cbase[b]};2;#{(v[0] * 2).hex};#{
583
- (v[1] * 2).hex
584
- };#{(v[2] * 2).hex}"
585
- when 6
586
- "#{@cbase[b]};2;#{v[0, 2].hex};#{v[2, 2].hex};#{v[4, 2].hex}"
587
- end
588
- )
589
- end
590
- b, v = /\A(fg|bg|on|ul)?_?([a-z]{3,}[0-9]{0,3})\z/.match(str)&.captures
591
- return unless v
592
- name = NAMED_COLORS[v] and return "#{@cbase[b]};#{name}"
593
- end
594
566
  end
595
567
 
596
568
  @cbase = { 'bg' => '48', 'on' => '48', 'ul' => '58' }
@@ -606,56 +578,6 @@ module Terminal
606
578
 
607
579
  @re_bbcode = /(?:\[((?~[\[\]]))\])/
608
580
 
609
- attr_map = {
610
- '' => 'reset',
611
- '1' => 'bold',
612
- '2' => 'faint',
613
- '3' => 'italic',
614
- '4' => 'underline',
615
- '5' => 'blink',
616
- '6' => 'rapid_blink',
617
- '7' => 'invert',
618
- '8' => 'hide',
619
- '9' => 'strike',
620
- '10' => 'primary_font',
621
- '11' => 'font1',
622
- '12' => 'font2',
623
- '13' => 'font3',
624
- '14' => 'font4',
625
- '15' => 'font5',
626
- '16' => 'font6',
627
- '17' => 'font7',
628
- '18' => 'font8',
629
- '19' => 'font9',
630
- '20' => 'fraktur',
631
- '21' => 'double_underline',
632
- '22' => 'bold_off', # faint_off
633
- '23' => 'italic_off', # fraktur_off
634
- '24' => 'underline_off', # double_underline_off
635
- '25' => 'blink_off', # rapid_blink_off
636
- '26' => 'proportional',
637
- '27' => 'invert_off',
638
- '28' => 'hide_off',
639
- '29' => 'strike_off',
640
- # colors ...
641
- '50' => 'proportional_off',
642
- '51' => 'framed',
643
- '52' => 'encircled',
644
- '53' => 'overlined',
645
- '54' => 'framed_off', # encircled_off
646
- '55' => 'overlined_off',
647
- # ...
648
- '73' => 'superscript',
649
- '74' => 'subscript',
650
- '75' => 'superscript_off', # subscript_off
651
- # special underline
652
- '4:3' => 'curly_underline',
653
- '4:4' => 'dotted_underline',
654
- '4:5' => 'dashed_underline',
655
- '4:0' => 'curly_underline_off' # dotted_underline_off, dashed_underline_off
656
- }.invert
657
- attr_alias = ->(t, s) { attr_map[t] = attr_map[s] }
658
-
659
581
  clr_map = {
660
582
  # foreground
661
583
  '30' => 'black',
@@ -725,6 +647,56 @@ module Terminal
725
647
  clr_alias['fg_default', 'default']
726
648
  clr_alias['bg_default', 'on_default']
727
649
 
650
+ attr_map = {
651
+ '' => 'reset',
652
+ '1' => 'bold',
653
+ '2' => 'faint',
654
+ '3' => 'italic',
655
+ '4' => 'underline',
656
+ '5' => 'blink',
657
+ '6' => 'rapid_blink',
658
+ '7' => 'invert',
659
+ '8' => 'hide',
660
+ '9' => 'strike',
661
+ '10' => 'primary_font',
662
+ '11' => 'font1',
663
+ '12' => 'font2',
664
+ '13' => 'font3',
665
+ '14' => 'font4',
666
+ '15' => 'font5',
667
+ '16' => 'font6',
668
+ '17' => 'font7',
669
+ '18' => 'font8',
670
+ '19' => 'font9',
671
+ '20' => 'fraktur',
672
+ '21' => 'double_underline',
673
+ '22' => 'bold_off', # faint_off
674
+ '23' => 'italic_off', # fraktur_off
675
+ '24' => 'underline_off', # double_underline_off
676
+ '25' => 'blink_off', # rapid_blink_off
677
+ '26' => 'proportional',
678
+ '27' => 'invert_off',
679
+ '28' => 'hide_off',
680
+ '29' => 'strike_off',
681
+ # colors ...
682
+ '50' => 'proportional_off',
683
+ '51' => 'framed',
684
+ '52' => 'encircled',
685
+ '53' => 'overlined',
686
+ '54' => 'framed_off', # encircled_off
687
+ '55' => 'overlined_off',
688
+ # ...
689
+ '73' => 'superscript',
690
+ '74' => 'subscript',
691
+ '75' => 'superscript_off', # subscript_off
692
+ # special underline
693
+ '4:3' => 'curly_underline',
694
+ '4:4' => 'dotted_underline',
695
+ '4:5' => 'dashed_underline',
696
+ '4:0' => 'curly_underline_off' # dotted_underline_off, dashed_underline_off
697
+ }.invert
698
+ attr_alias = ->(t, s) { attr_map[t] = attr_map[s] }
699
+
728
700
  attr_alias['faint_off', 'bold_off']
729
701
  attr_alias['fraktur_off', 'italic_off']
730
702
  attr_alias['double_underline_off', 'underline_off']
@@ -788,7 +760,23 @@ module Terminal
788
760
  attr_map['/bg'] = clr_map['on_default']
789
761
  attr_map['/ul'] = clr_map['ul_default']
790
762
 
791
- @attr_map = Hash.new { _color(_2) }
763
+ @attr_map =
764
+ Hash.new do |_, str|
765
+ b, v = /\A(fg|bg|on|ul)?_?#?([[:xdigit:]]{1,6})\z/.match(str)&.captures
766
+ unless v
767
+ b = /\A(fg|bg|on|ul)?_?([a-z]{3,}[0-9]{0,3})\z/.match(str) or next
768
+ name = NAMED_COLORS[b[2]] or next
769
+ next "#{@cbase[b[1]]};#{name}"
770
+ end
771
+ case v.size
772
+ when 1, 2
773
+ "#{@cbase[b]};5;#{v.hex}"
774
+ when 3
775
+ "#{@cbase[b]};2;#{(v[0] * 2).hex};#{(v[1] * 2).hex};#{(v[2] * 2).hex}"
776
+ when 6
777
+ "#{@cbase[b]};2;#{v[0, 2].hex};#{v[2, 2].hex};#{v[4, 2].hex}"
778
+ end
779
+ end
792
780
  attr_map.merge!(clr_map).keys.sort!.each { @attr_map[_1] = attr_map[_1] }
793
781
  @attr_map.freeze
794
782
 
@@ -796,21 +784,29 @@ module Terminal
796
784
  @attrs_map.default_proc = @attr_map.default_proc
797
785
  @attrs_map.compare_by_identity.freeze
798
786
 
787
+ @screen_erase = { below: nil, above: '1', scrollback: '3' }
788
+ @screen_erase.default = '2'
789
+ @screen_erase.compare_by_identity.freeze
790
+
791
+ @line_erase = { to_end: nil, to_start: '1' }
792
+ @line_erase.default = '2'
793
+ @line_erase.compare_by_identity.freeze
794
+
799
795
  autoload :NAMED_COLORS, "#{__dir__}/ansi/named_colors.rb"
800
796
  private_constant :NAMED_COLORS
801
797
 
802
798
  # @private
803
- RESET = self[:reset].freeze
799
+ RESET = -self[:reset]
804
800
 
805
801
  # @private
806
802
  FULL_RESET = "\ec"
807
803
 
808
804
  # @private
809
- CURSOR_HOME = cursor_pos(nil, nil).freeze
805
+ CURSOR_HOME = -cursor_pos(nil, nil)
810
806
  # @private
811
- CURSOR_FIRST_ROW = cursor_pos(1).freeze
807
+ CURSOR_FIRST_ROW = -cursor_pos(1)
812
808
  # @private
813
- CURSOR_FIRST_COLUMN = cursor_column(1).freeze
809
+ CURSOR_FIRST_COLUMN = -cursor_column(1)
814
810
 
815
811
  # @private
816
812
  CURSOR_SHOW = "\e[?25h"
@@ -828,13 +824,13 @@ module Terminal
828
824
  CURSOR_POS_RESTORE = "\e8"
829
825
 
830
826
  # @private
831
- SCREEN_ERASE = screen_erase.freeze
827
+ SCREEN_ERASE = -screen_erase
832
828
  # @private
833
- SCREEN_ERASE_BELOW = screen_erase(:below).freeze
829
+ SCREEN_ERASE_BELOW = -screen_erase(:below)
834
830
  # @private
835
- SCREEN_ERASE_ABOVE = screen_erase(:above).freeze
831
+ SCREEN_ERASE_ABOVE = -screen_erase(:above)
836
832
  # @private
837
- SCREEN_ERASE_SCROLLBACK = screen_erase(:scrollback).freeze
833
+ SCREEN_ERASE_SCROLLBACK = -screen_erase(:scrollback)
838
834
 
839
835
  # @private
840
836
  SCREEN_SAVE = "\e[?47h"
@@ -843,7 +839,7 @@ module Terminal
843
839
 
844
840
  # @private
845
841
  # @comment at least Kitty requires CURSOR_HOME too
846
- SCREEN_ALTERNATE = "\e[?1049h#{CURSOR_HOME}".freeze
842
+ SCREEN_ALTERNATE = -"\e[?1049h#{CURSOR_HOME}"
847
843
  # @private
848
844
  SCREEN_ALTERNATE_OFF = "\e[?1049l"
849
845
 
@@ -853,21 +849,19 @@ module Terminal
853
849
  SCREEN_REVERSE_MODE_OFF = "\e[?5l"
854
850
 
855
851
  # @private
856
- LINE_ERASE = line_erase.freeze
852
+ LINE_ERASE = -line_erase
857
853
  # @private
858
- LINE_ERASE_TO_END = line_erase(:to_end).freeze
854
+ LINE_ERASE_TO_END = -line_erase(:to_end)
859
855
  # @private
860
- LINE_ERASE_TO_START = line_erase(:to_start).freeze
856
+ LINE_ERASE_TO_START = -line_erase(:to_start)
861
857
  # @private
862
- LINE_ERASE_PREV = "#{cursor_prev_line(nil)}#{LINE_ERASE}".freeze
858
+ LINE_ERASE_PREV = -"#{cursor_prev_line(nil)}#{LINE_ERASE}"
863
859
 
864
860
  # @comment seems not widely supported:
865
861
  # doubled: def cursor_column(column = 1) = "\e[#{column}`"
866
862
  # doubled: def cursor_row(row = 1) = "\e[#{row}d"
867
863
  # doubled: def cursor_pos(row, col) = "\e[#{row};#{col}f"
868
864
  #
869
- # def cursor_column_rel(columns = 1) = "\e[#{columns}a"
870
- # def cursor_row_rel(rows = 1) = "\e[#{rows}e"
871
865
  # def cursor_tab(count = 1) = "\e[#{column}I"
872
866
  # def cursor_reverse_tab(count = 1) = "\e[#{count}Z"
873
867
  #
@@ -878,20 +872,14 @@ module Terminal
878
872
  # def chars_delete(count = 1) = "\e[#{count}P"
879
873
  # def chars_erase(count = 1) = "\e[#{count}X"
880
874
  #
881
- # def chars_repeat_last(count = 1) = "\e[#{count}b"
882
- #
883
- # def notify(title) = "\e]9;#{title}\a"
884
- #
885
875
  # def set_scroll_region(top = nil, bottom = nil) = "\e[#{top};#{bottom}r"
886
876
 
887
877
  # @comment other:
888
878
  # "\eE" same as "\r\n"
889
- # "\eD" same as "\n" but preserves X coord
879
+ # "\eD" same as "\n" but preserves column
890
880
  # "\eM" reverse "\n"
891
881
  # "\e[6n" report Cursor Position as "ESC \[ row ; col R"
892
882
  # "\e[21t report window’s title as ESC ] l title ESC \"
893
- # "\e[?1004h" report focus lost "\e[O" and focus get "\e[I"
894
- # (disable by "\e[?1004l")
895
883
 
896
884
  # @comment TODO:
897
885
  # https://sw.kovidgoyal.net/kitty/desktop-notifications
@@ -2,9 +2,85 @@
2
2
 
3
3
  module Terminal
4
4
  #
5
- # Key event reported from {read_key_event}.
5
+ # Key event reported from {read_key_event} and {on_key_event}.
6
6
  #
7
7
  class KeyEvent
8
+ # ANSI code sequence received from standard input.
9
+ # This can be a simple value like `"a"`or more complex input like
10
+ # `"\e[24;6~"` (for Shift+Ctrl+F12).
11
+ #
12
+ # @return [String] received ANSI code sequence
13
+ attr_reader :raw
14
+
15
+ # Pressed key without any modifiers.
16
+ # This can be a string for simple keys like `"a"` or a Symbol like `:F12`
17
+ # (see {.key_names}).
18
+ # @return [String, Symbol] key without modifiers
19
+ attr_reader :key
20
+
21
+ # Modifier key code. This represents the encoded key modifier like `Shift`
22
+ # or `Alt`.
23
+ #
24
+ # @return [Integer] modifier key code
25
+ attr_reader :modifier
26
+
27
+ # Mouse event position.
28
+ #
29
+ # @return [[Integer,Integer]] row and column
30
+ # @return nil when no coordinates are assigned
31
+ attr_reader :position
32
+
33
+ # Name of the key event.
34
+ # This can be a simple name like `"a"` or `"Shift+Ctrl+F12"` for combined
35
+ # keys.
36
+ #
37
+ # @return [String] key name
38
+ attr_reader :name
39
+
40
+ # @attribute [r] modifier?
41
+ # @return [true, false] whether a key modifier was pressed
42
+ def modifier? = @modifier != 0
43
+
44
+ # @attribute [r] simple?
45
+ # @return [true, false] whether a simple key was pressed
46
+ def simple? = @raw == @name
47
+
48
+ # All pressed keys.
49
+ # This is composed by all {modifier} and the {key}.
50
+ #
51
+ # @return [Array<Symbol, String>] all pressed keys
52
+ def to_a = @ary.dup
53
+
54
+ # @private
55
+ def to_ary = simple? ? [@raw] : [@raw, @name]
56
+
57
+ # @private
58
+ def to_s = @name.dup
59
+
60
+ # @private
61
+ def inspect = "<#{self.class.name} #{to_ary.map(&:inspect).join(' ')}>"
62
+
63
+ # @private
64
+ def freeze
65
+ @raw.freeze
66
+ @key.freeze
67
+ @position.freeze
68
+ @name.freeze
69
+ super
70
+ end
71
+
72
+ private
73
+
74
+ def initialize(raw, key, modifier, position)
75
+ @raw = raw
76
+ @key = key
77
+ @modifier = modifier
78
+ @position = position
79
+ @ary = MODIFIERS.filter_map { |b, n| n if modifier.allbits?(b) }
80
+ @ary << key if key
81
+ @name = @ary.join('+') # .encode(Encoding::UTF_8)
82
+ end
83
+
8
84
  class << self
9
85
  # @attribute [w] caching
10
86
  # @return [true, false] whether {KeyEvent}s should be cached
@@ -12,24 +88,33 @@ module Terminal
12
88
 
13
89
  # @attribute [w] caching
14
90
  def caching=(value)
15
- if value
16
- @cache ||= {}
17
- else
18
- @cache = nil
19
- end
91
+ value ? @cache ||= {} : @cache = nil
20
92
  end
21
93
 
22
- # Translate a keyboard input string into a related KeyEvent
94
+ # @attribute [r] key_names
95
+ # @return [Array<Symbol>] list of all avilable key names (see {key})
96
+ def key_names
97
+ (
98
+ @names ||=
99
+ (@csiu.values + @csi.values + @ss3.values)
100
+ .uniq
101
+ .keep_if { Symbol === _1 }
102
+ .sort!
103
+ ).dup
104
+ end
105
+
106
+ # Translate a ANSI input code sequence into a related KeyEvent.
23
107
  #
24
108
  # @param raw [String] keyboard input
25
109
  # @return [KeyEvent] related key event
26
110
  def [](raw)
111
+ cached = @cache[raw] and return cached if @cache
27
112
  return new(raw, *@single_key[raw.ord]) if raw.size == 1
28
113
  return unknown(raw) if raw[0] != "\e"
29
114
  return esc1(raw, raw[1]) if raw.size == 2 # ESC ?
30
115
  case raw[1]
31
- when "\e" # ESC ESC ...
32
- return esc_esc(raw)
116
+ when "\e"
117
+ return esc_esc(raw) # ESC ESC ...
33
118
  when 'O'
34
119
  return new(raw, *@ss3[raw[2].ord]) if raw.size == 3 # ESC O ?
35
120
  when '['
@@ -37,15 +122,17 @@ module Terminal
37
122
  if raw.size == 6 && raw[2] == 'M' # ESC [ M b c r
38
123
  return mouse_vt200(raw)
39
124
  end
40
- return csi1(raw) if raw.start_with?("\e[1;") # ESC [ 1 ; ...
125
+ if raw.size > 4 && raw[2] == '1' && raw[3] == ';'
126
+ return csi(raw) # ESC [ 1 ; ...
127
+ end
41
128
  case raw[-1]
42
- when '~' # ESC [ ... ~
43
- return legacy(raw)
44
- when 'u' # ESC [ ... u
45
- return csi_u(raw)
46
- when 'M' # ESC [ ... M
47
- return raw[2] == '<' ? mouse_sgr(raw) : mouse_urxvt(raw)
48
- when 'm' # ESC [ ... m
129
+ when '~'
130
+ return legacy(raw) # ESC [ ... ~
131
+ when 'u'
132
+ return csi_u(raw) # ESC [ ... u
133
+ when 'M', 'm'
134
+ # ESC [ ... M
135
+ # ESC [ ... m
49
136
  return mouse_sgr(raw) if raw[2] == '<'
50
137
  end
51
138
  end
@@ -53,40 +140,29 @@ module Terminal
53
140
  end
54
141
 
55
142
  # @private
56
- def new(raw, key = raw, modifier = 0, extra = nil)
57
- @cache ? (@cache[raw] ||= super.freeze) : super.freeze
143
+ def new(raw, key = raw, modifier = 0, position = nil)
144
+ (@cache && position.nil? ? @cache[raw] ||= super : super).freeze
58
145
  end
59
146
 
60
147
  private
61
148
 
62
149
  def unknown(raw) = new(raw, nil)
63
150
 
64
- def csi_u(raw)
65
- # ESC [ <code> u
66
- # ESC [ <code> ; <modifier> u
67
- return unknown(raw) if raw.size < 4
68
- idx = raw.index(';')
69
- code = raw[2..(idx ? idx - 1 : -2)].to_i
70
- new(
71
- raw,
72
- @csiu[code] || code.chr(Encoding::UTF_8),
73
- idx ? [raw[(idx + 1)..-2].to_i - 1, 0].max : 0
74
- )
151
+ def esc1(raw, char)
152
+ # ESC ?
153
+ key, modifier = @esc1[char.ord]
154
+ new(raw, key || char, modifier || 2)
75
155
  end
76
156
 
77
- def legacy(raw)
78
- # ESC [ <code> ~
79
- # ESC [ <code> ; <modifier> ~
80
- return unknown(raw) if raw.size < 4
81
- idx = raw.index(';')
82
- new(
83
- raw,
84
- @csi[raw[2..(idx ? idx - 1 : -2)].to_i],
85
- idx ? [raw[(idx + 1)..-2].to_i - 1, 0].max : 0
86
- )
157
+ def esc_esc(raw)
158
+ # ESC ESC ?
159
+ # ESC ESC ...
160
+ return esc1(raw, raw[2]) if raw.size == 3
161
+ ret = self[raw[1..]]
162
+ new(raw, ret.key, ret.modifier | 2)
87
163
  end
88
164
 
89
- def csi1(raw)
165
+ def csi(raw)
90
166
  # ESC [ 1 ; [~ABCDEFHPQRS]
91
167
  # ESC [ 1 ; <modifier> [~ABCDEFHPQRS]
92
168
  return unknown(raw) if raw.size < 5
@@ -97,19 +173,33 @@ module Terminal
97
173
  new(raw, key, modifier)
98
174
  end
99
175
 
100
- def esc_esc(raw)
101
- return esc1(raw, raw[2]) if raw.size == 3 # ESC ESC ?
102
- ret = self[raw[1..]]
103
- new(raw, ret.key, ret.modifier | 2) # ESC ESC ...
176
+ def legacy(raw)
177
+ # ESC [ <code> ~
178
+ # ESC [ <code> ; <modifier> ~
179
+ return unknown(raw) if raw.size < 4
180
+ idx = raw.index(';')
181
+ new(
182
+ raw,
183
+ @csi[raw[2..(idx ? idx - 1 : -2)].to_i],
184
+ idx ? [raw[(idx + 1)..-2].to_i - 1, 0].max : 0
185
+ )
104
186
  end
105
187
 
106
- def esc1(raw, char)
107
- key, modifier = @esc1[char.ord]
108
- new(raw, key || char, modifier || 2)
188
+ def csi_u(raw)
189
+ # ESC [ <code> u
190
+ # ESC [ <code> ; <modifier> u
191
+ return unknown(raw) if raw.size < 4
192
+ idx = raw.index(';')
193
+ code = raw[2..(idx ? idx - 1 : -2)].to_i
194
+ new(
195
+ raw,
196
+ @csiu[code] || code.chr(Encoding::UTF_8),
197
+ idx ? [raw[(idx + 1)..-2].to_i - 1, 0].max : 0
198
+ )
109
199
  end
110
200
 
111
201
  def mouse_vt200(raw)
112
- # ESC [ M b c r
202
+ # ESC [ M <btn> <col> <row>
113
203
  mouse_event(raw, *raw[3..].chars.map! { _1.ord - 32 })
114
204
  end
115
205
 
@@ -118,117 +208,40 @@ module Terminal
118
208
  # ESC [ < <code> ; <col> ; <row> m
119
209
  return unknown(raw) if raw.size < 8
120
210
  bcr = raw[3..-2].split(';', 3).map!(&:to_i)
121
- bcr.size == 3 ? mouse_event(raw, *bcr) : unknown(raw)
211
+ bcr.size == 3 ? mouse_event(raw, *bcr, raw[-1] == 'm') : unknown(raw)
122
212
  end
123
213
 
124
- def mouse_urxvt(raw)
125
- # ESC [ <code> ; <col> ; <row> M
126
- return unknown(raw) if raw.size < 8
127
- bcr = raw[2..-2].split(';', 3).map!(&:to_i)
128
- bcr.size == 3 ? mouse_event(raw, *bcr) : unknown(raw)
129
- end
214
+ # def mouse_urxvt(raw)
215
+ # # ESC [ <code> ; <col> ; <row> M
216
+ # unknown(raw) # currently not supported
217
+ # end
130
218
 
131
- def mouse_event(raw, btn, col, row)
219
+ def mouse_event(raw, btn, col, row, up = false)
132
220
  return unknown(raw) if btn < 0 || col < 1 || row < 1
133
- key, btn = kind_of_mouse_event(btn)
221
+ key = (btn & 1).nonzero? ? 1 : 0
222
+ key += 2 if btn.allbits?(2)
134
223
  modifier = btn.allbits?(4) ? 1 : 0
135
224
  modifier += 2 if btn.allbits?(8)
136
225
  modifier += 4 if btn.allbits?(16)
137
- new(raw, key, modifier, [col, row])
226
+ modifier += 256 if moved = btn.allbits?(32)
227
+ key += 4 if btn.allbits?(64)
228
+ key += 8 if btn.allbits?(128)
229
+ new(raw, mouse_name(key, up, moved), modifier, [row, col])
138
230
  end
139
231
 
140
- def mouse_event_key(btn)
141
- kind = btn & 3
142
- key = (btn >= 64 ? @mouse_wheel_kind : @mouse_kind)[kind]
143
- # TODO: what about btn >= 128 for more mouse wheel events?
144
- [btn - kind, key]
232
+ def mouse_name(key, up, moved)
233
+ return up ? :MBLeftUp : :MBLeft if key == 0
234
+ return up ? :MBMiddleUp : :MBMiddle if key == 1
235
+ return up ? :MBRightUp : :MBRight if key == 2
236
+ return moved ? :Mouse : :MBUp if key == 3
237
+ return :MWUp if key == 4
238
+ return :MWDown if key == 5
239
+ return :MWLeft if key == 6
240
+ return :MWRight if key == 7
241
+ :"MB_#{key - 8}#{'Up' if up}"
145
242
  end
146
243
  end
147
244
 
148
- # Event string received from standard input.
149
- # This can be a simple value like `"a"`or more complex input like
150
- # `"\e[24;6~"` (for Shift+Ctrl+F12).
151
- #
152
- # @return [String] received event string
153
- attr_reader :raw
154
-
155
- # Pressed key without any modifiers.
156
- # This can be a string for simple keys like `"a"` or a Symbol like `:F12`.
157
- # @return [String, Symbol] key without modifiers
158
- attr_reader :key
159
-
160
- # Modifier key code. This represents the encoded key modifier like `Shift`
161
- # or `Alt`.
162
- #
163
- # @return [Integer] modifier key code
164
- attr_reader :modifier
165
-
166
- # @comment for mouse events
167
- # @private
168
- attr_reader :extra
169
-
170
- # Name of the key event.
171
- # This can be a simple name like `"a"` or `"Shift+Ctrl+F12"` for combined
172
- # keys.
173
- #
174
- # @return [String] key name
175
- attr_reader :name
176
-
177
- # @attribute [r] modifier?
178
- # @return [true, false] whether a key modifier was pressed
179
- def modifier? = @modifier != 0
180
-
181
- # @attribute [r] simple?
182
- # @return [true, false] whether a simple key was pressed
183
- def simple? = @raw == @name
184
-
185
- # All pressed keys.
186
- # This is composed by all {modifier} and the {key}.
187
- #
188
- # @return [Array<Symbol, String>] all pressed keys
189
- def to_a = @ary.dup
190
-
191
- # @private
192
- def to_ary = simple? ? [@raw] : [@raw, @name]
193
-
194
- # @private
195
- def to_s = @name.dup
196
-
197
- # @private
198
- def inspect = "<#{self.class.name} #{to_ary.map(&:inspect).join(' ')}>"
199
-
200
- # @private
201
- def freeze
202
- @raw.freeze
203
- @key.freeze
204
- @extra.freeze
205
- @name.freeze
206
- super
207
- end
208
-
209
- private
210
-
211
- def initialize(raw, key, modifier, extra)
212
- @raw = raw
213
- @key = key
214
- @modifier = modifier
215
- @extra = extra
216
- @ary = MODIFIERS.filter_map { |b, n| n if modifier.allbits?(b) }
217
- @ary << key if key
218
- @name = @ary.join('+') # .encode(Encoding::UTF_8)
219
- end
220
-
221
- @mouse_kind = %i[
222
- MButton1
223
- MButton2
224
- MButton3
225
- MButtonUp
226
- MButton4
227
- MButton5
228
- ].freeze
229
-
230
- @mouse_wheel_kind = %i[MWheelDown MWheelUp MWheelRight MWheelLeft].freeze
231
-
232
245
  @csiu = {
233
246
  0x02 => :Ins,
234
247
  0x09 => :Tab,
@@ -357,6 +370,8 @@ module Terminal
357
370
  0x44 => :Left, # D
358
371
  0x46 => :End, # F
359
372
  0x48 => :Home, # H
373
+ 0x49 => :Focus, # I
374
+ 0x4f => :UnFocus, # O
360
375
  0x50 => :F1, # P
361
376
  0x51 => :F2, # Q
362
377
  0x52 => :F3, # R
@@ -421,7 +436,8 @@ module Terminal
421
436
  16 => :Hyper,
422
437
  32 => :Meta,
423
438
  64 => :Caps,
424
- 128 => :Num
439
+ 128 => :Num,
440
+ 256 => :Move # mouse
425
441
  }.freeze
426
442
  private_constant :MODIFIERS
427
443
  end
@@ -16,7 +16,7 @@ module Terminal
16
16
  # @return [:dumb]
17
17
  # for non-interactive input (pipes etc.)
18
18
  # @return [:error]
19
- # when input device is closed
19
+ # when input device is not avail (closed)
20
20
  def input_mode
21
21
  @input_mode ||= find_input_mode
22
22
  end
@@ -34,6 +34,10 @@ module Terminal
34
34
  # @return [[String, String]] key code and key name in `:both` mode
35
35
  # @return [nil] in error case
36
36
  def read_key(mode: :named)
37
+ warn(
38
+ 'Terminal.read_key is deprecaded; use Terminal.read_key_event instead.',
39
+ uplevel: 1
40
+ )
37
41
  event = read_key_event or return
38
42
  return event.raw if mode == :raw
39
43
  key, name = event
@@ -48,25 +52,109 @@ module Terminal
48
52
  case input_mode
49
53
  when :dumb
50
54
  raw = read_dumb or return
51
- opts = DUMB_KEYS[raw.ord] if raw.size == 1
55
+ opts = @dumb_keys[raw.ord] if raw.size == 1
52
56
  KeyEvent.new(raw, *opts)
53
57
  when :csi_u, :legacy
54
- # raw = with_mouse ? read_tty_with_mouse : read_tty
55
- KeyEvent[raw] if (raw = read_tty)
58
+ raw = read_tty or return
59
+ KeyEvent[raw]
60
+ end
61
+ end
62
+
63
+ # Event loop for key and mouse events.
64
+ #
65
+ # @param mouse [true, false]
66
+ # whether mouse buttons should be reported
67
+ # @param mouse_move [true, false]
68
+ # whether mouse movement should be reported
69
+ # @param focus [true, false]
70
+ # whether focus/unfocus of terminal window should be reported
71
+ # @yieldparam event [KeyEvent]
72
+ # next event
73
+ # @yieldreturn [true, false]
74
+ # whether the loop should be continued
75
+ # @return [true]
76
+ # when the loop was started
77
+ # @return [false]
78
+ # when the current input device is not available
79
+ # @return [nil]
80
+ # when no block was given
81
+ def on_key_event(mouse: false, mouse_move: false, focus: false, &block)
82
+ return unless block
83
+ case input_mode
84
+ when :dumb
85
+ on_bumb_key_event(&block)
86
+ true
87
+ when :csi_u, :legacy
88
+ on_tty_key_event(mouse, mouse_move, focus, &block)
89
+ true
90
+ else
91
+ false
56
92
  end
57
93
  end
58
94
 
59
95
  private
60
96
 
97
+ def on_tty_key_event(mouse, mouse_move, focus)
98
+ # highlight: '1001'
99
+ # drag: '1002'
100
+ # move: '1003'
101
+ # focus: '1004' - lost: "\e[O", get: "\e[I"
102
+ # ext: '1005'
103
+ # sgr: '1006'
104
+ # urxvt: '1015'
105
+ # pixel: '1016'
106
+ opts =
107
+ if mouse || mouse_move || focus
108
+ opts = mouse ? +"\e[?1006;1000" : +"\e[?1006"
109
+ opts << ';1003' if mouse_move
110
+ opts << ';1004' if focus
111
+ raw_write("#{opts}h") ? "#{opts}l" : nil
112
+ end
113
+ STDIN.noecho do |stdin|
114
+ while (raw = stdin.getch)
115
+ if raw != "\e"
116
+ yield(KeyEvent[raw]) ? next : break
117
+ end
118
+ lesci = 0
119
+ while (nc = stdin.read_nonblock(1, exception: false))
120
+ break unless String === nc
121
+ lesci = raw.size if nc == "\e"
122
+ raw << nc
123
+ end
124
+ if lesci < 2
125
+ yield(KeyEvent[raw]) ? next : break
126
+ end
127
+ break unless raw[1..].split("\e").all? { yield(KeyEvent["\e#{_1}"]) }
128
+ end
129
+ end
130
+ rescue Interrupt
131
+ # nop
132
+ rescue IOError, SystemCallError
133
+ @input_mode = :error
134
+ ensure
135
+ raw_write(opts) if opts
136
+ end
137
+
138
+ def on_bumb_key_event
139
+ while (raw = read_dumb)
140
+ opts = @dumb_keys[raw.ord] if raw.size == 1
141
+ return unless yield(KeyEvent.new(raw, *opts))
142
+ end
143
+ end
144
+
61
145
  def find_input_mode
62
- # order is important!
63
146
  im = ENV['INPUT_MODE']
64
- return :dumb if im == 'dumb'
65
147
  return :legacy if im == 'legacy'
66
- return :dumb unless STDIN.tty?
67
- if ansi? && raw_write("\e[>1u\e[?u\e[c") && csi_u?
68
- at_exit { raw_write("\e[<u") }
69
- return :csi_u
148
+ return :dumb if im == 'dumb' || !STDIN.tty?
149
+ STDIN.noecho do |stdin|
150
+ if raw_write("\e[>1u\e[?u\e[c")
151
+ inp = +''
152
+ inp << stdin.getch until inp.rindex('c')
153
+ if inp.include?("\e[?1u")
154
+ at_exit { raw_write("\e[<u") }
155
+ return :csi_u
156
+ end
157
+ end
70
158
  end
71
159
  :legacy
72
160
  rescue Interrupt
@@ -75,12 +163,6 @@ module Terminal
75
163
  :error
76
164
  end
77
165
 
78
- def csi_u?
79
- inp = +''
80
- STDIN.raw { inp << _1.getch until inp.rindex('c') }
81
- inp.include?("\e[?1u")
82
- end
83
-
84
166
  def read_dumb
85
167
  STDIN.getc
86
168
  rescue Interrupt
@@ -90,45 +172,31 @@ module Terminal
90
172
  nil
91
173
  end
92
174
 
93
- def read_tty_with_mouse(each_move: false)
94
- # highlight: '1001'
95
- # drag: '1002'
96
- # move: '1003'
97
- # ext: '1005'
98
- # sgr: '1006'
99
- # urxvt: '1015'
100
- # pixel: '1016'
101
- opts = each_move ? '1000;1003;1006;1015' : '1000;1006;1015'
102
- opts = raw_write("\e[?#{opts}h") ? "\e[?#{opts}l" : nil
103
- read_tty
104
- ensure
105
- raw_write(opts) if opts
106
- end
107
-
108
175
  def read_tty
109
- if (key = STDIN.getch) == "\e"
110
- while (nc = STDIN.read_nonblock(1, exception: false))
111
- String === nc ? key << nc : break
176
+ STDIN.noecho do |stdin|
177
+ if (key = stdin.getch) == "\e"
178
+ while (nc = stdin.read_nonblock(1, exception: false))
179
+ String === nc ? key << nc : break
180
+ end
112
181
  end
182
+ key
113
183
  end
114
- key
115
184
  rescue Interrupt
116
185
  nil
117
186
  rescue IOError, SystemCallError
118
187
  @input_mode = :error
119
188
  nil
120
189
  end
121
-
122
- DUMB_KEYS = {
123
- 0x05 => ['c', 4],
124
- 0x08 => :Back,
125
- 0x09 => :Tab,
126
- 0x0a => :Enter,
127
- 0x0d => :Return,
128
- 0x1b => :Esc
129
- }.compare_by_identity.freeze
130
- private_constant :DUMB_KEYS
131
190
  end
132
191
 
192
+ @dumb_keys = {
193
+ 0x05 => ['c', 4],
194
+ 0x08 => :Back,
195
+ 0x09 => :Tab,
196
+ 0x0a => :Enter,
197
+ 0x0d => :Return,
198
+ 0x1b => :Esc
199
+ }.compare_by_identity.freeze
200
+
133
201
  autoload :KeyEvent, "#{__dir__}/input/key_event.rb"
134
202
  end
@@ -116,12 +116,8 @@ module Terminal
116
116
  end
117
117
 
118
118
  def initialize(enum)
119
- @enum =
120
- if enum.respond_to?(:enum_for)
121
- enum.enum_for(:each)
122
- else
123
- Enumerator.new { |y| enum.each { y << _1 } }
124
- end
119
+ return @enum = enum.enum_for(:each) if enum.respond_to?(:enum_for)
120
+ @enum = Enumerator.new { |y| enum.each { y << _1 } }
125
121
  end
126
122
  end
127
123
  end
data/lib/terminal/text.rb CHANGED
@@ -24,14 +24,14 @@ module Terminal
24
24
  # some are ambiguous. The function uses {ambiguous_char_width} for each of
25
25
  # these characters.
26
26
  #
27
- # @param object [#to_s] str to process
27
+ # @param str [#to_s] str to process
28
28
  # @param bbcode [true|false] whether to interpret embedded BBCode
29
29
  # @return [Integer] display width
30
30
  def width(str, bbcode: true)
31
31
  return 0 if (str = bbcode ? Ansi.unbbcode(str) : str.to_s).empty?
32
32
  str = str.encode(@encoding) if str.encoding != @encoding
33
33
  width = 0
34
- str.scan(WIDTH_SCANNER) do |sp, gc|
34
+ str.scan(@scan_width) do |sp, gc|
35
35
  next width += char_width(gc) if gc
36
36
  width += 1 if sp
37
37
  end
@@ -343,7 +343,7 @@ module Terminal
343
343
 
344
344
  txt = txt.encode(@encoding) if txt.encoding != @encoding
345
345
 
346
- txt.scan(SCAN_EXPR) do |nl, csi, osc, space, gc|
346
+ txt.scan(@scan_snippet) do |nl, csi, osc, space, gc|
347
347
  if gc
348
348
  next last.add(gc, char_width(gc)) if last.is_a?(word_class)
349
349
  next ret << (last = word_class.new(gc, char_width(gc)))
@@ -441,13 +441,10 @@ module Terminal
441
441
  end
442
442
  end
443
443
 
444
- private_constant :Osc, :CsiEnd, :Csi, :Word, :WordEx
445
-
446
444
  @ambiguous_char_width = 1
447
- @encoding = Encoding::UTF_8
448
- @empty = String.new(encoding: @encoding).freeze
445
+ @empty = String.new(encoding: @encoding = Encoding::UTF_8).freeze
449
446
 
450
- SCAN_EXPR =
447
+ @scan_snippet =
451
448
  /\G(?:
452
449
  (\r?\n)
453
450
  | (\e\[[\d;:?]*[ABCDEFGHJKSTfhilmnsu])
@@ -456,14 +453,13 @@ module Terminal
456
453
  | (\X)
457
454
  )/x
458
455
 
459
- WIDTH_SCANNER =
456
+ @scan_width =
460
457
  /\G(?:
461
458
  (?:\e\[[\d;:?]*[ABCDEFGHJKSTfhilmnsu])
462
459
  | (?:\e\]\d+(?:;[^\a\e]+)*(?:\a|\e\\))
463
460
  | (\s+)
464
461
  | (\X)
465
462
  )/x
466
- private_constant :SCAN_EXPR, :WIDTH_SCANNER
467
463
 
468
464
  @ctrlchar_width = {
469
465
  0x00 => 0,
@@ -501,6 +497,7 @@ module Terminal
501
497
  }.compare_by_identity.freeze
502
498
 
503
499
  autoload :CharWidth, "#{__dir__}/text/char_width.rb"
504
- private_constant :CharWidth
500
+
501
+ private_constant :Osc, :Csi, :CsiEnd, :Word, :WordEx, :CharWidth
505
502
  end
506
503
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Terminal
4
4
  # The version number of the gem.
5
- VERSION = '0.15.0'
5
+ VERSION = '0.16.0'
6
6
  end
data/lib/terminal.rb CHANGED
@@ -10,7 +10,7 @@ require_relative 'terminal/input'
10
10
  # It automagically detects whether your terminal supports ANSI features, like
11
11
  # coloring (see {colors}) or the
12
12
  # [CSIu protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol) support
13
- # (see {read_key_event} and {input_mode}).
13
+ # (see {read_key_event}, {on_key_event} and {input_mode}).
14
14
  # It calculates the display width for Unicode chars (see {Text.width}) and help
15
15
  # you to display text with word-wise line breaks (see {Text.each_line}).
16
16
  #
@@ -19,8 +19,8 @@ module Terminal
19
19
  # Return true if the current terminal supports ANSI control codes.
20
20
  # When the terminal does not support it, {colors} will return 2 (two) and
21
21
  # all output methods ({<<}, {print}, {puts}) will not forward ANSI control
22
- # codes to the terminal, {read_key_event} will not support extended key
23
- # codes.
22
+ # codes to the terminal, {read_key_event} and {on_key_event} will not
23
+ # support extended key codes and/or mouse events.
24
24
  #
25
25
  # @attribute [r] ansi?
26
26
  # @return [Boolean] whether ANSI control codes are supported
@@ -290,7 +290,7 @@ module Terminal
290
290
 
291
291
  # @private
292
292
  def raw_write(str)
293
- @out&.write(str)
293
+ @out&.syswrite(str)
294
294
  rescue IOError
295
295
  @out = nil
296
296
  end
data/terminal_rb.gemspec CHANGED
@@ -5,13 +5,15 @@ require_relative 'lib/terminal/version'
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = 'terminal_rb'
7
7
  spec.version = Terminal::VERSION
8
- spec.summary =
9
- 'Fast terminal access with ANSI, CSIu, BBCode, word-wise line break support.'
8
+ spec.summary = <<~SUMMARY.tr("\n", ' ')
9
+ Fast terminal access with ANSI, CSIu, mouse eventing, BBCode, word-wise
10
+ line break support and much more.
11
+ SUMMARY
10
12
  spec.description = <<~DESCRIPTION.tr("\n", ' ')
11
13
  Terminal.rb supports you with input and output on your terminal.
12
14
  Simple BBCode-like markup for attributes and coloring, word-wise line
13
- breaks, and correct special key recognition enable you to implement your
14
- CLI app quickly and easily.
15
+ breaks, correct special key recognition and mouse event reporting enable
16
+ you to implement your CLI app quickly and easily.
15
17
  DESCRIPTION
16
18
 
17
19
  spec.author = 'Mike Blumtritt'
@@ -25,9 +27,8 @@ Gem::Specification.new do |spec|
25
27
 
26
28
  spec.required_ruby_version = '> 3.0'
27
29
 
28
- spec.files = Dir['lib/**/*.rb']
29
- spec.files += Dir['examples/*.rb']
30
- spec.files += %w[terminal_rb.gemspec .yardopts]
30
+ spec.files = Dir['lib/**/*.rb'] + Dir['examples/*.rb']
31
+ spec.files << 'terminal_rb.gemspec' << '.yardopts'
31
32
  spec.executables = %w[bbcode]
32
33
  spec.extra_rdoc_files = %w[README.md]
33
34
  end
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.15.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Blumtritt
@@ -10,8 +10,9 @@ cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: 'Terminal.rb supports you with input and output on your terminal. Simple
13
- BBCode-like markup for attributes and coloring, word-wise line breaks, and correct
14
- special key recognition enable you to implement your CLI app quickly and easily. '
13
+ BBCode-like markup for attributes and coloring, word-wise line breaks, correct special
14
+ key recognition and mouse event reporting enable you to implement your CLI app quickly
15
+ and easily. '
15
16
  executables:
16
17
  - bbcode
17
18
  extensions: []
@@ -68,5 +69,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
69
  requirements: []
69
70
  rubygems_version: 3.7.2
70
71
  specification_version: 4
71
- summary: Fast terminal access with ANSI, CSIu, BBCode, word-wise line break support.
72
+ summary: Fast terminal access with ANSI, CSIu, mouse eventing, BBCode, word-wise line
73
+ break support and much more.
72
74
  test_files: []