reline 0.0.2 → 0.0.7

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: f47cc0525de297744dff41dc87b9a4ac5c9ff1cad21da7f60a41aa4b17bd1b0e
4
- data.tar.gz: '04681f2a2dc431f65fa9f2df68c8b7aa1955a7a4032b8d6d5483984e236d4c9b'
3
+ metadata.gz: 985d3ce68260542cce79b3d3291c29ca30ee82250e50089c2de7bb16f60d755d
4
+ data.tar.gz: d3444d9e894a66fd2a0e5be4fddd5d36b9b254f0aaff7ed1d590fec26c13b3f6
5
5
  SHA512:
6
- metadata.gz: ad38bf76659bc7553ebf084fd4aeedf6aba7cba1a2860fc78af0683222109216d59bdb3b4bf66962ce9a640b9033d21f707442151834e9ccc21c6fc8d115347b
7
- data.tar.gz: 95d9d943870e64897076cdc97750f6110817bc4ad14e958755164aa862785d7e48399465f7997f900547f797e6304cf48d6761f78612651ebe7aa6dca505fab3
6
+ metadata.gz: 9a5651134aeb6d0eba7e33b4fa6e85c2ac43677432a7b94e8aa24d1c9f05326126c81669aa297f9cb03bea7f92c2ee8600364205c8a8eafa7c6185441472f9d1
7
+ data.tar.gz: 63cc8b0b0b936e8ac1a490c9a1c849631b2c4c33c520eb2dd666b9b93b641417f3640542067de4f64ff2e28fd4dd883ed3821c7704bb82a64485ee52d5273c3b
@@ -16,12 +16,6 @@ module Reline
16
16
  CursorPos = Struct.new(:x, :y)
17
17
 
18
18
  class Core
