reline 0.2.5 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
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.