reline 0.2.8.pre.7 → 0.2.8.pre.8

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: c109228f50bec62ad64f9231151865fc6767b95ea5e714c254f2ce8f4480d3e6
4
- data.tar.gz: 8fe386600dca93bb0fce22c72a628392840ad0922d5285afedd5542bdb9e1f3b
3
+ metadata.gz: 6f03d46c602265d3a197f00021df365a9686e4fd0b5d0ef2a037a9ad25143b91
4
+ data.tar.gz: e97bfb610d3c9498e651b25de97e87380e624b56ea970ab7048b25dca7be9488
5
5
  SHA512:
6
- metadata.gz: c1652f7c8c852328fe3371e8881e1fdf13e0c41c4e34894e08c36c15c7521f33035bdfad34cdf0264a3ef4bdf23e1cb45a4a25e3231f592831dd6e8fb6cbeb8f
7
- data.tar.gz: 9aadb3ba73cd2c8ca051e0310d297dc76e6a7a210c4048620c143bf76afce3b7a780f642d2f8167894978301daa82c7229fdd443718ba25a14e2970ead7f9702
6
+ metadata.gz: be1a180b58930985ea7ba5d63be81b62c8ffb0b960daf33c5ad984652fa6021151519cc1fa41055c240d7675d628c6749ab69ca67c2c2bfa306a9deab8200c56
7
+ data.tar.gz: 68be299c9815a350c8afec2c5b27aa02905c8fcc04c6fbba331aded2aded1d0dc160d2e8b01be58a91f308b1ad1dd880209e237de770002422910c6014ddc51a
data/lib/reline/config.rb CHANGED
@@ -50,6 +50,7 @@ class Reline::Config
50
50
  @additional_key_bindings[:emacs] = {}
51
51
  @additional_key_bindings[:vi_insert] = {}
52
52
  @additional_key_bindings[:vi_command] = {}
53
+ @oneshot_key_bindings = {}
53
54
  @skip_section = nil
54
55
  @if_stack = nil
55
56
  @editing_mode_label = :emacs
@@ -75,6 +76,7 @@ class Reline::Config
75
76
  @additional_key_bindings.keys.each do |key|
76
77
  @additional_key_bindings[key].clear
77
78
  end
79
+ @oneshot_key_bindings.clear
78
80
  reset_default_key_bindings
79
81
  end
80
82
 
@@ -128,8 +130,12 @@ class Reline::Config
128
130
  return home_rc_path
129
131
  end
130
132
 
133
+ private def default_inputrc_path
134
+ @default_inputrc_path ||= inputrc_path
135
+ end
136
+
131
137
  def read(file = nil)
132
- file ||= inputrc_path
138
+ file ||= default_inputrc_path
133
139
  begin
134
140
  if file.respond_to?(:readlines)
135
141
  lines = file.readlines
@@ -149,7 +155,15 @@ class Reline::Config
149
155
 
150
156
  def key_bindings
151
157
  # override @key_actors[@editing_mode_label].default_key_bindings with @additional_key_bindings[@editing_mode_label]
152
- @key_actors[@editing_mode_label].default_key_bindings.merge(@additional_key_bindings[@editing_mode_label])
158
+ @key_actors[@editing_mode_label].default_key_bindings.merge(@additional_key_bindings[@editing_mode_label]).merge(@oneshot_key_bindings)
159
+ end
160
+
161
+ def add_oneshot_key_binding(keystroke, target)
162
+ @oneshot_key_bindings[keystroke] = target
163
+ end
164
+
165
+ def reset_oneshot_key_bindings
166
+ @oneshot_key_bindings.clear
153
167
  end
154
168
 
155
169
  def add_default_key_binding_by_keymap(keymap, keystroke, target)
@@ -1,8 +1,59 @@
1
1
  class Reline::KeyStroke