19
- if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
20
- IS_WINDOWS = true
21
- else
22
- IS_WINDOWS = false
23
- end
24
-
25
19
  ATTR_READER_NAMES = %i(
26
20
  completion_append_character
27
21
  basic_word_break_characters
@@ -90,22 +84,22 @@ module Reline
90
84
  end
91
85
 
92
86
  def completion_proc=(p)
93
- raise ArgumentError unless p.is_a?(Proc)
87
+ raise ArgumentError unless p.respond_to?(:call)
94
88
  @completion_proc = p
95
89
  end
96
90
 
97
91
  def output_modifier_proc=(p)
98
- raise ArgumentError unless p.is_a?(Proc)
92
+ raise ArgumentError unless p.respond_to?(:call)
99
93
  @output_modifier_proc = p
100
94
  end
101
95
 
102
96
  def prompt_proc=(p)
103
- raise ArgumentError unless p.is_a?(Proc)
97
+ raise ArgumentError unless p.respond_to?(:call)
104
98
  @prompt_proc = p
105
99
  end
106
100
 
107
101
  def auto_indent_proc=(p)
108
- raise ArgumentError unless p.is_a?(Proc)
102
+ raise ArgumentError unless p.respond_to?(:call)
109
103
  @auto_indent_proc = p
110
104
  end
111
105
 
@@ -114,7 +108,7 @@ module Reline
114
108
  end
115
109
 
116
110
  def dig_perfect_match_proc=(p)
117
- raise ArgumentError unless p.is_a?(Proc)
111
+ raise ArgumentError unless p.respond_to?(:call)
118
112
  @dig_perfect_match_proc = p
119
113
  end
120
114
 
@@ -166,7 +160,7 @@ module Reline
166
160
  inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
167
161
 
168
162
  whole_buffer = line_editor.whole_buffer.dup
169
- whole_buffer.taint
163
+ whole_buffer.taint if RUBY_VERSION < '2.7'
170
164
  if add_hist and whole_buffer and whole_buffer.chomp.size > 0
171
165
  Reline::HISTORY << whole_buffer
172
166
  end
@@ -179,7 +173,7 @@ module Reline
179
173
  inner_readline(prompt, add_hist, false)
180
174
 
181
175
  line = line_editor.line.dup
182
- line.taint
176
+ line.taint if RUBY_VERSION < '2.7'
183
177
  if add_hist and line and line.chomp.size > 0
184
178
  Reline::HISTORY << line.chomp
185
179
  end
@@ -260,7 +254,10 @@ module Reline
260
254
  result = key_stroke.match_status(buffer)
261
255
  case result
262
256
  when :matched
263
- block.(key_stroke.expand(buffer).map{ |c| Reline::Key.new(c, c, false) })
257
+ expanded = key_stroke.expand(buffer).map{ |expanded_c|
258
+ Reline::Key.new(expanded_c, expanded_c, false)
259
+ }
260
+ block.(expanded)
264
261
  break
265
262
  when :matching
266
263
  if buffer.size == 1
@@ -287,16 +284,19 @@ module Reline
287
284
  end
288
285
  when :unmatched
289
286
  if buffer.size == 1 and c == "\e".ord
290
- read_escaped_key(keyseq_timeout, buffer, block)
287
+ read_escaped_key(keyseq_timeout, c, block)
291
288
  else
292
- block.(buffer.map{ |c| Reline::Key.new(c, c, false) })
289
+ expanded = buffer.map{ |expanded_c|
290
+ Reline::Key.new(expanded_c, expanded_c, false)
291
+ }
292
+ block.(expanded)
293
293
  end
294
294
  break
295
295
  end
296
296
  end
297
297
  end
298
298
 
299
- private def read_escaped_key(keyseq_timeout, buffer, block)
299
+ private def read_escaped_key(keyseq_timeout, c, block)
300
300
  begin
301
301
  escaped_c = nil
302
302
  Timeout.timeout(keyseq_timeout / 1000.0) {
@@ -319,7 +319,7 @@ module Reline
319
319
 
320
320
  private def may_req_ambiguous_char_width
321
321
  @ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
322
- return if @ambiguous_width
322
+ return if ambiguous_width
323
323
  Reline::IOGate.move_cursor_column(0)
324
324
  print "\u{25bd}"
325
325
  @ambiguous_width = Reline::IOGate.cursor_pos.x
@@ -342,6 +342,7 @@ module Reline
342
342
  def_single_delegators :core, :vi_editing_mode, :emacs_editing_mode
343
343
  def_single_delegators :core, :readline
344
344
  def_instance_delegators self, :readline
345
+ private :readline
345
346
 
346
347
 
347
348
  #--------------------------------------------------------
@@ -369,6 +370,7 @@ module Reline
369
370
 
370
371
  def_single_delegators :core, :readmultiline
371
372
  def_instance_delegators self, :readmultiline
373
+ private :readmultiline
372
374
 
373
375
  def self.core
374
376
  @core ||= Core.new { |core|
@@ -392,9 +394,15 @@ module Reline
392
394
  HISTORY = History.new(core.config)
393
395
  end
394
396
 
395
- if Reline::Core::IS_WINDOWS
397
+ if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/
396
398
  require 'reline/windows'
397
- Reline::IOGate = Reline::Windows
399
+ if Reline::Windows.get_screen_size == [0, 0]
400
+ # Maybe Mintty on Cygwin
401
+ require 'reline/ansi'
402
+ Reline::IOGate = Reline::ANSI
403
+ else
404
+ Reline::IOGate = Reline::Windows
405
+ end
398
406
  else
399
407
  require 'reline/ansi'
400
408
  Reline::IOGate = Reline::ANSI
@@ -7,7 +7,11 @@ class Reline::ANSI
7
7
  [27, 91, 51, 126] => :key_delete, # Del
8
8
  [27, 91, 49, 126] => :ed_move_to_beg, # Home
9
9
  [27, 91, 52, 126] => :ed_move_to_end, # End
10
- }.each_key(&:freeze).freeze
10
+ [27, 91, 72] => :ed_move_to_beg, # Home
11
+ [27, 91, 70] => :ed_move_to_end, # End
12
+ [27, 32] => :em_set_mark, # M-<space>
13
+ [24, 24] => :em_exchange_mark, # C-x C-x TODO also add Windows
14
+ }
11
15
 
12
16
  @@input = STDIN
13
17
  def self.input=(val)
@@ -24,20 +28,22 @@ class Reline::ANSI
24
28
  unless @@buf.empty?
25
29
  return @@buf.shift
26
30
  end
27
- c = nil
28
- loop do
29
- result = select([@@input], [], [], 0.1)
30
- next if result.nil?
31
- c = @@input.read(1)
32
- break
33
- end
34
- c&.ord
31
+ @@input.getbyte
35
32
  end
36
33
 
37
34
  def self.ungetc(c)
38
35
  @@buf.unshift(c)
39
36
  end
40
37
 
38
+ def self.retrieve_keybuffer
39
+ result = select([@@input], [], [], 0.001)
40
+ return if result.nil?
41
+ str = @@input.read_nonblock(1024)
42
+ str.bytes.each do |c|
43
+ @@buf.push(c)
44
+ end
45
+ end
46
+
41
47
  def self.get_screen_size
42
48
  @@input.winsize
43
49
  rescue Errno::ENOTTY
@@ -112,12 +118,17 @@ class Reline::ANSI
112
118
  end
113
119
 
114
120
  def self.prep
121
+ retrieve_keybuffer
115
122
  int_handle = Signal.trap('INT', 'IGNORE')
116
123
  otio = `stty -g`.chomp
117
124
  setting = ' -echo -icrnl cbreak'
118
- if /-parenb\b/ =~ `stty -a`
125
+ stty = `stty -a`
126
+ if /-parenb\b/ =~ stty
119
127
  setting << ' pass8'
120
128
  end
129
+ if /\bdsusp *=/ =~ stty
130
+ setting << ' dsusp undef'
131
+ end
121
132
  setting << ' -ixoff'
122
133
  `stty #{setting}`
123
134
  Signal.trap('INT', int_handle)
@@ -126,7 +137,7 @@ class Reline::ANSI
126
137
 
127
138
  def self.deprep(otio)
128
139
  int_handle = Signal.trap('INT', 'IGNORE')
129
- `stty #{otio}`
140
+ system("stty #{otio}", err: File::NULL)
130
141
  Signal.trap('INT', int_handle)
131
142
  Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler
132
143
  end
@@ -1,7 +1,7 @@
1
1
  require 'timeout'
2
2
 
3
3
  class Reline::GeneralIO
4
- RAW_KEYSTROKE_CONFIG = {}.freeze
4
+ RAW_KEYSTROKE_CONFIG = {}
5
5
 
6
6
  @@buf = []
7
7
 
@@ -55,7 +55,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
55
55
  # 26 ^Z
56
56
  :ed_unassigned,
57
57
  # 27 ^[
58
- :em_meta_next,
58
+ :ed_unassigned,
59
59
  # 28 ^\
60
60
  :ed_ignore,
61
61
  # 29 ^]
@@ -37,7 +37,7 @@ class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
37
37
  # 17 ^Q
38
38
  :ed_ignore,
39
39
  # 18 ^R
40
- :ed_insert,
40
+ :ed_search_prev_history,
41
41
  # 19 ^S
42
42
  :ed_ignore,
43
43
  # 20 ^T
@@ -32,7 +32,7 @@ class Reline::KeyStroke
32
32
  end
33
33
 
34
34
  def expand(input)
35
- lhs = key_mapping.keys.select { |lhs| input.start_with? lhs }.sort_by(&:size).reverse.first
35
+ lhs = key_mapping.keys.select { |item| input.start_with? item }.sort_by(&:size).reverse.first
36
36
  return input unless lhs
37
37
  rhs = key_mapping[lhs]
38
38
 
@@ -60,14 +60,35 @@ class Reline::LineEditor
60
60
  reset_variables
61
61
  end
62
62
 
63
+ private def check_multiline_prompt(buffer, prompt)
64
+ if @vi_arg
65
+ prompt = "(arg: #{@vi_arg}) "
66
+ @rerender_all = true
67
+ elsif @searching_prompt
68
+ prompt = @searching_prompt
69
+ @rerender_all = true
70
+ else
71
+ prompt = @prompt
72
+ end
73
+ if @prompt_proc
74
+ prompt_list = @prompt_proc.(buffer)
75
+ prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
76
+ prompt = prompt_list[@line_index]
77
+ prompt_width = calculate_width(prompt, true)
78
+ [prompt, prompt_width, prompt_list]
79
+ else
80
+ prompt_width = calculate_width(prompt, true)
81
+ [prompt, prompt_width, nil]
82
+ end
83
+ end
84
+
63
85
  def reset(prompt = '', encoding = Encoding.default_external)
64
86
  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
65
87
  @screen_size = Reline::IOGate.get_screen_size
66
88
  reset_variables(prompt, encoding)
67
89
  @old_trap = Signal.trap('SIGINT') {
68
- scroll_down(@highest_in_all - @first_line_started_from)
69
- Reline::IOGate.move_cursor_column(0)
70
90
  @old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
91
+ raise Interrupt
71
92
  }
72
93
  Reline::IOGate.set_winch_handler do
73
94
  @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
@@ -77,28 +98,9 @@ class Reline::LineEditor
77
98
  @rerender_all = true
78
99
  rerender
79
100
  else
80
- special_prompt = nil
81
- if @vi_arg
82
- prompt = "(arg: #{@vi_arg}) "
83
- prompt_width = calculate_width(prompt)
84
- special_prompt = prompt
85
- elsif @searching_prompt
86
- prompt = @searching_prompt
87
- prompt_width = calculate_width(prompt)
88
- special_prompt = prompt
89
- else
90
- prompt = @prompt
91
- prompt_width = calculate_width(prompt, true)
92
- end
93
101
  back = 0
94
102
  new_buffer = whole_lines
95
- prompt_list = nil
96
- if @prompt_proc
97
- prompt_list = @prompt_proc.(new_buffer)
98
- prompt_list[@line_index] = special_prompt if special_prompt
99
- prompt = prompt_list[@line_index]
100
- prompt_width = calculate_width(prompt, true)
101
- end
103
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
102
104
  new_buffer.each_with_index do |line, index|
103
105
  prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
104
106
  width = prompt_width + calculate_width(line)
@@ -111,9 +113,7 @@ class Reline::LineEditor
111
113
  if @line_index.zero?
112
114
  0
113
115
  else
114
- @buffer_of_lines[0..(@line_index - 1)].inject(0) { |result, line|
115
- result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
116
- }
116
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
117
117
  end
118
118
  if @prompt_proc
119
119
  prompt = prompt_list[@line_index]
@@ -138,6 +138,7 @@ class Reline::LineEditor
138
138
 
139
139
  def reset_variables(prompt = '', encoding = Encoding.default_external)
140
140
  @prompt = prompt
141
+ @mark_pointer = nil
141
142
  @encoding = encoding
142
143
  @is_multiline = false
143
144
  @finished = false
@@ -186,6 +187,16 @@ class Reline::LineEditor
186
187
  @is_multiline = false
187
188
  end
188
189
 
190
+ private def calculate_height_by_lines(lines, prompt_list)
191
+ result = 0
192
+ lines.each_with_index { |line, i|
193
+ prompt = ''
194
+ prompt = prompt_list[i] if prompt_list and prompt_list[i]
195
+ result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
196
+ }
197
+ result
198
+ end
199
+
189
200
  private def insert_new_line(cursor_line, next_line)
190
201
  @line = cursor_line
191
202
  @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
@@ -306,36 +317,18 @@ class Reline::LineEditor
306
317
  @menu_info.list.each do |item|
307
318
  Reline::IOGate.move_cursor_column(0)
308
319
  @output.print item
320
+ @output.flush
309
321
  scroll_down(1)
310
322
  end
311
323
  scroll_down(@highest_in_all - 1)
312
324
  move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
313
325
  @menu_info = nil
314
326
  end
315
- special_prompt = nil
316
- if @vi_arg
317
- prompt = "(arg: #{@vi_arg}) "
318
- prompt_width = calculate_width(prompt)
319
- special_prompt = prompt
320
- elsif @searching_prompt
321
- prompt = @searching_prompt
322
- prompt_width = calculate_width(prompt)
323
- special_prompt = prompt
324
- else
325
- prompt = @prompt
326
- prompt_width = calculate_width(prompt, true)
327
- end
327
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
328
328
  if @cleared
329
329
  Reline::IOGate.clear_screen
330
330
  @cleared = false
331
331
  back = 0
332
- prompt_list = nil
333
- if @prompt_proc
334
- prompt_list = @prompt_proc.(whole_lines)
335
- prompt_list[@line_index] = special_prompt if special_prompt
336
- prompt = prompt_list[@line_index]
337
- prompt_width = calculate_width(prompt, true)
338
- end
339
332
  modify_lines(whole_lines).each_with_index do |line, index|
340
333
  if @prompt_proc
341
334
  pr = prompt_list[index]
@@ -361,16 +354,8 @@ class Reline::LineEditor
361
354
  else
362
355
  new_lines = whole_lines
363
356
  end
364
- prompt_list = nil
365
- if @prompt_proc
366
- prompt_list = @prompt_proc.(new_lines)
367
- prompt_list[@line_index] = special_prompt if special_prompt
368
- prompt = prompt_list[@line_index]
369
- prompt_width = calculate_width(prompt, true)
370
- end
371
- all_height = new_lines.inject(0) { |result, line|
372
- result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
373
- }
357
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
358
+ all_height = calculate_height_by_lines(new_lines, prompt_list)
374
359
  diff = all_height - @highest_in_all
375
360
  move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
376
361
  if diff > 0
@@ -410,9 +395,7 @@ class Reline::LineEditor
410
395
  if @line_index.zero?
411
396
  0
412
397
  else
413
- @buffer_of_lines[0..(@line_index - 1)].inject(0) { |result, line|
414
- result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
415
- }
398
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
416
399
  end
417
400
  if @prompt_proc
418
401
  prompt = prompt_list[@line_index]
@@ -431,13 +414,7 @@ class Reline::LineEditor
431
414
  Reline::IOGate.move_cursor_column(0)
432
415
  back = 0
433
416
  new_buffer = whole_lines
434
- prompt_list = nil
435
- if @prompt_proc
436
- prompt_list = @prompt_proc.(new_buffer)
437
- prompt_list[@line_index] = special_prompt if special_prompt
438
- prompt = prompt_list[@line_index]
439
- prompt_width = calculate_width(prompt, true)
440
- end
417
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
441
418
  new_buffer.each_with_index do |line, index|
442
419
  prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
443
420
  width = prompt_width + calculate_width(line)
@@ -477,9 +454,7 @@ class Reline::LineEditor
477
454
  if @line_index.zero?
478
455
  0
479
456
  else
480
- new_buffer[0..(@line_index - 1)].inject(0) { |result, line|
481
- result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
482
- }
457
+ calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list)
483
458
  end
