reline 0.5.8 → 0.5.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -56,51 +56,26 @@ class Reline::Unicode
56
56
 
57
57
  require 'reline/unicode/east_asian_width'
58
58
 
59
- HalfwidthDakutenHandakuten = /[\u{FF9E}\u{FF9F}]/
60
-
61
- MBCharWidthRE = /
62
- (?<width_2_1>
63
- [#{ EscapedChars.map {|c| "\\x%02x" % c.ord }.join }] (?# ^ + char, such as ^M, ^H, ^[, ...)
64
- )
65
- | (?<width_3>^\u{2E3B}) (?# THREE-EM DASH)
66
- | (?<width_0>^\p{M})
67
- | (?<width_2_2>
68
- #{ EastAsianWidth::TYPE_F }
69
- | #{ EastAsianWidth::TYPE_W }
70
- )
71
- | (?<width_1>
72
- #{ EastAsianWidth::TYPE_H }
73
- | #{ EastAsianWidth::TYPE_NA }
74
- | #{ EastAsianWidth::TYPE_N }
75
- )(?!#{ HalfwidthDakutenHandakuten })
76
- | (?<width_2_3>
77
- (?: #{ EastAsianWidth::TYPE_H }
78
- | #{ EastAsianWidth::TYPE_NA }
79
- | #{ EastAsianWidth::TYPE_N })
80
- #{ HalfwidthDakutenHandakuten }
81
- )
82
- | (?<ambiguous_width>
83
- #{EastAsianWidth::TYPE_A}
84
- )
85
- /x
86
-
87
59
  def self.get_mbchar_width(mbchar)
88
60
  ord = mbchar.ord
89
- if (0x00 <= ord and ord <= 0x1F) # in EscapedPairs
61
+ if ord <= 0x1F # in EscapedPairs
90
62
  return 2
91
- elsif (0x20 <= ord and ord <= 0x7E) # printable ASCII chars
63
+ elsif ord <= 0x7E # printable ASCII chars
92
64
  return 1
93
65
  end
94
- m = mbchar.encode(Encoding::UTF_8).match(MBCharWidthRE)
95
- case
96
- when m.nil? then 1 # TODO should be U+FFFD � REPLACEMENT CHARACTER
97
- when m[:width_2_1], m[:width_2_2], m[:width_2_3] then 2
98
- when m[:width_3] then 3
99
- when m[:width_0] then 0
100
- when m[:width_1] then 1
101
- when m[:ambiguous_width] then Reline.ambiguous_width
66
+ utf8_mbchar = mbchar.encode(Encoding::UTF_8)
67
+ ord = utf8_mbchar.ord
68
+ chunk_index = EastAsianWidth::CHUNK_LAST.bsearch_index { |o| ord <= o }
69
+ size = EastAsianWidth::CHUNK_WIDTH[chunk_index]
70
+ if size == -1
71
+ Reline.ambiguous_width
72
+ elsif size == 1 && utf8_mbchar.size >= 2
73
+ second_char_ord = utf8_mbchar[1].ord
74
+ # Halfwidth Dakuten Handakuten
75
+ # Only these two character has Letter Modifier category and can be combined in a single grapheme cluster
76
+ (second_char_ord == 0xFF9E || second_char_ord == 0xFF9F) ? 2 : 1
102
77
  else
103
- nil
78
+ size
104
79
  end
105
80
  end
106
81
 
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.5.8'
2
+ VERSION = '0.5.10'
3
3
  end
data/lib/reline.rb CHANGED
@@ -7,6 +7,7 @@ require 'reline/key_stroke'
7
7
  require 'reline/line_editor'
8
8
  require 'reline/history'
9
9
  require 'reline/terminfo'
10
+ require 'reline/io'
10
11
  require 'reline/face'
11
12
  require 'rbconfig'
12
13
 
@@ -18,20 +19,10 @@ module Reline
18
19
  class ConfigEncodingConversionError < StandardError; end
19
20
 
20
21
  Key = Struct.new(:char, :combined_char, :with_meta) do
21
- def match?(other)
22
- case other
23
- when Reline::Key
24
- (other.char.nil? or char.nil? or char == other.char) and
25
- (other.combined_char.nil? or combined_char.nil? or combined_char == other.combined_char) and
26
- (other.with_meta.nil? or with_meta.nil? or with_meta == other.with_meta)
27
- when Integer, Symbol
28
- (combined_char and combined_char == other) or
29
- (combined_char.nil? and char and char == other)
30
- else
31
- false
32
- end
22
+ # For dialog_proc `key.match?(dialog.name)`
23
+ def match?(sym)
24
+ combined_char.is_a?(Symbol) && combined_char == sym
33
25
  end
34
- alias_method :==, :match?
35
26
  end
36
27
  CursorPos = Struct.new(:x, :y)
37
28
  DialogRenderInfo = Struct.new(
@@ -263,7 +254,6 @@ module Reline
263
254
  raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
264
255
  end
265
256
 
266
- Reline.update_iogate
267
257
  io_gate.with_raw_input do
268
258
  inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
269
259
  end
@@ -286,7 +276,6 @@ module Reline
286
276
 
287
277
  def readline(prompt = '', add_hist = false)
288
278
  @mutex.synchronize do
289
- Reline.update_iogate
290
279
  io_gate.with_raw_input do
291
280
  inner_readline(prompt, add_hist, false)
292
281
  end
@@ -335,14 +324,17 @@ module Reline
335
324
  line_editor.prompt_proc = prompt_proc
336
325
  line_editor.auto_indent_proc = auto_indent_proc
337
326
  line_editor.dig_perfect_match_proc = dig_perfect_match_proc
327
+
328
+ # Readline calls pre_input_hook just after printing the first prompt.
329
+ line_editor.print_nomultiline_prompt
338
330
  pre_input_hook&.call
339
- unless Reline::IOGate == Reline::GeneralIO
331
+
332
+ unless Reline::IOGate.dumb?
340
333
  @dialog_proc_list.each_pair do |name_sym, d|
341
334
  line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context)
342
335
  end
343
336
  end
344
337
 
345
- line_editor.print_nomultiline_prompt(prompt)
346
338
  line_editor.update_dialogs
347
339
  line_editor.rerender
348
340
 
@@ -354,7 +346,7 @@ module Reline
354
346
  inputs.each do |key|
355
347
  if key.char == :bracketed_paste_start
356
348
  text = io_gate.read_bracketed_paste
357
- line_editor.insert_pasted_text(text)
349
+ line_editor.insert_multiline_text(text)
358
350
  line_editor.scroll_into_view
359
351
  else
360
352
  line_editor.update(key)
@@ -378,92 +370,39 @@ module Reline
378
370
  end
379
371
  end
380
372
 
381
- # GNU Readline waits for "keyseq-timeout" milliseconds to see if the ESC
382
- # is followed by a character, and times out and treats it as a standalone
383
- # ESC if the second character does not arrive. If the second character
384
- # comes before timed out, it is treated as a modifier key with the
385
- # meta-property of meta-key, so that it can be distinguished from
386
- # multibyte characters with the 8th bit turned on.
387
- #
388
- # GNU Readline will wait for the 2nd character with "keyseq-timeout"
389
- # milli-seconds but wait forever after 3rd characters.
373
+ # GNU Readline watis for "keyseq-timeout" milliseconds when the input is
374
+ # ambiguous whether it is matching or matched.
375
+ # If the next character does not arrive within the specified timeout, input
376
+ # is considered as matched.
377
+ # `ESC` is ambiguous because it can be a standalone ESC (matched) or part of
378
+ # `ESC char` or part of CSI sequence (matching).
390
379
  private def read_io(keyseq_timeout, &block)
391
380
  buffer = []
381
+ status = KeyStroke::MATCHING
392
382
  loop do
393
- c = io_gate.getc(Float::INFINITY)
394
- if c == -1
395
- result = :unmatched
396
- else
397
- buffer << c
398
- result = key_stroke.match_status(buffer)
399
- end
400
- case result
401
- when :matched
402
- expanded = key_stroke.expand(buffer).map{ |expanded_c|
403
- Reline::Key.new(expanded_c, expanded_c, false)
404
- }
405
- block.(expanded)
406
- break
407
- when :matching
408
- if buffer.size == 1
409
- case read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
410
- when :break then break
411
- when :next then next
412
- end
413
- end
414
- when :unmatched
415
- if buffer.size == 1 and c == "\e".ord
416
- read_escaped_key(keyseq_timeout, c, block)
383
+ timeout = status == KeyStroke::MATCHING_MATCHED ? keyseq_timeout.fdiv(1000) : Float::INFINITY
384
+ c = io_gate.getc(timeout)
385
+ if c.nil? || c == -1
386
+ if status == KeyStroke::MATCHING_MATCHED
387
+ status = KeyStroke::MATCHED
388
+ elsif buffer.empty?
389
+ # io_gate is closed and reached EOF
390
+ block.call([Key.new(nil, nil, false)])
391
+ return
417
392
  else
418
- expanded = buffer.map{ |expanded_c|
419
- Reline::Key.new(expanded_c, expanded_c, false)
420
- }
421
- block.(expanded)
393
+ status = KeyStroke::UNMATCHED
422
394
  end
423
- break
395
+ else
396
+ buffer << c
397
+ status = key_stroke.match_status(buffer)
424
398
  end
425
- end
426
- end
427
399
 
428
- private def read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
429
- succ_c = io_gate.getc(keyseq_timeout.fdiv(1000))
430
- if succ_c
431
- case key_stroke.match_status(buffer.dup.push(succ_c))
432
- when :unmatched
433
- if c == "\e".ord
434
- block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
435
- else
436
- block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
437
- end
438
- return :break
439
- when :matching
440
- io_gate.ungetc(succ_c)
441
- return :next
442
- when :matched
443
- buffer << succ_c
444
- expanded = key_stroke.expand(buffer).map{ |expanded_c|
445
- Reline::Key.new(expanded_c, expanded_c, false)
446
- }
447
- block.(expanded)
448
- return :break
400
+ if status == KeyStroke::MATCHED || status == KeyStroke::UNMATCHED
401
+ expanded, rest_bytes = key_stroke.expand(buffer)
402
+ rest_bytes.reverse_each { |c| io_gate.ungetc(c) }
403
+ block.call(expanded)
404
+ return
449
405
  end
450
- else
451
- block.([Reline::Key.new(c, c, false)])
452
- return :break
453
- end
454
- end
455
-
456
- private def read_escaped_key(keyseq_timeout, c, block)
457
- escaped_c = io_gate.getc(keyseq_timeout.fdiv(1000))
458
-
459
- if escaped_c.nil?
460
- block.([Reline::Key.new(c, c, false)])
461
- elsif escaped_c >= 128 # maybe, first byte of multi byte
462
- block.([Reline::Key.new(c, c, false), Reline::Key.new(escaped_c, escaped_c, false)])
463
- elsif escaped_c == "\e".ord # escape twice
464
- block.([Reline::Key.new(c, c, false), Reline::Key.new(c, c, false)])
465
- else
466
- block.([Reline::Key.new(escaped_c, escaped_c | 0b10000000, true)])
467
406
  end
468
407
  end
469
408
 
@@ -473,7 +412,7 @@ module Reline
473
412
  end
474
413
 
475
414
  private def may_req_ambiguous_char_width
476
- @ambiguous_width = 2 if io_gate == Reline::GeneralIO or !STDOUT.tty?
415
+ @ambiguous_width = 2 if io_gate.dumb? || !STDIN.tty? || !STDOUT.tty?
477
416
  return if defined? @ambiguous_width
478
417
  io_gate.move_cursor_column(0)
479
418
  begin
@@ -521,8 +460,8 @@ module Reline
521
460
  def_single_delegator :line_editor, :byte_pointer, :point
522
461
  def_single_delegator :line_editor, :byte_pointer=, :point=
523
462
 
524
- def self.insert_text(*args, &block)
525
- line_editor.insert_text(*args, &block)
463
+ def self.insert_text(text)
464
+ line_editor.insert_multiline_text(text)
526
465
  self
527
466
  end
528
467
 
@@ -567,37 +506,13 @@ module Reline
567
506
  def self.line_editor
568
507
  core.line_editor
569
508
  end
509
+ end
570
510
 
571
- def self.update_iogate
572
- return if core.config.test_mode
573
511
 
574
- # Need to change IOGate when `$stdout.tty?` change from false to true by `$stdout.reopen`
575
- # Example: rails/spring boot the application in non-tty, then run console in tty.
576
- if ENV['TERM'] != 'dumb' && core.io_gate == Reline::GeneralIO && $stdout.tty?
577
- require 'reline/ansi'
578
- remove_const(:IOGate)
579
- const_set(:IOGate, Reline::ANSI)
580
- end
581
- end
582
- end
512
+ Reline::IOGate = Reline::IO.decide_io_gate
583
513
 
584
- require 'reline/general_io'
585
- io = Reline::GeneralIO
586
- unless ENV['TERM'] == 'dumb'
587
- case RbConfig::CONFIG['host_os']
588
- when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
589
- require 'reline/windows'
590
- tty = (io = Reline::Windows).msys_tty?
591
- else
592
- tty = $stdout.tty?
593
- end
594
- end
595
- Reline::IOGate = if tty
596
- require 'reline/ansi'
597
- Reline::ANSI
598
- else
599
- io
600
- end
514
+ # Deprecated
515
+ Reline::GeneralIO = Reline::Dumb.new
601
516
 
602
517
  Reline::Face.load_initial_configs
603
518
 
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reline
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.8
4
+ version: 0.5.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-05-29 00:00:00.000000000 Z
10
+ date: 2024-09-05 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: io-console
@@ -35,13 +34,16 @@ files:
35
34
  - COPYING
36
35
  - README.md
37
36
  - lib/reline.rb
38
- - lib/reline/ansi.rb
39
37
  - lib/reline/config.rb
40
38
  - lib/reline/face.rb
41
- - lib/reline/general_io.rb
42
39
  - lib/reline/history.rb
40
+ - lib/reline/io.rb
41
+ - lib/reline/io/ansi.rb
42
+ - lib/reline/io/dumb.rb
43
+ - lib/reline/io/windows.rb
43
44
  - lib/reline/key_actor.rb
44
45
  - lib/reline/key_actor/base.rb
46
+ - lib/reline/key_actor/composite.rb
45
47
  - lib/reline/key_actor/emacs.rb
46
48
  - lib/reline/key_actor/vi_command.rb
47
49
  - lib/reline/key_actor/vi_insert.rb
@@ -52,7 +54,6 @@ files:
52
54
  - lib/reline/unicode.rb
53
55
  - lib/reline/unicode/east_asian_width.rb
54
56
  - lib/reline/version.rb
55
- - lib/reline/windows.rb
56
57
  - license_of_rb-readline
57
58
  homepage: https://github.com/ruby/reline
58
59
  licenses:
@@ -61,7 +62,6 @@ metadata:
61
62
  bug_tracker_uri: https://github.com/ruby/reline/issues
62
63
  changelog_uri: https://github.com/ruby/reline/releases
63
64
  source_code_uri: https://github.com/ruby/reline
64
- post_install_message:
65
65
  rdoc_options: []
66
66
  require_paths:
67
67
  - lib
@@ -76,8 +76,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
76
76
  - !ruby/object:Gem::Version
77
77
  version: '0'
78
78
  requirements: []
79
- rubygems_version: 3.5.9
80
- signing_key:
79
+ rubygems_version: 3.6.0.dev
81
80
  specification_version: 4
82
81
  summary: Alternative GNU Readline or Editline implementation by pure Ruby.
83
82
  test_files: []
@@ -1,111 +0,0 @@
1
- require 'io/wait'
2
-
3
- class Reline::GeneralIO
4
- RESET_COLOR = '' # Do not send color reset sequence
5
-
6
- def self.reset(encoding: nil)
7
- @@pasting = false
8
- if encoding
9
- @@encoding = encoding
10
- elsif defined?(@@encoding)
11
- remove_class_variable(:@@encoding)
12
- end
13
- end
14
-
15
- def self.encoding
16
- if defined?(@@encoding)
17
- @@encoding
18
- elsif RUBY_PLATFORM =~ /mswin|mingw/
19
- Encoding::UTF_8
20
- else
21
- Encoding::default_external
22
- end
23
- end
24
-
25
- def self.win?
26
- false
27
- end
28
-
29
- def self.set_default_key_bindings(_)
30
- end
31
-
32
- @@buf = []
33
- @@input = STDIN
34
-
35
- def self.input=(val)
36
- @@input = val
37
- end
38
-
39
- def self.with_raw_input
40
- yield
41
- end
42
-
43
- def self.getc(_timeout_second)
44
- unless @@buf.empty?
45
- return @@buf.shift
46
- end
47
- c = nil
48
- loop do
49
- Reline.core.line_editor.handle_signal
50
- result = @@input.wait_readable(0.1)
51
- next if result.nil?
52
- c = @@input.read(1)
53
- break
54
- end
55
- c&.ord
56
- end
57
-
58
- def self.ungetc(c)
59
- @@buf.unshift(c)
60
- end
61
-
62
- def self.get_screen_size
63
- [24, 80]
64
- end
65
-
66
- def self.cursor_pos
67
- Reline::CursorPos.new(1, 1)
68
- end
69
-
70
- def self.hide_cursor
71
- end
72
-
73
- def self.show_cursor
74
- end
75
-
76
- def self.move_cursor_column(val)
77
- end
78
-
79
- def self.move_cursor_up(val)
80
- end
81
-
82
- def self.move_cursor_down(val)
83
- end
84
-
85
- def self.erase_after_cursor
86
- end
87
-
88
- def self.scroll_down(val)
89
- end
90
-
91
- def self.clear_screen
92
- end
93
-
94
- def self.set_screen_size(rows, columns)
95
- end
96
-
97
- def self.set_winch_handler(&handler)
98
- end
99
-
100
- @@pasting = false
101
-
102
- def self.in_pasting?
103
- @@pasting
104
- end
105
-
106
- def self.prep
107
- end
108
-
109
- def self.deprep(otio)
110
- end
111
- end