reline 0.2.5 → 0.2.6

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: 1da0f8ec80f16c720589413fbc14c02ac199dcfc0b0344270a61edbad4aec966
4
- data.tar.gz: d214e02ad3500323e98d97d57677b7bbc94ea4c09df0b529d5b21ea66374a6a8
3
+ metadata.gz: fc0f6fb7c331a37907554b292249a75b218646aa14298586790cc09d6c174894
4
+ data.tar.gz: 79fd7b502a7ab601f6642964bb0987dae5d0a62bc8cfda016d8806c8b76bb723
5
5
  SHA512:
6
- metadata.gz: 0d218a92032ad8b02218e44bda7fe7d7404845f6cc9cceb9aade01d3c49bc33a0396b02857de417ecee59b5d65c0cb787eecec8dea60180ae3b5457df6c89ebd
7
- data.tar.gz: 12c4b8d00608e1f9f16a1c5e530ae86c7530f39ddb1c0f6100b1d4ae87a6dbd53def15d9f387941062aafdb57360fd499dc5850a5bd60d07776daf60e057acfe
6
+ metadata.gz: 2b4300db4d7bef4ab3ddc8f45db07e20e37b371d89e7a21f343dd3c7096209d0c19028def435d584c7bc1f0a9f18d0d6c783b47d28967eb3d34e4d0eaa613490
7
+ data.tar.gz: '081a683b1980b9c8d27822e62dcdc776024cc408e3f93b0c999f8da2c6b2fef92c4eded142f7bee8f95fcd7a18fc8431a87f2cd19d1c1064fa677f7de606dcf0'
data/README.md CHANGED
@@ -8,6 +8,52 @@ This is a screen capture of *IRB improved by Reline*.
8
8
 
9
9
  Reline is compatible with the API of Ruby's stdlib 'readline', GNU Readline and Editline by pure Ruby implementation.
10
10
 