484
459
  @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
485
460
  move_cursor_down(@first_line_started_from + @started_from)
@@ -489,13 +464,7 @@ class Reline::LineEditor
489
464
  end
490
465
  line = modify_lines(whole_lines)[@line_index]
491
466
  if @is_multiline
492
- prompt_list = nil
493
- if @prompt_proc
494
- prompt_list = @prompt_proc.(whole_lines)
495
- prompt_list[@line_index] = special_prompt if special_prompt
496
- prompt = prompt_list[@line_index]
497
- prompt_width = calculate_width(prompt, true)
498
- end
467
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
499
468
  if finished?
500
469
  # Always rerender on finish because output_modifier_proc may return a different output.
501
470
  render_partial(prompt, prompt_width, line)
@@ -541,6 +510,7 @@ class Reline::LineEditor
541
510
  next
542
511
  end
543
512
  @output.print line
513
+ @output.flush
544
514
  if @first_prompt
545
515
  @first_prompt = false
546
516
  @pre_input_hook&.call
@@ -683,9 +653,9 @@ class Reline::LineEditor
683
653
  else
684
654
  old_waiting_proc = @waiting_proc
685
655
  old_waiting_operator_proc = @waiting_operator_proc
686
- @waiting_proc = proc { |key|
656
+ @waiting_proc = proc { |k|
687
657
  old_cursor, old_byte_pointer = @cursor, @byte_pointer
688
- old_waiting_proc.(key)
658
+ old_waiting_proc.(k)
689
659
  cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
690
660
  @cursor, @byte_pointer = old_cursor, old_byte_pointer
691
661
  @waiting_operator_proc.(cursor_diff, byte_pointer_diff)
@@ -796,7 +766,7 @@ class Reline::LineEditor
796
766
  end
797
767
 
798
768
  def input_key(key)
799
- if key.nil? or key.char.nil?
769
+ if key.char.nil?
800
770
  if @first_char
801
771
  @line = nil
802
772
  end
@@ -836,6 +806,26 @@ class Reline::LineEditor
836
806
 
837
807
  private def process_auto_indent
838
808
  return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
809
+ if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
810
+ # Fix indent of a line when a newline is inserted to the next
811
+ new_lines = whole_lines(index: @previous_line_index, line: @line)
812
+ new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
813
+ md = @line.match(/\A */)
814
+ prev_indent = md[0].count(' ')
815
+ @line = ' ' * new_indent + @line.lstrip
816
+
817
+ new_indent = nil
818
+ (new_lines[-2].size + 1).times do |n|
819
+ result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, n, false)
820
+ if result
821
+ new_indent = result
822
+ break
823
+ end
824
+ end
825
+ if new_indent&.>= 0
826
+ @line = ' ' * new_indent + @line.lstrip
827
+ end
828
+ end
839
829
  if @previous_line_index
840
830
  new_lines = whole_lines(index: @previous_line_index, line: @line)
841
831
  else
@@ -1027,8 +1017,8 @@ class Reline::LineEditor
1027
1017
  end
1028
1018
  width
1029
1019
  else
1030
- str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |width, gc|
1031
- width + Reline::Unicode.get_mbchar_width(gc)
1020
+ str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc|
1021
+ w + Reline::Unicode.get_mbchar_width(gc)
1032
1022
  }
