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

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: 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