11
+ ## Usage
12
+
13
+ ### Single line editing mode
14
+
15
+ It's compatible with the readline standard library.
16
+
17
+ See [the document of readline stdlib](https://ruby-doc.org/stdlib/libdoc/readline/rdoc/Readline.html) or [bin/example](https://github.com/ruby/reline/blob/master/bin/example).
18
+
19
+ ### Multi-line editing mode
20
+
21
+ ```ruby
22
+ require "reline"
23
+
24
+ prompt = 'prompt> '
25
+ use_history = true
26
+
27
+ begin
28
+ while true
29
+ text = Reline.readmultiline(prompt, use_history) do |multiline_input|
30
+ # Accept the input until `end` is entered
31
+ multiline_input.split.last == "end"
32
+ end
33
+
34
+ puts 'You entered:'
35
+ puts text
36
+ end
37
+ # If you want to exit, type Ctrl-C
38
+ rescue Interrupt
39
+ puts '^C'
40
+ exit 0
41
+ end
42
+ ```
43
+
44
+ ```bash
45
+ $ ruby example.rb
46
+ prompt> aaa
47
+ prompt> bbb
48
+ prompt> end
49
+ You entered:
50
+ aaa
51
+ bbb
52
+ end
53
+ ```
54
+
55
+ See also: [test/reline/yamatanooroti/multiline_repl](https://github.com/ruby/reline/blob/master/test/reline/yamatanooroti/multiline_repl)
56
+
11
57
  ## License
12
58
 
13
59
  The gem is available as open source under the terms of the [Ruby License](https://www.ruby-lang.org/en/about/license.txt).
data/lib/reline.rb CHANGED
@@ -231,9 +231,7 @@ module Reline
231
231
  unless config.test_mode
232
232
  config.read
233
233
  config.reset_default_key_bindings
234
- Reline::IOGate::RAW_KEYSTROKE_CONFIG.each_pair do |key, func|
235
- config.add_default_key_binding(key, func)
236
- end
234
+ Reline::IOGate.set_default_key_bindings(config)
237
235
  end
238
236
 
239
237
  line_editor.rerender
@@ -273,11 +271,12 @@ module Reline
273
271
  Reline::IOGate.deprep(otio)
274
272
  end
275
273
 
276
- # Keystrokes of GNU Readline will timeout it with the specification of
277
- # "keyseq-timeout" when waiting for the 2nd character after the 1st one.
278
- # If the 2nd character comes after 1st ESC without timeout it has a
279
- # meta-property of meta-key to discriminate modified key with meta-key
280
- # from multibyte characters that come with 8th bit on.
274
+ # GNU Readline waits for "keyseq-timeout" milliseconds to see if the ESC
275
+ # is followed by a character, and times out and treats it as a standalone
276
+ # ESC if the second character does not arrive. If the second character
277
+ # comes before timed out, it is treated as a modifier key with the
278
+ # meta-property of meta-key, so that it can be distinguished from
279
+ # multibyte characters with the 8th bit turned on.
281
280
  #
282
281
  # GNU Readline will wait for the 2nd character with "keyseq-timeout"
283
282
  # milli-seconds but wait forever after 3rd characters.
@@ -455,17 +454,25 @@ module Reline
455
454
  end
456
455
  end
457
456
 
457
+ require 'reline/general_io'
458
458
  if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
459
459
  require 'reline/windows'
460
460
  if Reline::Windows.msys_tty?
461
- require 'reline/ansi'
462
- Reline::IOGate = Reline::ANSI
461
+ Reline::IOGate = if ENV['TERM'] == 'dumb'
462
+ Reline::GeneralIO
463
+ else
464
+ require 'reline/ansi'
465
+ Reline::ANSI
466
+ end
463
467
  else
464
468
  Reline::IOGate = Reline::Windows
465
469
  end
466
470
  else
467
- require 'reline/ansi'
468
- Reline::IOGate = Reline::ANSI
471
+ Reline::IOGate = if $stdout.isatty
472
+ require 'reline/ansi'
473
+ Reline::ANSI
474
+ else
475
+ Reline::GeneralIO
476
+ end
469
477
  end
470
478
  Reline::HISTORY = Reline::History.new(Reline.core.config)
471
- require 'reline/general_io'
data/lib/reline/ansi.rb CHANGED
@@ -1,7 +1,12 @@
1
1
  require 'io/console'
2
2
  require 'timeout'
3
+ require_relative 'terminfo'
3
4
 
4
5
  class Reline::ANSI
6
+ if Reline::Terminfo.enabled?
7
+ Reline::Terminfo.setupterm(0, 2)
8
+ end
9
+
5
10
  def self.encoding
6
11
  Encoding.default_external
7
12
  end
@@ -10,52 +15,99 @@ class Reline::ANSI
10
15
  false
11
16
  end
12
17
 
13
- RAW_KEYSTROKE_CONFIG = {
14
- # Console (80x25)
15
- [27, 91, 49, 126] => :ed_move_to_beg, # Home
16
- [27, 91, 52, 126] => :ed_move_to_end, # End
17
- [27, 91, 51, 126] => :key_delete, # Del
18
- [27, 91, 65] => :ed_prev_history, # ↑
19
- [27, 91, 66] => :ed_next_history, # ↓
20
- [27, 91, 67] => :ed_next_char, # →
21
- [27, 91, 68] => :ed_prev_char, #
22
-
23
- # KDE
24
- [27, 91, 72] => :ed_move_to_beg, # Home
25
- [27, 91, 70] => :ed_move_to_end, # End
26
- # Del is 0x08
27
- [27, 71, 65] => :ed_prev_history, # ↑
28
- [27, 71, 66] => :ed_next_history, # ↓
29
- [27, 71, 67] => :ed_next_char, # →
30
- [27, 71, 68] => :ed_prev_char, # ←
31
-
32
- # urxvt / exoterm
33
- [27, 91, 55, 126] => :ed_move_to_beg, # Home
34
- [27, 91, 56, 126] => :ed_move_to_end, # End
35
-
36
- # GNOME
37
- [27, 79, 72] => :ed_move_to_beg, # Home
38
- [27, 79, 70] => :ed_move_to_end, # End
39
- # Del is 0x08
40
- # Arrow keys are the same of KDE
41
-
42
- # iTerm2
43
- [27, 27, 91, 67] => :em_next_word, # Option+→
44
- [27, 27, 91, 68] => :ed_prev_word, # Option+←
45
- [195, 166] => :em_next_word, # Option+f
46
- [195, 162] => :ed_prev_word, # Option+b
47
-
48
- # others
49
- [27, 32] => :em_set_mark, # M-<space>
50
- [24, 24] => :em_exchange_mark, # C-x C-x TODO also add Windows
51
- [27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→
52
- [27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←
53
-
54
- [27, 79, 65] => :ed_prev_history, # ↑
55
- [27, 79, 66] => :ed_next_history, # ↓
56
- [27, 79, 67] => :ed_next_char, # →
57
- [27, 79, 68] => :ed_prev_char, # ←
58
- }
18
+ def self.set_default_key_bindings(config)
19
+ if Reline::Terminfo.enabled?
20
+ set_default_key_bindings_terminfo(config)
21
+ else
22
+ set_default_key_bindings_comprehensive_list(config)
23
+ end
24
+ {
25
+ # extended entries of terminfo
26
+ [27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→, extended entry
27
+ [27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←, extended entry
28
+ [27, 91, 49, 59, 51, 67] => :em_next_word, # Meta+→, extended entry
29
+ [27, 91, 49, 59, 51, 68] => :ed_prev_word, # Meta+←, extended entry
30
+ }.each_pair do |key, func|
31
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
32
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
33
+ config.add_default_key_binding_by_keymap(:vi_command, key, func)
34
+ end
35
+ {
36
+ # default bindings
37
+ [27, 32] => :em_set_mark, # M-<space>
38
+ [24, 24] => :em_exchange_mark, # C-x C-x
39
+ }.each_pair do |key, func|
40
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
41
+ end
42
+ end
43
+
44
+ def self.set_default_key_bindings_terminfo(config)
45
+ {
46
+ Reline::Terminfo.tigetstr('khome').bytes => :ed_move_to_beg,
47
+ Reline::Terminfo.tigetstr('kend').bytes => :ed_move_to_end,
48
+ Reline::Terminfo.tigetstr('kcuu1').bytes => :ed_prev_history,
49
+ Reline::Terminfo.tigetstr('kcud1').bytes => :ed_next_history,
50
+ Reline::Terminfo.tigetstr('kcuf1').bytes => :ed_next_char,
51
+ Reline::Terminfo.tigetstr('kcub1').bytes => :ed_prev_char,
52
+ # Escape sequences that omit the move distance and are set to defaults
53
+ # value 1 may be sometimes sent by pressing the arrow-key.
54
+ Reline::Terminfo.tigetstr('cuu').sub(/%p1%d/, '').bytes => :ed_prev_history,
55
+ Reline::Terminfo.tigetstr('cud').sub(/%p1%d/, '').bytes => :ed_next_history,
56
+ Reline::Terminfo.tigetstr('cuf').sub(/%p1%d/, '').bytes => :ed_next_char,
57
+ Reline::Terminfo.tigetstr('cub').sub(/%p1%d/, '').bytes => :ed_prev_char,
58
+ }.each_pair do |key, func|
59
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
60
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
61
+ config.add_default_key_binding_by_keymap(:vi_command, key, func)
62
+ end
63
+ end
64
+
65
+ def self.set_default_key_bindings_comprehensive_list(config)
66
+ {
67
+ # Console (80x25)
68
+ [27, 91, 49, 126] => :ed_move_to_beg, # Home
69
+ [27, 91, 52, 126] => :ed_move_to_end, # End
70
+ [27, 91, 51, 126] => :key_delete, # Del
71
+ [27, 91, 65] => :ed_prev_history, # ↑
72
+ [27, 91, 66] => :ed_next_history, # ↓
73
+ [27, 91, 67] => :ed_next_char, # →
74
+ [27, 91, 68] => :ed_prev_char, # ←
75
+
76
+ # KDE
77
+ [27, 91, 72] => :ed_move_to_beg, # Home
78
+ [27, 91, 70] => :ed_move_to_end, # End
79
+ # Del is 0x08
80
+ [27, 71, 65] => :ed_prev_history, # ↑
81
+ [27, 71, 66] => :ed_next_history, # ↓
82
+ [27, 71, 67] => :ed_next_char, # →
83
+ [27, 71, 68] => :ed_prev_char, # ←
84
+
85
+ # urxvt / exoterm
86
+ [27, 91, 55, 126] => :ed_move_to_beg, # Home
87
+ [27, 91, 56, 126] => :ed_move_to_end, # End
88
+
89
+ # GNOME
90
+ [27, 79, 72] => :ed_move_to_beg, # Home
91
+ [27, 79, 70] => :ed_move_to_end, # End
92
+ # Del is 0x08
93
+ # Arrow keys are the same of KDE
94
+
95
+ # iTerm2
96
+ [27, 27, 91, 67] => :em_next_word, # Option+→, extended entry
97
+ [27, 27, 91, 68] => :ed_prev_word, # Option+←, extended entry
98
+ [195, 166] => :em_next_word, # Option+f
99
+ [195, 162] => :ed_prev_word, # Option+b
100
+
101
+ [27, 79, 65] => :ed_prev_history, # ↑
102
+ [27, 79, 66] => :ed_next_history, # ↓
103
+ [27, 79, 67] => :ed_next_char, # →
104
+ [27, 79, 68] => :ed_prev_char, # ←
105
+ }.each_pair do |key, func|
106
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
107
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
108
+ config.add_default_key_binding_by_keymap(:vi_command, key, func)
109
+ end
110
+ end
59
111
 
60
112
  @@input = STDIN
61
113
  def self.input=(val)
@@ -79,6 +131,8 @@ class Reline::ANSI
79
131
  rescue Errno::EIO
80
132
  # Maybe the I/O has been closed.
81
133
  nil
134
+ rescue Errno::ENOTTY
135
+ nil
82
136
  end
83
137
 
84
138
  @@in_bracketed_paste_mode = false
data/lib/reline/config.rb CHANGED
@@ -47,7 +47,9 @@ class Reline::Config
47
47
 
48
48
  def initialize
49
49
  @additional_key_bindings = {} # from inputrc
50
- @default_key_bindings = {} # environment-dependent
50
+ @additional_key_bindings[:emacs] = {}
51
+ @additional_key_bindings[:vi_insert] = {}
52
+ @additional_key_bindings[:vi_command] = {}
51
53
  @skip_section = nil
52
54
  @if_stack = nil
53
55
  @editing_mode_label = :emacs
@@ -69,8 +71,9 @@ class Reline::Config
69
71
  if editing_mode_is?(:vi_command)
70
72
  @editing_mode_label = :vi_insert
71
73
  end
72
- @additional_key_bindings = {}
73
- @default_key_bindings = {}
74
+ @additional_key_bindings.keys.each do |key|
75
+ @additional_key_bindings[key].clear
76
+ end
74
77
  end
75
78
 
76
79
  def editing_mode
@@ -135,19 +138,28 @@ class Reline::Config
135
138
  end
136
139
 
137
140
  def key_bindings
138
- # override @default_key_bindings with @additional_key_bindings
139
- @default_key_bindings.merge(@additional_key_bindings)
141
+ # override @key_actors[@editing_mode_label].default_key_bindings with @additional_key_bindings[@editing_mode_label]
142
+ @key_actors[@editing_mode_label].default_key_bindings.merge(@additional_key_bindings[@editing_mode_label])
143
+ end
144
+
145
+ def add_default_key_binding_by_keymap(keymap, keystroke, target)
146
+ @key_actors[keymap].default_key_bindings[keystroke] = target
140
147
  end
141
148
 
142
149
  def add_default_key_binding(keystroke, target)
143
- @default_key_bindings[keystroke] = target
150
+ @key_actors[@keymap_label].default_key_bindings[keystroke] = target
144
151
  end
145
152
 
146
153
  def reset_default_key_bindings
147
- @default_key_bindings = {}
154
+ @key_actors.values.each do |ka|
155
+ ka.reset_default_key_bindings
156
+ end
148
157
  end
149
158
 
150
159
  def read_lines(lines, file = nil)
160
+ if lines.first.encoding != Reline.encoding_system_needs
161
+ lines = lines.map { |l| l.encode(Reline.encoding_system_needs) }
162
+ end
151
163
  conditions = [@skip_section, @if_stack]
152
164
  @skip_section = nil
153
165
  @if_stack = []
@@ -174,7 +186,7 @@ class Reline::Config
174
186
  key, func_name = $1, $2
175
187
  keystroke, func = bind_key(key, func_name)
176
188
  next unless keystroke
177
- @additional_key_bindings[keystroke] = func
189
+ @additional_key_bindings[@keymap_label][keystroke] = func
178
190
  end
179
191
  end
180
192
  unless @if_stack.empty?
@@ -282,11 +294,8 @@ class Reline::Config
282
294
  end
283
295
 
284
296
  def retrieve_string(str)
285
- if str =~ /\A"(.*)"\z/
286
- parse_keyseq($1).map(&:chr).join
287
- else
288
- parse_keyseq(str).map(&:chr).join
289
- end
297
+ str = $1 if str =~ /\A"(.*)"\z/
298
+ parse_keyseq(str).map { |c| c.chr(Reline.encoding_system_needs) }.join
290
299
  end
291
300
 
292
301
  def bind_key(key, func_name)
@@ -1,19 +1,27 @@
1
1
  require 'timeout'
2
2
 
3
3
  class Reline::GeneralIO
4
- def self.reset
4
+ def self.reset(encoding: nil)
5
5
  @@pasting = false
6
+ @@encoding = encoding
6
7
  end
7
8
 
8
9
  def self.encoding
9
- RUBY_PLATFORM =~ /mswin|mingw/ ? Encoding::UTF_8 : Encoding::default_external
10
+ if defined?(@@encoding)
11
+ @@encoding
12
+ elsif RUBY_PLATFORM =~ /mswin|mingw/
13
+ Encoding::UTF_8
14
+ else
15
+ Encoding::default_external
16
+ end
10
17
  end
11
18
 
12
19
  def self.win?
13
20
  false
14
21
  end
15
22
 
16
- RAW_KEYSTROKE_CONFIG = {}
23
+ def self.set_default_key_bindings(_)
24
+ end
17
25
 
18
26
  @@buf = []
19
27
 
@@ -4,4 +4,16 @@ class Reline::KeyActor::Base
4
4
  def get_method(key)
5
5
  self.class::MAPPING[key]
6
6
  end
7
+
8
+ def initialize
9
+ @default_key_bindings = {}
10
+ end
11
+
12
+ def default_key_bindings
13
+ @default_key_bindings
14
+ end
15
+
16
+ def reset_default_key_bindings
17
+ @default_key_bindings.clear
18
+ end
7
19
  end
@@ -409,7 +409,6 @@ class Reline::LineEditor
409
409
  return
410
410
  end
411
411
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
412
- # FIXME: end of logical line sometimes breaks
413
412
  rendered = false
414
413
  if @add_newline_to_end_of_buffer
415
414
  rerender_added_newline(prompt, prompt_width)
@@ -678,7 +677,6 @@ class Reline::LineEditor
678
677
  private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
679
678
  visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
680
679
  cursor_up_from_last_line = 0
681
- # TODO: This logic would be sometimes buggy if this logical line isn't the current @line_index.
682
680
  if @scroll_partial_screen
683
681
  last_visual_line = this_started_from + (height - 1)
684
682
  last_screen_line = @scroll_partial_screen + (@screen_height - 1)
@@ -0,0 +1,84 @@
1
+ require 'fiddle'
2
+ require 'fiddle/import'
3
+
4
+ module Reline::Terminfo
5
+ extend Fiddle::Importer
6
+
7
+ class TerminfoError < StandardError; end
8
+
9
+ @curses_dl = nil
10
+ def self.curses_dl
11
+ return @curses_dl if @curses_dl
12
+ if Fiddle.const_defined?(:VERSION) and Gem::Version.create(Fiddle::VERSION) >= Gem::Version.create('1.0.1')
13
+ # Fiddle::TYPE_VARIADIC is supported from Fiddle 1.0.1.
14
+ %w[libncursesw.so libcursesw.so libncurses.so libcurses.so].each do |curses_name|
15
+ result = Fiddle::Handle.new(curses_name)
16
+ rescue Fiddle::DLError
17
+ next
18
+ else
19
+ @curses_dl = result
20
+ break
21
+ end
22
+ end
23
+ @curses_dl
24
+ end
25
+ end
26
+
27
+ module Reline::Terminfo
28
+ dlload curses_dl
29
+ #extern 'int setupterm(char *term, int fildes, int *errret)'
30
+ @setupterm = Fiddle::Function.new(curses_dl['setupterm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
31
+ #extern 'char *tigetstr(char *capname)'
32
+ @tigetstr = Fiddle::Function.new(curses_dl['tigetstr'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOIDP)
33
+ #extern 'char *tiparm(const char *str, ...)'
34
+ @tiparm = Fiddle::Function.new(curses_dl['tiparm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VARIADIC], Fiddle::TYPE_VOIDP)
35
+
36
+ def self.setupterm(term, fildes)
37
+ errret_int = String.new("\x00" * 8, encoding: 'ASCII-8BIT')
38
+ ret = @setupterm.(term, fildes, errret_int)
39
+ errret = errret_int.unpack('i')[0]
40
+ case ret
41
+ when 0 # OK
42
+ 0
43
+ when -1 # ERR
44
+ case errret
45
+ when 1
46
+ raise TerminfoError.new('The terminal is hardcopy, cannot be used for curses applications.')
47
+ when 0
48
+ raise TerminfoError.new('The terminal could not be found, or that it is a generic type, having too little information for curses applications to run.')
49
+ when -1
50
+ raise TerminfoError.new('The terminfo database could not be found.')
51
+ else # unknown
52
+ -1
53
+ end
54
+ else # unknown
55
+ -2
56
+ end
57
+ end
58
+
59
+ def self.tigetstr(capname)
60
+ result = @tigetstr.(capname).to_s
61
+ def result.tiparm(*args) # for method chain
62
+ Reline::Terminfo.tiparm(self, *args)
63
+ end
64
+ result
65
+ end
66
+
67
+ def self.tiparm(str, *args)
68
+ new_args = []
69
+ args.each do |a|
70
+ new_args << Fiddle::TYPE_INT << a
71
+ end
72
+ @tiparm.(str, *new_args).to_s
73
+ end
74
+
75
+ def self.enabled?
76
+ true
77
+ end
78
+ end if Reline::Terminfo.curses_dl
79
+
80
+ module Reline::Terminfo
81
+ def self.enabled?
82
+ false
83
+ end
84
+ end unless Reline::Terminfo.curses_dl
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.2.5'
2
+ VERSION = '0.2.6'
3
3
  end
@@ -13,23 +13,36 @@ class Reline::Windows
13
13
  @@legacy_console
14
14
  end
15
15
 
16
- RAW_KEYSTROKE_CONFIG = {
17
- [224, 72] => :ed_prev_history, # ↑
18
- [224, 80] => :ed_next_history, #
19
- [224, 77] => :ed_next_char, #
20
- [224, 75] => :ed_prev_char, #
21
- [224, 83] => :key_delete, # Del
22
- [224, 71] => :ed_move_to_beg, # Home
23
- [224, 79] => :ed_move_to_end, # End
24
- [ 0, 41] => :ed_unassigned, # input method on/off
25
- [ 0, 72] => :ed_prev_history, #
26
- [ 0, 80] => :ed_next_history, #
27
- [ 0, 77] => :ed_next_char, #
28
- [ 0, 75] => :ed_prev_char, #
29
- [ 0, 83] => :key_delete, # Del
30
- [ 0, 71] => :ed_move_to_beg, # Home
31
- [ 0, 79] => :ed_move_to_end # End
32
- }
16
+ def self.set_default_key_bindings(config)
17
+ {
18
+ [224, 72] => :ed_prev_history, #
19
+ [224, 80] => :ed_next_history, #
20
+ [224, 77] => :ed_next_char, #
21
+ [224, 75] => :ed_prev_char, #
22
+ [224, 83] => :key_delete, # Del
23
+ [224, 71] => :ed_move_to_beg, # Home
24
+ [224, 79] => :ed_move_to_end, # End
25
+ [ 0, 41] => :ed_unassigned, # input method on/off
26
+ [ 0, 72] => :ed_prev_history, #
27
+ [ 0, 80] => :ed_next_history, #
28
+ [ 0, 77] => :ed_next_char, #
29
+ [ 0, 75] => :ed_prev_char, #
30
+ [ 0, 83] => :key_delete, # Del
31
+ [ 0, 71] => :ed_move_to_beg, # Home
32
+ [ 0, 79] => :ed_move_to_end # End
33
+ }.each_pair do |key, func|
34
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
35
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
36
+ config.add_default_key_binding_by_keymap(:vi_command, key, func)
37
+ end
38
+
39
+ {
40
+ [27, 32] => :em_set_mark, # M-<space>
41
+ [24, 24] => :em_exchange_mark, # C-x C-x
42
+ }.each_pair do |key, func|
43
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
44
+ end
45
+ end
33
46
 
34
47
  if defined? JRUBY_VERSION
35
48
  require 'win32api'
@@ -73,13 +86,35 @@ class Reline::Windows
73
86
  end
74
87
  end
75
88
 
89
+ VK_RETURN = 0x0D
76
90
  VK_MENU = 0x12
77
91
  VK_LMENU = 0xA4
78
92
  VK_CONTROL = 0x11
79
93
  VK_SHIFT = 0x10
94
+
95
+ KEY_EVENT = 0x01
96
+ WINDOW_BUFFER_SIZE_EVENT = 0x04
97
+
98
+ CAPSLOCK_ON = 0x0080
99
+ ENHANCED_KEY = 0x0100
100
+ LEFT_ALT_PRESSED = 0x0002
101
+ LEFT_CTRL_PRESSED = 0x0008
102
+ NUMLOCK_ON = 0x0020
103
+ RIGHT_ALT_PRESSED = 0x0001
104
+ RIGHT_CTRL_PRESSED = 0x0004
105
+ SCROLLLOCK_ON = 0x0040
106
+ SHIFT_PRESSED = 0x0010
107
+
108
+ VK_END = 0x23
109
+ VK_HOME = 0x24
110
+ VK_LEFT = 0x25
111
+ VK_UP = 0x26
112
+ VK_RIGHT = 0x27
113
+ VK_DOWN = 0x28
114
+ VK_DELETE = 0x2E
115
+
80
116
  STD_INPUT_HANDLE = -10
81
117
  STD_OUTPUT_HANDLE = -11
82
- WINDOW_BUFFER_SIZE_EVENT = 0x04
83
118
  FILE_TYPE_PIPE = 0x0003
84
119
  FILE_NAME_INFO = 2
85
120
  @@getwch = Win32API.new('msvcrt', '_getwch', [], 'I')
@@ -93,7 +128,7 @@ class Reline::Windows
93
128
  @@hConsoleHandle = @@GetStdHandle.call(STD_OUTPUT_HANDLE)
94
129
  @@hConsoleInputHandle = @@GetStdHandle.call(STD_INPUT_HANDLE)
95
130
  @@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L')
96
- @@ReadConsoleInput = Win32API.new('kernel32', 'ReadConsoleInput', ['L', 'P', 'L', 'P'], 'L')
131
+ @@ReadConsoleInputW = Win32API.new('kernel32', 'ReadConsoleInputW', ['L', 'P', 'L', 'P'], 'L')
97
132
  @@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
98
133
  @@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
99
134
  @@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
@@ -145,78 +180,78 @@ class Reline::Windows
145
180
  name =~ /(msys-|cygwin-).*-pty/ ? true : false
146
181
  end
147
182
 
148
- def self.getwch
149
- unless @@input_buf.empty?
150
- return @@input_buf.shift
151
- end
152
- while @@kbhit.call == 0
153
- sleep(0.001)
154
- end
155
- until @@kbhit.call == 0
156
- ret = @@getwch.call
157
- if ret == 0 or ret == 0xE0
158
- @@input_buf << ret
159
- ret = @@getwch.call
160
- @@input_buf << ret
161
- return @@input_buf.shift
183
+ def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
184
+ char = char_code.chr(Encoding::UTF_8)
185
+ if char_code == 0x0D and control_key_state.anybits?(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED | SHIFT_PRESSED)
186
+ # It's treated as Meta+Enter on Windows.
187
+ @@output_buf.push("\e".ord)
188
+ @@output_buf.push(char_code)
189
+ elsif char_code == 0x20 and control_key_state.anybits?(LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)
190
+ # It's treated as Meta+Space on Windows.
191
+ @@output_buf.push("\e".ord)
192
+ @@output_buf.push(char_code)
193
+ elsif control_key_state.anybits?(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)
194
+ @@output_buf.push("\e".ord)
195
+ @@output_buf.concat(char.bytes)
196
+ elsif control_key_state.anybits?(ENHANCED_KEY)
197
+ case virtual_key_code # Emulate getwch() key sequences.
198
+ when VK_END
199
+ @@output_buf.push(0, 79)
200
+ when VK_HOME
201
+ @@output_buf.push(0, 71)
202
+ when VK_LEFT
203
+ @@output_buf.push(0, 75)
204
+ when VK_UP
205
+ @@output_buf.push(0, 72)
206
+ when VK_RIGHT
207
+ @@output_buf.push(0, 77)
208
+ when VK_DOWN
209
+ @@output_buf.push(0, 80)
210
+ when VK_DELETE
211
+ @@output_buf.push(0, 83)
162
212
  end
163
- begin
164
- bytes = ret.chr(Encoding::UTF_8).bytes
165
- @@input_buf.push(*bytes)
166
- rescue Encoding::UndefinedConversionError
167
- @@input_buf << ret
168
- @@input_buf << @@getwch.call if ret == 224
213
+ elsif char_code == 0 and control_key_state != 0
214
+ # unknown
215
+ else
216
+ case virtual_key_code
217
+ when VK_RETURN
218
+ @@output_buf.push("\n".ord)
219
+ else
220
+ @@output_buf.concat(char.bytes)
169
221
  end
170
222
  end
171
- @@input_buf.shift
172
223
  end
173
224
 
174
- def self.getc
225
+ def self.check_input_event
175
226
  num_of_events = 0.chr * 8
176
- while @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) != 0 and num_of_events.unpack('L').first > 0
227
+ while @@output_buf.empty? #or true
228
+ next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack('L').first == 0
177
229
  input_record = 0.chr * 18
178
230
  read_event = 0.chr * 4
179
- if @@ReadConsoleInput.(@@hConsoleInputHandle, input_record, 1, read_event) != 0
231
+ if @@ReadConsoleInputW.(@@hConsoleInputHandle, input_record, 1, read_event) != 0
180
232
  event = input_record[0, 2].unpack('s*').first
181
- if event == WINDOW_BUFFER_SIZE_EVENT
233
+ case event
234
+ when WINDOW_BUFFER_SIZE_EVENT
182
235
  @@winch_handler.()
236
+ when KEY_EVENT
237
+ key_down = input_record[4, 4].unpack('l*').first
238
+ repeat_count = input_record[8, 2].unpack('s*').first
239
+ virtual_key_code = input_record[10, 2].unpack('s*').first
240
+ virtual_scan_code = input_record[12, 2].unpack('s*').first
241
+ char_code = input_record[14, 2].unpack('S*').first
242
+ control_key_state = input_record[16, 2].unpack('S*').first
243
+ is_key_down = key_down.zero? ? false : true
244
+ if is_key_down
245
+ process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
246
+ end
183
247
  end
184
248
  end
185
249
  end
186
- unless @@output_buf.empty?
187
- return @@output_buf.shift
188
- end
189
- input = getwch
190
- meta = (@@GetKeyState.call(VK_LMENU) & 0x80) != 0
191
- control = (@@GetKeyState.call(VK_CONTROL) & 0x80) != 0
192
- shift = (@@GetKeyState.call(VK_SHIFT) & 0x80) != 0
193
- force_enter = !input.instance_of?(Array) && (control or shift) && input == 0x0D
194
- if force_enter
195
- # It's treated as Meta+Enter on Windows
196
- @@output_buf.push("\e".ord)
197
- @@output_buf.push(input)
198
- else
199
- case input
200
- when 0x00
201
- meta = false
202
- @@output_buf.push(input)
203
- input = getwch
204
- @@output_buf.push(*input)
205
- when 0xE0
206
- @@output_buf.push(input)
207
- input = getwch
208
- @@output_buf.push(*input)
209
- when 0x03
210
- @@output_buf.push(input)
211
- else
212
- @@output_buf.push(input)
213
- end
214
- end
215
- if meta
216
- "\e".ord
217
- else
218
- @@output_buf.shift
219
- end
250
+ end
251
+
252
+ def self.getc
253
+ check_input_event
254
+ @@output_buf.shift
220
255
  end
221
256
 
222
257
  def self.ungetc(c)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.5
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-04-02 00:00:00.000000000 Z
11
+ date: 2021-06-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console
@@ -24,62 +24,6 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0.5'
27
- - !ruby/object:Gem::Dependency
28
- name: bundler
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rake
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: test-unit
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
69
- - !ruby/object:Gem::Dependency
70
- name: yamatanooroti
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: 0.0.6
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: 0.0.6
83
27
  description: Alternative GNU Readline or Editline implementation by pure Ruby.
84
28
  email:
85
29
  - aycabta@gmail.com
@@ -104,6 +48,7 @@ files:
104
48
  - lib/reline/kill_ring.rb
105
49
  - lib/reline/line_editor.rb
106
50
  - lib/reline/sibori.rb
51
+ - lib/reline/terminfo.rb
107
52
  - lib/reline/unicode.rb
108
53
  - lib/reline/unicode/east_asian_width.rb
109
54
  - lib/reline/version.rb
@@ -128,7 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
128
73
  - !ruby/object:Gem::Version
129
74
  version: '0'
130
75
  requirements: []
131
- rubygems_version: 3.2.0.rc.1
76
+ rubygems_version: 3.2.17
132
77
  signing_key:
133
78
  specification_version: 4
134
79
  summary: Alternative GNU Readline or Editline implementation by pure Ruby.