1033
1023
  end
1034
1024
  end
@@ -1049,6 +1039,8 @@ class Reline::LineEditor
1049
1039
  end
1050
1040
  end
1051
1041
 
1042
+ private def ed_unassigned(key) end # do nothing
1043
+
1052
1044
  private def ed_insert(key)
1053
1045
  if key.instance_of?(String)
1054
1046
  width = Reline::Unicode.get_mbchar_width(key)
@@ -1153,7 +1145,11 @@ class Reline::LineEditor
1153
1145
  alias_method :end_of_line, :ed_move_to_end
1154
1146
 
1155
1147
  private def ed_search_prev_history(key)
1156
- @line_backup_in_history = @line
1148
+ if @is_multiline
1149
+ @line_backup_in_history = whole_buffer
1150
+ else
1151
+ @line_backup_in_history = @line
1152
+ end
1157
1153
  searcher = Fiber.new do
1158
1154
  search_word = String.new(encoding: @encoding)
1159
1155
  multibyte_buf = String.new(encoding: 'ASCII-8BIT')
@@ -1188,18 +1184,32 @@ class Reline::LineEditor
1188
1184
  end
1189
1185
  end
1190
1186
  if hit
1191
- @searching_prompt = "(reverse-i-search)`%s': %s" % [search_word, hit]
1192
- @line = hit
1187
+ if @is_multiline
1188
+ @buffer_of_lines = hit.split("\n")
1189
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1190
+ @line_index = @buffer_of_lines.size - 1
1191
+ @line = @buffer_of_lines.last
1192
+ @rerender_all = true
1193
+ @searching_prompt = "(reverse-i-search)`%s'" % [search_word]
1194
+ else
1195
+ @line = hit
1196
+ @searching_prompt = "(reverse-i-search)`%s': %s" % [search_word, hit]
1197
+ end
1193
1198
  last_hit = hit