2
2
  using Module.new {
3
+ refine Integer do
4
+ def ==(other)
5
+ if other.is_a?(Reline::Key)
6
+ if other.combined_char == "\e".ord
7
+ false
8
+ else
9
+ other.combined_char == self
10
+ end
11
+ else
12
+ super
13
+ end
14
+ end
15
+ end
16
+
3
17
  refine Array do
4
18
  def start_with?(other)
5
- other.size <= size && other == self.take(other.size)
19
+ compressed_me = compress_meta_key
20
+ compressed_other = other.compress_meta_key
21
+ i = 0
22
+ loop do
23
+ my_c = compressed_me[i]
24
+ other_c = compressed_other[i]
25
+ other_is_last = (i + 1) == compressed_other.size
26
+ me_is_last = (i + 1) == compressed_me.size
27
+ if my_c != other_c
28
+ if other_c == "\e".ord and other_is_last and my_c.is_a?(Reline::Key) and my_c.with_meta
29
+ return true
30
+ else
31
+ return false
32
+ end
33
+ elsif other_is_last
34
+ return true
35
+ elsif me_is_last
36
+ return false
37
+ end
38
+ i += 1
39
+ end
40
+ end
41
+
42
+ def ==(other)
43
+ compressed_me = compress_meta_key
44
+ compressed_other = other.compress_meta_key
45
+ compressed_me.size == compressed_other.size and [compressed_me, compressed_other].transpose.all?{ |i| i[0] == i[1] }
46
+ end
47
+
48
+ def compress_meta_key
49
+ inject([]) { |result, key|
50
+ if result.size > 0 and result.last == "\e".ord
51
+ result[result.size - 1] = Reline::Key.new(key, key | 0b10000000, true)
52
+ else
53
+ result << key
54
+ end
55
+ result
56
+ }
6
57
  end
7
58
 
8
59
  def bytes
@@ -19,8 +70,8 @@ class Reline::KeyStroke
19
70
  key_mapping.keys.select { |lhs|
20
71
  lhs.start_with? input
21
72
  }.tap { |it|
22
- return :matched if it.size == 1 && (it.max_by(&:size)&.size&.== input.size)
23
- return :matching if it.size == 1 && (it.max_by(&:size)&.size&.!= input.size)
73
+ return :matched if it.size == 1 && (it[0] == input)
74
+ return :matching if it.size == 1 && (it[0] != input)
24
75
  return :matched if it.max_by(&:size)&.size&.< input.size
25
76
  return :matching if it.size > 1
26
77
  }
@@ -32,7 +83,8 @@ class Reline::KeyStroke
32
83
  end
33
84
 
34
85
  def expand(input)
35
- lhs = key_mapping.keys.select { |item| input.start_with? item }.sort_by(&:size).reverse.first
86
+ input = input.compress_meta_key
87
+ lhs = key_mapping.keys.select { |item| input.start_with? item }.sort_by(&:size).last
36
88
  return input unless lhs
37
89
  rhs = key_mapping[lhs]
38
90
 
@@ -550,8 +550,9 @@ class Reline::LineEditor
550
550
  attr_reader :name, :contents, :width
551
551
  attr_accessor :scroll_top, :column, :vertical_offset, :lines_backup, :trap_key
552
552
 
553
- def initialize(name, proc_scope)
553
+ def initialize(name, config, proc_scope)
554
554
  @name = name
555
+ @config = config
555
556
  @proc_scope = proc_scope
556
557
  @width = nil
557
558
  @scroll_top = 0
@@ -575,13 +576,25 @@ class Reline::LineEditor
575
576
  def call(key)
576
577
  @proc_scope.set_dialog(self)
577
578
  @proc_scope.set_key(key)
578
- @proc_scope.call
579
+ dialog_render_info = @proc_scope.call
580
+ if @trap_key
581
+ if @trap_key.any?{ |i| i.is_a?(Array) } # multiple trap
582
+ @trap_key.each do |t|
583
+ @config.add_oneshot_key_binding(t, @name)
584
+ end
585
+ elsif @trap_key.is_a?(Array)
586
+ @config.add_oneshot_key_binding(@trap_key, @name)
587
+ elsif @trap_key.is_a?(Integer) or @trap_key.is_a?(Reline::Key)
588
+ @config.add_oneshot_key_binding([@trap_key], @name)
589
+ end
590
+ end
591
+ dialog_render_info
579
592
  end
580
593
  end
581
594
 
582
595
  def add_dialog_proc(name, p, context = nil)
583
596
  return if @dialogs.any? { |d| d.name == name }
584
- @dialogs << Dialog.new(name, DialogProcScope.new(self, @config, p, context))
597
+ @dialogs << Dialog.new(name, @config, DialogProcScope.new(self, @config, p, context))
585
598
  end
586
599
 
587
600
  DIALOG_HEIGHT = 20
@@ -661,6 +674,13 @@ class Reline::LineEditor
661
674
  reset_dialog(dialog, old_dialog)
662
675
  move_cursor_down(dialog.vertical_offset)
663
676
  Reline::IOGate.move_cursor_column(dialog.column)
677
+ if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
678
+ bar_max_height = height * 2
679
+ moving_distance = (dialog_render_info.contents.size - height) * 2
680
+ position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
681
+ bar_height = (bar_max_height * ((dialog.contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
682
+ position = ((bar_max_height - bar_height) * position_ratio).floor.to_i
683
+ end
664
684
  dialog.contents.each_with_index do |item, i|
665
685
  if i == pointer
666
686
  bg_color = '45'
@@ -672,10 +692,26 @@ class Reline::LineEditor
672
692
  end
673
693
  end
674
694
  str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, dialog.width), dialog.width)
675
- @output.write "\e[#{bg_color}m#{str}\e[49m"
695
+ @output.write "\e[#{bg_color}m#{str}"
696
+ if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
697
+ @output.write "\e[37m"
698
+ if position <= (i * 2) and (i * 2 + 1) < (position + bar_height)
699
+ @output.write '█'
700
+ elsif position <= (i * 2) and (i * 2) < (position + bar_height)
701
+ @output.write '▀'
702
+ str += ''
703
+ elsif position <= (i * 2 + 1) and (i * 2) < (position + bar_height)
704
+ @output.write '▄'
705
+ else
706
+ @output.write ' '
707
+ end
708
+ @output.write "\e[39m"
709
+ end
710
+ @output.write "\e[49m"
676
711
  Reline::IOGate.move_cursor_column(dialog.column)
677
712
  move_cursor_down(1) if i < (dialog.contents.size - 1)
678
713
  end
714
+ dialog.width += 1 if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
679
715
  Reline::IOGate.move_cursor_column(cursor_column)
680
716
  move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
681
717
  Reline::IOGate.show_cursor
@@ -1478,9 +1514,9 @@ class Reline::LineEditor
1478
1514
 
1479
1515
  def input_key(key)
1480
1516
  @last_key = key
1517
+ @config.reset_oneshot_key_bindings
1481
1518
  @dialogs.each do |dialog|
1482
- # The dialog will intercept the key if trap_key is set.
1483
- if dialog.trap_key and dialog.trap_key.match?(key)
1519
+ if key.char.instance_of?(Symbol) and key.char == dialog.name
1484
1520
  return
1485
1521
  end
1486
1522
  end
@@ -251,6 +251,7 @@ class Reline::LineEditor
251
251
  @in_pasting = false
252
252
  @auto_indent_proc = nil
253
253
  @dialogs = []
254
+ @last_key = nil
254
255
  reset_line
255
256
  end
256
257
 
@@ -512,6 +513,14 @@ class Reline::LineEditor
512
513
  @cursor_pos.y = row
513
514
  end
514
515
 
516
+ def set_key(key)
517
+ @key = key
518
+ end
519
+
520
+ def key
521
+ @key
522
+ end
523
+
515
524
  def cursor_pos
516
525
  @cursor_pos
517
526
  end
@@ -539,7 +548,7 @@ class Reline::LineEditor
539
548
 
540
549
  class Dialog
541
550
  attr_reader :name, :contents, :width
542
- attr_accessor :scroll_top, :column, :vertical_offset, :lines_backup
551
+ attr_accessor :scroll_top, :column, :vertical_offset, :lines_backup, :trap_key
543
552
 
544
553
  def initialize(name, proc_scope)
545
554
  @name = name
@@ -563,8 +572,9 @@ class Reline::LineEditor
563
572
  end
564
573
  end
565
574
 
566
- def call
575
+ def call(key)
567
576
  @proc_scope.set_dialog(self)
577
+ @proc_scope.set_key(key)
568
578
  @proc_scope.call
569
579
  end
570
580
  end
@@ -588,10 +598,11 @@ class Reline::LineEditor
588
598
  private def render_each_dialog(dialog, cursor_column)
589
599
  if @in_pasting
590
600
  dialog.contents = nil
601
+ dialog.trap_key = nil
591
602
  return
592
603
  end
593
604
  dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
594
- dialog_render_info = dialog.call
605
+ dialog_render_info = dialog.call(@last_key)
595
606
  if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
596
607
  dialog.lines_backup = {
597
608
  lines: modify_lines(whole_lines),
@@ -602,13 +613,18 @@ class Reline::LineEditor
602
613
  }
603
614
  clear_each_dialog(dialog)
604
615
  dialog.contents = nil
616
+ dialog.trap_key = nil
605
617
  return
606
618
  end
607
619
  old_dialog = dialog.clone
608
- dialog.width = dialog_render_info.width if dialog_render_info.width
609
- height = dialog_render_info.height || DIALOG_HEIGHT
610
- pointer = dialog_render_info.pointer
611
620
  dialog.contents = dialog_render_info.contents
621
+ pointer = dialog_render_info.pointer
622
+ if dialog_render_info.width
623
+ dialog.width = dialog_render_info.width
624
+ else
625
+ dialog.width = dialog.contents.map { |l| calculate_width(l, true) }.max
626
+ end
627
+ height = dialog_render_info.height || DIALOG_HEIGHT
612
628
  height = dialog.contents.size if dialog.contents.size < height
613
629
  if dialog.contents.size > height
614
630
  if dialog_render_info.pointer
@@ -645,6 +661,13 @@ class Reline::LineEditor
645
661
  reset_dialog(dialog, old_dialog)
646
662
  move_cursor_down(dialog.vertical_offset)
647
663
  Reline::IOGate.move_cursor_column(dialog.column)
664
+ if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
665
+ bar_max_height = height * 2
666
+ moving_distance = (dialog_render_info.contents.size - height) * 2
667
+ position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
668
+ bar_height = (bar_max_height * ((dialog.contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
669
+ position = ((bar_max_height - bar_height) * position_ratio).floor.to_i
670
+ end
648
671
  dialog.contents.each_with_index do |item, i|
649
672
  if i == pointer
650
673
  bg_color = '45'
@@ -656,10 +679,22 @@ class Reline::LineEditor
656
679
  end
657
680
  end
658
681
  str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, dialog.width), dialog.width)
682
+ if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
683
+ if position <= (i * 2) and (i * 2 + 1) < (position + bar_height)
684
+ str += '█'
685
+ elsif position <= (i * 2) and (i * 2) < (position + bar_height)
686
+ str += '▀'
687
+ elsif position <= (i * 2 + 1) and (i * 2) < (position + bar_height)
688
+ str += '▄'
689
+ else
690
+ str += ' '
691
+ end
692
+ end
659
693
  @output.write "\e[#{bg_color}m#{str}\e[49m"
660
694
  Reline::IOGate.move_cursor_column(dialog.column)
661
695
  move_cursor_down(1) if i < (dialog.contents.size - 1)
662
696
  end
697
+ dialog.width += 1 if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
663
698
  Reline::IOGate.move_cursor_column(cursor_column)
664
699
  move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
665
700
  Reline::IOGate.show_cursor
@@ -774,6 +809,7 @@ class Reline::LineEditor
774
809
  end
775
810
 
776
811
  private def clear_each_dialog(dialog)
812
+ dialog.trap_key = nil
777
813
  return unless dialog.contents
778
814
  prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
779
815
  visual_lines = []
@@ -1460,6 +1496,13 @@ class Reline::LineEditor
1460
1496
  end
1461
1497
 
1462
1498
  def input_key(key)
1499
+ @last_key = key
1500
+ @dialogs.each do |dialog|
1501
+ # The dialog will intercept the key if trap_key is set.
1502
+ if dialog.trap_key and dialog.trap_key.match?(key)
1503
+ return
1504
+ end
1505
+ end
1463
1506
  @just_cursor_moving = nil
1464
1507
  if key.char.nil?
1465
1508
  if @first_char
@@ -101,9 +101,9 @@ class Reline::Unicode
101
101
 
102
102
  def self.get_mbchar_width(mbchar)
103
103
  ord = mbchar.ord
104
- if (0x00 <= ord and ord <= 0x1F)
104
+ if (0x00 <= ord and ord <= 0x1F) # in EscapedPairs
105
105
  return 2
106
- elsif (0x20 <= ord and ord <= 0x7E)
106
+ elsif (0x20 <= ord and ord <= 0x7E) # printable ASCII chars
107
107
  return 1
108
108
  end
109
109
  m = mbchar.encode(Encoding::UTF_8).match(MBCharWidthRE)
@@ -185,7 +185,7 @@ class Reline::Unicode
185
185
  [lines, height]
186
186
  end
187
187
 
188
- # Take a chunk of a String with escape sequences.
188
+ # Take a chunk of a String cut by width with escape sequences.
189
189
  def self.take_range(str, start_col, max_width, encoding = str.encoding)
190
190
  chunk = String.new(encoding: encoding)
191
191
  total_width = 0
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.2.8.pre.7'
2
+ VERSION = '0.2.8.pre.8'
3
3
  end
data/lib/reline.rb CHANGED
@@ -18,13 +18,26 @@ module Reline
18
18
 
19
19
  Key = Struct.new('Key', :char, :combined_char, :with_meta) do
20
20
  def match?(key)
21
- (key.char.nil? or char.nil? or char == key.char) and
22
- (key.combined_char.nil? or combined_char.nil? or combined_char == key.combined_char) and
23
- (key.with_meta.nil? or with_meta.nil? or with_meta == key.with_meta)
21
+ if key.instance_of?(Reline::Key)
22
+ (key.char.nil? or char.nil? or char == key.char) and
23
+ (key.combined_char.nil? or combined_char.nil? or combined_char == key.combined_char) and
24
+ (key.with_meta.nil? or with_meta.nil? or with_meta == key.with_meta)
25
+ elsif key.is_a?(Integer) or key.is_a?(Symbol)
26
+ if not combined_char.nil? and combined_char == key
27
+ true
28
+ elsif combined_char.nil? and not char.nil? and char == key
29
+ true
30
+ else
31
+ false
32
+ end
33
+ else
34
+ false
35
+ end
24
36
  end
37
+ alias_method :==, :match?
25
38
  end
26
39
  CursorPos = Struct.new(:x, :y)
27
- DialogRenderInfo = Struct.new(:pos, :contents, :pointer, :bg_color, :width, :height, keyword_init: true)
40
+ DialogRenderInfo = Struct.new(:pos, :contents, :pointer, :bg_color, :width, :height, :scrollbar, keyword_init: true)
28
41
 
29
42
  class Core
30
43
  ATTR_READER_NAMES = %i(
@@ -228,7 +241,7 @@ module Reline
228
241
  context.clear
229
242
  context.push(cursor_pos_to_render, result, pointer, dialog)
230
243
  end
231
- DialogRenderInfo.new(pos: cursor_pos_to_render, contents: result, pointer: pointer, height: 15)
244
+ DialogRenderInfo.new(pos: cursor_pos_to_render, contents: result, pointer: pointer, scrollbar: true, height: 15)
232
245
  }
233
246
  Reline::DEFAULT_DIALOG_CONTEXT = Array.new
234
247
 
@@ -368,25 +381,9 @@ module Reline
368
381
  break
369
382
  when :matching
370
383
  if buffer.size == 1
371
- begin
372
- succ_c = nil
373
- Timeout.timeout(keyseq_timeout / 1000.0) {
374
- succ_c = Reline::IOGate.getc
375
- }
376
- rescue Timeout::Error # cancel matching only when first byte
377
- block.([Reline::Key.new(c, c, false)])
378
- break
379
- else
380
- if key_stroke.match_status(buffer.dup.push(succ_c)) == :unmatched
381
- if c == "\e".ord
382
- block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
383
- else
384
- block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
385
- end
386
- break
387
- else
388
- Reline::IOGate.ungetc(succ_c)
389
- end
384
+ case read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
385
+ when :break then break
386
+ when :next then next
390
387
  end
391
388
  end
392
389
  when :unmatched
@@ -403,6 +400,38 @@ module Reline
403
400
  end
404
401
  end
405
402
 
403
+ private def read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
404
+ begin
405
+ succ_c = nil
406
+ Timeout.timeout(keyseq_timeout / 1000.0) {
407
+ succ_c = Reline::IOGate.getc
408
+ }
409
+ rescue Timeout::Error # cancel matching only when first byte
410
+ block.([Reline::Key.new(c, c, false)])
411
+ return :break
412
+ else
413
+ case key_stroke.match_status(buffer.dup.push(succ_c))
414
+ when :unmatched
415
+ if c == "\e".ord
416
+ block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
417
+ else
418
+ block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
419
+ end
420
+ return :break
421
+ when :matching
422
+ Reline::IOGate.ungetc(succ_c)
423
+ return :next
424
+ when :matched
425
+ buffer << succ_c
426
+ expanded = key_stroke.expand(buffer).map{ |expanded_c|
427
+ Reline::Key.new(expanded_c, expanded_c, false)
428
+ }
429
+ block.(expanded)
430
+ return :break
431
+ end
432
+ end
433
+ end
434
+
406
435
  private def read_escaped_key(keyseq_timeout, c, block)
407
436
  begin
408
437
  escaped_c = nil
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.8.pre.7
4
+ version: 0.2.8.pre.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-03 00:00:00.000000000 Z
11
+ date: 2021-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console