1194
1199
  else
1195
- @searching_prompt = "(failed reverse-i-search)`%s': %s" % [search_word, last_hit]
1200
+ if @is_multiline
1201
+ @rerender_all = true
1202
+ @searching_prompt = "(failed reverse-i-search)`%s'" % [search_word]
1203
+ else
1204
+ @searching_prompt = "(failed reverse-i-search)`%s': %s" % [search_word, last_hit]
1205
+ end
1196
1206
  end
1197
1207
  end
1198
1208
  end
1199
1209
  searcher.resume
1200
1210
  @searching_prompt = "(reverse-i-search)`': "
1201
- @waiting_proc = ->(key) {
1202
- case key
1211
+ @waiting_proc = ->(k) {
1212
+ case k
1203
1213
  when "\C-j".ord, "\C-?".ord
1204
1214
  if @history_pointer
1205
1215
  @line = Reline::HISTORY[@history_pointer]
@@ -1219,14 +1229,25 @@ class Reline::LineEditor
1219
1229
  @cursor_max = calculate_width(@line)
1220
1230
  @cursor = @byte_pointer = 0
1221
1231
  else
1222
- chr = key.is_a?(String) ? key : key.chr(Encoding::ASCII_8BIT)
1223
- if chr.match?(/[[:print:]]/)
1224
- searcher.resume(key)
1232
+ chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
1233
+ if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == 127
1234
+ searcher.resume(k)
1225
1235
  else
1226
1236
  if @history_pointer
1227
- @line = Reline::HISTORY[@history_pointer]
1237
+ line = Reline::HISTORY[@history_pointer]
1228
1238
  else
1229
- @line = @line_backup_in_history
1239
+ line = @line_backup_in_history
1240
+ end
1241
+ if @is_multiline
1242
+ @line_backup_in_history = whole_buffer
1243
+ @buffer_of_lines = line.split("\n")
1244
+ @buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
1245
+ @line_index = @buffer_of_lines.size - 1
1246
+ @line = @buffer_of_lines.last
1247
+ @rerender_all = true
1248
+ else
1249
+ @line_backup_in_history = @line
1250
+ @line = line
1230
1251
  end
1231
1252
  @searching_prompt = nil
1232
1253
  @waiting_proc = nil
@@ -1279,7 +1300,7 @@ class Reline::LineEditor
1279
1300
  @line = Reline::HISTORY[@history_pointer]
1280
1301
  end
1281
1302
  end
1282
- if @config.editing_mode_is?(:emacs)
1303
+ if @config.editing_mode_is?(:emacs, :vi_insert)
1283
1304
  @cursor_max = @cursor = calculate_width(@line)
1284
1305
  @byte_pointer = @line.bytesize
1285
1306
  elsif @config.editing_mode_is?(:vi_command)
@@ -1326,7 +1347,7 @@ class Reline::LineEditor
1326
1347
  end
1327
1348
  end
1328
1349
  @line = '' unless @line
1329
- if @config.editing_mode_is?(:emacs)
1350
+ if @config.editing_mode_is?(:emacs, :vi_insert)
1330
1351
  @cursor_max = @cursor = calculate_width(@line)
1331
1352
  @byte_pointer = @line.bytesize
1332
1353
  elsif @config.editing_mode_is?(:vi_command)
@@ -1863,13 +1884,13 @@ class Reline::LineEditor
1863
1884
  end
1864
1885
 
1865
1886
  private def vi_replace_char(key, arg: 1)
1866
- @waiting_proc = ->(key) {
1887
+ @waiting_proc = ->(k) {
1867
1888
  if arg == 1
1868
1889
  byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
1869
1890
  before = @line.byteslice(0, @byte_pointer)
1870
1891
  remaining_point = @byte_pointer + byte_size
1871
1892
  after = @line.byteslice(remaining_point, @line.size - remaining_point)
1872
- @line = before + key.chr + after
1893
+ @line = before + k.chr + after
1873
1894
  @cursor_max = calculate_width(@line)
1874
1895
  @waiting_proc = nil
1875
1896
  elsif arg > 1
@@ -1880,7 +1901,7 @@ class Reline::LineEditor
1880
1901
  before = @line.byteslice(0, @byte_pointer)
1881
1902
  remaining_point = @byte_pointer + byte_size
1882
1903
  after = @line.byteslice(remaining_point, @line.size - remaining_point)
1883
- replaced = key.chr * arg
1904
+ replaced = k.chr * arg
1884
1905
  @line = before + replaced + after
1885
1906
  @byte_pointer += replaced.bytesize
1886
1907
  @cursor += calculate_width(replaced)
@@ -1941,4 +1962,20 @@ class Reline::LineEditor
1941
1962
  arg -= 1
1942
1963
  vi_join_lines(key, arg: arg) if arg > 0
1943
1964
  end
1965
+
1966
+ private def em_set_mark(key)
1967
+ @mark_pointer = [@byte_pointer, @line_index]
1968
+ end
1969
+ alias_method :set_mark, :em_set_mark
1970
+
1971
+ private def em_exchange_mark(key)
1972
+ new_pointer = [@byte_pointer, @line_index]
1973
+ @previous_line_index = @line_index
1974
+ @byte_pointer, @line_index = @mark_pointer
1975
+ @byte_pointer, @line_index = @mark_pointer
1976
+ @cursor = calculate_width(@line.byteslice(0, @byte_pointer))
1977
+ @cursor_max = calculate_width(@line)
1978
+ @mark_pointer = new_pointer
1979
+ end
1980
+ alias_method :exchange_point_and_mark, :em_exchange_mark
1944
1981
  end
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.7'
3
3
  end
@@ -9,47 +9,61 @@ class Reline::Windows
9
9
  [224, 83] => :key_delete, # Del
10
10
  [224, 71] => :ed_move_to_beg, # Home
11
11
  [224, 79] => :ed_move_to_end, # End
12
- }.each_key(&:freeze).freeze
13
-
14
- class Win32API
15
- DLL = {}
16
- TYPEMAP = {"0" => Fiddle::TYPE_VOID, "S" => Fiddle::TYPE_VOIDP, "I" => Fiddle::TYPE_LONG}
17
- POINTER_TYPE = Fiddle::SIZEOF_VOIDP == Fiddle::SIZEOF_LONG_LONG ? 'q*' : 'l!*'
18
-
19
- WIN32_TYPES = "VPpNnLlIi"
20
- DL_TYPES = "0SSI"
21
-
22
- def initialize(dllname, func, import, export = "0", calltype = :stdcall)
23
- @proto = [import].join.tr(WIN32_TYPES, DL_TYPES).sub(/^(.)0*$/, '\1')
24
- import = @proto.chars.map {|win_type| TYPEMAP[win_type.tr(WIN32_TYPES, DL_TYPES)]}
25
- export = TYPEMAP[export.tr(WIN32_TYPES, DL_TYPES)]
26
- calltype = Fiddle::Importer.const_get(:CALL_TYPE_TO_ABI)[calltype]
27
-
28
- handle = DLL[dllname] ||=
29
- begin
30
- Fiddle.dlopen(dllname)
31
- rescue Fiddle::DLError
32
- raise unless File.extname(dllname).empty?
33
- Fiddle.dlopen(dllname + ".dll")
34
- end
35
-
36
- @func = Fiddle::Function.new(handle[func], import, export, calltype)
37
- rescue Fiddle::DLError => e
38
- raise LoadError, e.message, e.backtrace
39
- end
12
+ [ 0, 41] => :ed_unassigned, # input method on/off
13
+ [ 0, 72] => :ed_prev_history, # ↑
14
+ [ 0, 80] => :ed_next_history, # ↓
15
+ [ 0, 77] => :ed_next_char, # →
16
+ [ 0, 75] => :ed_prev_char, #
17
+ [ 0, 83] => :key_delete, # Del
18
+ [ 0, 71] => :ed_move_to_beg, # Home
19
+ [ 0, 79] => :ed_move_to_end # End
20
+ }
21
+
22
+ if defined? JRUBY_VERSION
23
+ require 'win32api'
24
+ else
25
+ class Win32API
26
+ DLL = {}
27
+ TYPEMAP = {"0" => Fiddle::TYPE_VOID, "S" => Fiddle::TYPE_VOIDP, "I" => Fiddle::TYPE_LONG}
28
+ POINTER_TYPE = Fiddle::SIZEOF_VOIDP == Fiddle::SIZEOF_LONG_LONG ? 'q*' : 'l!*'
29
+
30
+ WIN32_TYPES = "VPpNnLlIi"
31
+ DL_TYPES = "0SSI"
32
+
33
+ def initialize(dllname, func, import, export = "0", calltype = :stdcall)
34
+ @proto = [import].join.tr(WIN32_TYPES, DL_TYPES).sub(/^(.)0*$/, '\1')
35
+ import = @proto.chars.map {|win_type| TYPEMAP[win_type.tr(WIN32_TYPES, DL_TYPES)]}
36
+ export = TYPEMAP[export.tr(WIN32_TYPES, DL_TYPES)]
37
+ calltype = Fiddle::Importer.const_get(:CALL_TYPE_TO_ABI)[calltype]
38
+
39
+ handle = DLL[dllname] ||=
40
+ begin
41
+ Fiddle.dlopen(dllname)
42
+ rescue Fiddle::DLError
43
+ raise unless File.extname(dllname).empty?
44
+ Fiddle.dlopen(dllname + ".dll")
45
+ end
46
+
47
+ @func = Fiddle::Function.new(handle[func], import, export, calltype)
48
+ rescue Fiddle::DLError => e
49
+ raise LoadError, e.message, e.backtrace
50
+ end
40
51
 
41
- def call(*args)
42
- import = @proto.split("")
43
- args.each_with_index do |x, i|
44
- args[i], = [x == 0 ? nil : x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
45
- args[i], = [x].pack("I").unpack("i") if import[i] == "I"
52
+ def call(*args)
53
+ import = @proto.split("")
54
+ args.each_with_index do |x, i|
55
+ args[i], = [x == 0 ? nil : x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
56
+ args[i], = [x].pack("I").unpack("i") if import[i] == "I"
57
+ end
58
+ ret, = @func.call(*args)
59
+ return ret || 0
46
60
  end
47
- ret, = @func.call(*args)
48
- return ret || 0
49
61
  end
50
62
  end
51
63
 
52
64
  VK_MENU = 0x12
65
+ VK_LMENU = 0xA4
66
+ VK_CONTROL = 0x11
53
67
  VK_SHIFT = 0x10
54
68
  STD_INPUT_HANDLE = -10
55
69
  STD_OUTPUT_HANDLE = -11
@@ -66,23 +80,33 @@ class Reline::Windows
66
80
  @@hConsoleInputHandle = @@GetStdHandle.call(STD_INPUT_HANDLE)
67
81
  @@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L')
68
82
  @@ReadConsoleInput = Win32API.new('kernel32', 'ReadConsoleInput', ['L', 'P', 'L', 'P'], 'L')
69
- @@buf = []
83
+ @@input_buf = []
84
+ @@output_buf = []
70
85
 
71
86
  def self.getwch
87
+ unless @@input_buf.empty?
88
+ return @@input_buf.shift
89
+ end
72
90
  while @@kbhit.call == 0
73
91
  sleep(0.001)
74
92
  end
75
- result = []
76
93
  until @@kbhit.call == 0
77
94
  ret = @@getwch.call
95
+ if ret == 0 or ret == 0xE0
96
+ @@input_buf << ret
97
+ ret = @@getwch.call
98
+ @@input_buf << ret
99
+ return @@input_buf.shift
100
+ end
78
101
  begin
79
- result.concat(ret.chr(Encoding::UTF_8).encode(Encoding.default_external).bytes)
102
+ bytes = ret.chr(Encoding::UTF_8).encode(Encoding.default_external).bytes
103
+ @@input_buf.push(*bytes)
80
104
  rescue Encoding::UndefinedConversionError
81
- result << ret
82
- result << @@getwch.call if ret == 224
105
+ @@input_buf << ret
106
+ @@input_buf << @@getwch.call if ret == 224
83
107
  end
84
108
  end
85
- result
109
+ @@input_buf.shift
86
110
  end
87
111
 
88
112
  def self.getc
@@ -97,44 +121,44 @@ class Reline::Windows
97
121
  end
98
122
  end
99
123
  end
100
- unless @@buf.empty?
101
- return @@buf.shift
124
+ unless @@output_buf.empty?
125
+ return @@output_buf.shift
102
126
  end
103
127
  input = getwch
104
- alt = (@@GetKeyState.call(VK_MENU) & 0x80) != 0
105
- shift_enter = (@@GetKeyState.call(VK_SHIFT) & 0x80) != 0 && input.first == 0x0D
106
- if shift_enter
128
+ meta = (@@GetKeyState.call(VK_LMENU) & 0x80) != 0
129
+ control = (@@GetKeyState.call(VK_CONTROL) & 0x80) != 0
130
+ shift = (@@GetKeyState.call(VK_SHIFT) & 0x80) != 0
131
+ force_enter = !input.instance_of?(Array) && (control or shift) && input == 0x0D
132
+ if force_enter
107
133
  # It's treated as Meta+Enter on Windows
108
- @@buf.concat(["\e".ord])
109
- @@buf.concat(input)
110
- elsif input.size > 1
111
- @@buf.concat(input)
112
- else # single byte
113
- case input[0]
134
+ @@output_buf.push("\e".ord)
135
+ @@output_buf.push(input)
136
+ else
137
+ case input
114
138
  when 0x00
115
- getwch
116
- alt = false
139
+ meta = false
140
+ @@output_buf.push(input)
117
141
  input = getwch
118
- @@buf.concat(input)
142
+ @@output_buf.push(*input)
119
143
  when 0xE0
120
- @@buf.concat(input)
144
+ @@output_buf.push(input)
121
145
  input = getwch
122
- @@buf.concat(input)
146
+ @@output_buf.push(*input)
123
147
  when 0x03
124
- @@buf.concat(input)
148
+ @@output_buf.push(input)
125
149
  else
126
- @@buf.concat(input)
150
+ @@output_buf.push(input)
127
151
  end
128
152
  end
129
- if alt
153
+ if meta
130
154
  "\e".ord
131
155
  else
132
- @@buf.shift
156
+ @@output_buf.shift
133
157
  end
134
158
  end
135
159
 
136
160
  def self.ungetc(c)
137
- @@buf.unshift(c)
161
+ @@output_buf.unshift(c)
138
162
  end
139
163
 
140
164
  def self.get_screen_size
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.0.2
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-01 00:00:00.000000000 Z
11
+ date: 2019-11-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler