reline 0.2.8.pre.6 → 0.2.8.pre.10

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: 161818586702ae2834bf5e6490adf5dcec415256925ffd83339ffe6ef3c13012
4
- data.tar.gz: 3ab65900ed4022e1baa2213d560938339711264e3188597c4eb98bc1891c81a8
3
+ metadata.gz: cdcb3b1edc046520bc5be9cbddb21a4ba498fd1111319c90b05f76b066155eaa
4
+ data.tar.gz: f5a5af7fb221dc0fb21aac67baa20af7c159f22ee792b5f9264badb1ff8b6ba4
5
5
  SHA512:
6
- metadata.gz: 610c5367d68b627fb7e0a7bef7919d147b5a19539725f9a2d184bbed69b0988c613acf1f001d2f05857fce2490e7309e666a4327f1f15cd0541e3bb7db5089ec
7
- data.tar.gz: b567f8283fe5637161e21536f500f6824d9a92aadf940e10386d27cf4df29940d3e3d6cb95a7a4fff6e711ac33c73f870ee419b45d44274184160b37354d9d77
6
+ metadata.gz: a40a42eb7fe33b39dbf8d56eae76708caee116462bbe35b537e36b39e0edb8263cd5854f48b8c7ef440b686c5dabb7e34c72ec844c2ab79661797c23a28a9f0f
7
+ data.tar.gz: 9937033c4b7509a3ef39522bc82e574cd6049cfa6204463625bf89ad427ff7c21e36ca3e0a94b11e3b36dfb1cacb2b3a357a3a115e80b13e94eb9512697aea1e
data/lib/reline/ansi.rb CHANGED
@@ -33,11 +33,16 @@ class Reline::ANSI
33
33
  config.add_default_key_binding_by_keymap(:vi_insert, key, func)
34
34
  config.add_default_key_binding_by_keymap(:vi_command, key, func)
35
35
  end
36
+ {
37
+ [27, 91, 90] => :completion_journey_up, # S-Tab
38
+ }.each_pair do |key, func|
39
+ config.add_default_key_binding_by_keymap(:emacs, key, func)
40
+ config.add_default_key_binding_by_keymap(:vi_insert, key, func)
41
+ end
36
42
  {
37
43
  # default bindings
38
44
  [27, 32] => :em_set_mark, # M-<space>
39
45
  [24, 24] => :em_exchange_mark, # C-x C-x
40
- [27, 91, 90] => :completion_journey_up, # S-Tab
41
46
  }.each_pair do |key, func|
42
47
  config.add_default_key_binding_by_keymap(:emacs, key, func)
43
48
  end
@@ -126,8 +131,8 @@ class Reline::ANSI
126
131
  unless @@buf.empty?
127
132
  return @@buf.shift
128
133
  end
129
- until c = @@input.raw(intr: true, &:getbyte)
130
- sleep 0.1
134
+ until c = @@input.raw(intr: true) { select([@@input], [], [], 0.1) && @@input.getbyte }
135
+ Reline.core.line_editor.resize
131
136
  end
132
137
  (c == 0x16 && @@input.raw(min: 0, tim: 0, &:getbyte)) || c
133
138
  rescue Errno::EIO
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
@@ -148,8 +154,19 @@ class Reline::Config
148
154
  end
149
155
 
150
156
  def key_bindings
151
- # 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])
157
+ # The key bindings for each editing mode will be overwritten by the user-defined ones.
158
+ kb = @key_actors[@editing_mode_label].default_key_bindings.dup
159
+ kb.merge!(@additional_key_bindings[@editing_mode_label])
160
+ kb.merge!(@oneshot_key_bindings)
161
+ kb
162
+ end
163
+
164
+ def add_oneshot_key_binding(keystroke, target)
165
+ @oneshot_key_bindings[keystroke] = target
166
+ end
167
+
168
+ def reset_oneshot_key_bindings
169
+ @oneshot_key_bindings.clear
153
170
  end
154
171
 
155
172
  def add_default_key_binding_by_keymap(keymap, keystroke, target)
@@ -43,7 +43,7 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
43
43
  # 20 ^T
44
44
  :ed_transpose_chars,
45
45
  # 21 ^U
46
- :em_kill_line,
46
+ :ed_kill_line,
47
47
  # 22 ^V
48
48
  :ed_quoted_insert,
49
49
  # 23 ^W
@@ -1,38 +1,87 @@
1
1
  class Reline::KeyStroke
2
- using Module.new {
3
- refine Array do
4
- def start_with?(other)
5
- other.size <= size && other == self.take(other.size)
2
+ def initialize(config)
3
+ @config = config
4
+ end
5
+
6
+ def compress_meta_key(ary)
7
+ ary.inject([]) { |result, key|
8
+ if result.size > 0 and result.last == "\e".ord
9
+ result[result.size - 1] = Reline::Key.new(key, key | 0b10000000, true)
10
+ else
11
+ result << key
6
12
  end
13
+ result
14
+ }
15
+ end
7
16
 
8
- def bytes
9
- self
17
+ def start_with?(me, other)
18
+ compressed_me = compress_meta_key(me)
19
+ compressed_other = compress_meta_key(other)
20
+ i = 0
21
+ loop do
22
+ my_c = compressed_me[i]
23
+ other_c = compressed_other[i]
24
+ other_is_last = (i + 1) == compressed_other.size
25
+ me_is_last = (i + 1) == compressed_me.size
26
+ if my_c != other_c
27
+ if other_c == "\e".ord and other_is_last and my_c.is_a?(Reline::Key) and my_c.with_meta
28
+ return true
29
+ else
30
+ return false
31
+ end
32
+ elsif other_is_last
33
+ return true
34
+ elsif me_is_last
35
+ return false
10
36
  end
37
+ i += 1
11
38
  end
12
- }
39
+ end
13
40
 
14
- def initialize(config)
15
- @config = config
41
+ def equal?(me, other)
42
+ case me
43
+ when Array
44
+ compressed_me = compress_meta_key(me)
45
+ compressed_other = compress_meta_key(other)
46
+ compressed_me.size == compressed_other.size and [compressed_me, compressed_other].transpose.all?{ |i| equal?(i[0], i[1]) }
47
+ when Integer
48
+ if other.is_a?(Reline::Key)
49
+ if other.combined_char == "\e".ord
50
+ false
51
+ else
52
+ other.combined_char == me
53
+ end
54
+ else
55
+ me == other
56
+ end
57
+ when Reline::Key
58
+ if other.is_a?(Integer)
59
+ me.combined_char == other
60
+ else
61
+ me == other
62
+ end
63
+ end
16
64
  end
17
65
 
18
66
  def match_status(input)
19
67
  key_mapping.keys.select { |lhs|
20
- lhs.start_with? input
68
+ start_with?(lhs, input)
21
69
  }.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)
70
+ return :matched if it.size == 1 && equal?(it[0], input)
71
+ return :matching if it.size == 1 && !equal?(it[0], input)
24
72
  return :matched if it.max_by(&:size)&.size&.< input.size
25
73
  return :matching if it.size > 1
26
74
  }
27
75
  key_mapping.keys.select { |lhs|
28
- input.start_with? lhs
76
+ start_with?(input, lhs)
29
77
  }.tap { |it|
30
78
  return it.size > 0 ? :matched : :unmatched
31
79
  }
32
80
  end
33
81
 
34
82
  def expand(input)
35
- lhs = key_mapping.keys.select { |item| input.start_with? item }.sort_by(&:size).reverse.first
83
+ input = compress_meta_key(input)
84
+ lhs = key_mapping.keys.select { |item| start_with?(input, item) }.sort_by(&:size).last
36
85
  return input unless lhs
37
86
  rhs = key_mapping[lhs]
38
87
 
@@ -5,6 +5,7 @@ require 'tempfile'
5
5
 
6
6
  class Reline::LineEditor
7
7
  # TODO: undo
8
+ # TODO: Use "private alias_method" idiom after drop Ruby 2.5.
8
9
  attr_reader :line
9
10
  attr_reader :byte_pointer
10
11
  attr_accessor :confirm_multiline_termination_proc
@@ -150,7 +151,7 @@ class Reline::LineEditor
150
151
  @screen_size = Reline::IOGate.get_screen_size
151
152
  @screen_height = @screen_size.first
152
153
  reset_variables(prompt, encoding: encoding)
153
- @old_trap = Signal.trap(:INT) {
154
+ @old_trap = Signal.trap('INT') {
154
155
  clear_dialog
155
156
  if @scroll_partial_screen
156
157
  move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
@@ -170,47 +171,65 @@ class Reline::LineEditor
170
171
  @old_trap.call
171
172
  end
172
173
  }
174
+ begin
175
+ @old_tstp_trap = Signal.trap('TSTP') {
176
+ Reline::IOGate.ungetc("\C-z".ord)
177
+ @old_tstp_trap.call if @old_tstp_trap.respond_to?(:call)
178
+ }
179
+ rescue ArgumentError
180
+ end
173
181
  Reline::IOGate.set_winch_handler do
174
- @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
175
- old_screen_size = @screen_size
176
- @screen_size = Reline::IOGate.get_screen_size
177
- @screen_height = @screen_size.first
178
- if old_screen_size.last < @screen_size.last # columns increase
179
- @rerender_all = true
180
- rerender
181
- else
182
- back = 0
183
- new_buffer = whole_lines
184
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
185
- new_buffer.each_with_index do |line, index|
186
- prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
187
- width = prompt_width + calculate_width(line)
188
- height = calculate_height_by_width(width)
189
- back += height
190
- end
191
- @highest_in_all = back
192
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
193
- @first_line_started_from =
194
- if @line_index.zero?
195
- 0
196
- else
197
- calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
198
- end
199
- if @prompt_proc
200
- prompt = prompt_list[@line_index]
201
- prompt_width = calculate_width(prompt, true)
182
+ @resized = true
183
+ end
184
+ @block_elem_width = Reline::Unicode.calculate_width('█')
185
+ end
186
+
187
+ def resize
188
+ return unless @resized
189
+ @resized = false
190
+ @rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
191
+ old_screen_size = @screen_size
192
+ @screen_size = Reline::IOGate.get_screen_size
193
+ @screen_height = @screen_size.first
194
+ if old_screen_size.last < @screen_size.last # columns increase
195
+ @rerender_all = true
196
+ rerender
197
+ else
198
+ back = 0
199
+ new_buffer = whole_lines
200
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
201
+ new_buffer.each_with_index do |line, index|
202
+ prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
203
+ width = prompt_width + calculate_width(line)
204
+ height = calculate_height_by_width(width)
205
+ back += height
206
+ end
207
+ @highest_in_all = back
208
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
209
+ @first_line_started_from =
210
+ if @line_index.zero?
211
+ 0
212
+ else
213
+ calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
202
214
  end
203
- calculate_nearest_cursor
204
- @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
205
- Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
206
- @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
207
- @rerender_all = true
215
+ if @prompt_proc
216
+ prompt = prompt_list[@line_index]
217
+ prompt_width = calculate_width(prompt, true)
208
218
  end
219
+ calculate_nearest_cursor
220
+ @started_from = calculate_height_by_width(prompt_width + @cursor) - 1
221
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
222
+ @highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
223
+ @rerender_all = true
209
224
  end
210
225
  end
211
226
 
212
227
  def finalize
213
- Signal.trap('SIGINT', @old_trap)
228
+ Signal.trap('INT', @old_trap)
229
+ begin
230
+ Signal.trap('TSTP', @old_tstp_trap)
231
+ rescue ArgumentError
232
+ end
214
233
  end
215
234
 
216
235
  def eof?
@@ -251,6 +270,8 @@ class Reline::LineEditor
251
270
  @in_pasting = false
252
271
  @auto_indent_proc = nil
253
272
  @dialogs = []
273
+ @last_key = nil
274
+ @resized = false
254
275
  reset_line
255
276
  end
256
277
 
@@ -512,6 +533,14 @@ class Reline::LineEditor
512
533
  @cursor_pos.y = row
513
534
  end
514
535
 
536
+ def set_key(key)
537
+ @key = key
538
+ end
539
+
540
+ def key
541
+ @key
542
+ end
543
+
515
544
  def cursor_pos
516
545
  @cursor_pos
517
546
  end
@@ -539,13 +568,15 @@ class Reline::LineEditor
539
568
 
540
569
  class Dialog
541
570
  attr_reader :name, :contents, :width
542
- attr_accessor :scroll_top, :column, :vertical_offset, :lines_backup
571
+ attr_accessor :scroll_top, :scrollbar_pos, :pointer, :column, :vertical_offset, :lines_backup, :trap_key
543
572
 
544
- def initialize(name, proc_scope)
573
+ def initialize(name, config, proc_scope)
545
574
  @name = name
575
+ @config = config
546
576
  @proc_scope = proc_scope
547
577
  @width = nil
548
578
  @scroll_top = 0
579
+ @trap_key = nil
549
580
  end
550
581
 
551
582
  def set_cursor_pos(col, row)
@@ -563,15 +594,28 @@ class Reline::LineEditor
563
594
  end
564
595
  end
565
596
 
566
- def call
597
+ def call(key)
567
598
  @proc_scope.set_dialog(self)
568
- @proc_scope.call
599
+ @proc_scope.set_key(key)
600
+ dialog_render_info = @proc_scope.call
601
+ if @trap_key
602
+ if @trap_key.any?{ |i| i.is_a?(Array) } # multiple trap
603
+ @trap_key.each do |t|
604
+ @config.add_oneshot_key_binding(t, @name)
605
+ end
606
+ elsif @trap_key.is_a?(Array)
607
+ @config.add_oneshot_key_binding(@trap_key, @name)
608
+ elsif @trap_key.is_a?(Integer) or @trap_key.is_a?(Reline::Key)
609
+ @config.add_oneshot_key_binding([@trap_key], @name)
610
+ end
611
+ end
612
+ dialog_render_info
569
613
  end
570
614
  end
571
615
 
572
616
  def add_dialog_proc(name, p, context = nil)
573
617
  return if @dialogs.any? { |d| d.name == name }
574
- @dialogs << Dialog.new(name, DialogProcScope.new(self, @config, p, context))
618
+ @dialogs << Dialog.new(name, @config, DialogProcScope.new(self, @config, p, context))
575
619
  end
576
620
 
577
621
  DIALOG_HEIGHT = 20
@@ -588,10 +632,11 @@ class Reline::LineEditor
588
632
  private def render_each_dialog(dialog, cursor_column)
589
633
  if @in_pasting
590
634
  dialog.contents = nil
635
+ dialog.trap_key = nil
591
636
  return
592
637
  end
593
638
  dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
594
- dialog_render_info = dialog.call
639
+ dialog_render_info = dialog.call(@last_key)
595
640
  if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
596
641
  dialog.lines_backup = {
597
642
  lines: modify_lines(whole_lines),
@@ -602,27 +647,41 @@ class Reline::LineEditor
602
647
  }
603
648
  clear_each_dialog(dialog)
604
649
  dialog.contents = nil
650
+ dialog.trap_key = nil
605
651
  return
606
652
  end
607
653
  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
654
  dialog.contents = dialog_render_info.contents
655
+ pointer = dialog.pointer
656
+ if dialog_render_info.width
657
+ dialog.width = dialog_render_info.width
658
+ else
659
+ dialog.width = dialog.contents.map { |l| calculate_width(l, true) }.max
660
+ end
661
+ height = dialog_render_info.height || DIALOG_HEIGHT
612
662
  height = dialog.contents.size if dialog.contents.size < height
613
663
  if dialog.contents.size > height
614
- if dialog_render_info.pointer
615
- if dialog_render_info.pointer < 0
664
+ if dialog.pointer
665
+ if dialog.pointer < 0
616
666
  dialog.scroll_top = 0
617
- elsif (dialog_render_info.pointer - dialog.scroll_top) >= (height - 1)
618
- dialog.scroll_top = dialog_render_info.pointer - (height - 1)
619
- elsif (dialog_render_info.pointer - dialog.scroll_top) < 0
620
- dialog.scroll_top = dialog_render_info.pointer
667
+ elsif (dialog.pointer - dialog.scroll_top) >= (height - 1)
668
+ dialog.scroll_top = dialog.pointer - (height - 1)
669
+ elsif (dialog.pointer - dialog.scroll_top) < 0
670
+ dialog.scroll_top = dialog.pointer
621
671
  end
622
- pointer = dialog_render_info.pointer - dialog.scroll_top
672
+ pointer = dialog.pointer - dialog.scroll_top
623
673
  end
624
674
  dialog.contents = dialog.contents[dialog.scroll_top, height]
625
675
  end
676
+ if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
677
+ bar_max_height = height * 2
678
+ moving_distance = (dialog_render_info.contents.size - height) * 2
679
+ position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
680
+ bar_height = (bar_max_height * ((dialog.contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
681
+ dialog.scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
682
+ else
683
+ dialog.scrollbar_pos = nil
684
+ end
626
685
  upper_space = @first_line_started_from - @started_from
627
686
  lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
628
687
  dialog.column = dialog_render_info.pos.x
@@ -642,6 +701,7 @@ class Reline::LineEditor
642
701
  dialog.vertical_offset = dialog_render_info.pos.y + 1
643
702
  end
644
703
  Reline::IOGate.hide_cursor
704
+ dialog.width += @block_elem_width if dialog.scrollbar_pos
645
705
  reset_dialog(dialog, old_dialog)
646
706
  move_cursor_down(dialog.vertical_offset)
647
707
  Reline::IOGate.move_cursor_column(dialog.column)
@@ -655,8 +715,23 @@ class Reline::LineEditor
655
715
  bg_color = '46'
656
716
  end
657
717
  end
658
- str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, dialog.width), dialog.width)
659
- @output.write "\e[#{bg_color}m#{str}\e[49m"
718
+ str_width = dialog.width - (dialog.scrollbar_pos.nil? ? 0 : @block_elem_width)
719
+ str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
720
+ @output.write "\e[#{bg_color}m#{str}"
721
+ if dialog.scrollbar_pos and (dialog.scrollbar_pos != old_dialog.scrollbar_pos or dialog.column != old_dialog.column)
722
+ @output.write "\e[37m"
723
+ if dialog.scrollbar_pos <= (i * 2) and (i * 2 + 1) < (dialog.scrollbar_pos + bar_height)
724
+ @output.write '█'
725
+ elsif dialog.scrollbar_pos <= (i * 2) and (i * 2) < (dialog.scrollbar_pos + bar_height)
726
+ @output.write '▀'
727
+ str += ''
728
+ elsif dialog.scrollbar_pos <= (i * 2 + 1) and (i * 2) < (dialog.scrollbar_pos + bar_height)
729
+ @output.write '▄'
730
+ else
731
+ @output.write ' ' * @block_elem_width
732
+ end
733
+ end
734
+ @output.write "\e[0m"
660
735
  Reline::IOGate.move_cursor_column(dialog.column)
661
736
  move_cursor_down(1) if i < (dialog.contents.size - 1)
662
737
  end
@@ -697,12 +772,12 @@ class Reline::LineEditor
697
772
  line_num.times do |i|
698
773
  Reline::IOGate.move_cursor_column(old_dialog.column)
699
774
  if visual_lines[start + i].nil?
700
- s = ' ' * dialog.width
775
+ s = ' ' * old_dialog.width
701
776
  else
702
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, dialog.width)
703
- s = padding_space_with_escape_sequences(s, dialog.width)
777
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
778
+ s = padding_space_with_escape_sequences(s, old_dialog.width)
704
779
  end
705
- @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
780
+ @output.write "\e[0m#{s}\e[0m"
706
781
  move_cursor_down(1) if i < (line_num - 1)
707
782
  end
708
783
  move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
@@ -715,12 +790,12 @@ class Reline::LineEditor
715
790
  line_num.times do |i|
716
791
  Reline::IOGate.move_cursor_column(old_dialog.column)
717
792
  if visual_lines[start + i].nil?
718
- s = ' ' * dialog.width
793
+ s = ' ' * old_dialog.width
719
794
  else
720
- s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, dialog.width)
721
- s = padding_space_with_escape_sequences(s, dialog.width)
795
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
796
+ s = padding_space_with_escape_sequences(s, old_dialog.width)
722
797
  end
723
- @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
798
+ @output.write "\e[0m#{s}\e[0m"
724
799
  move_cursor_down(1) if i < (line_num - 1)
725
800
  end
726
801
  move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
@@ -739,7 +814,7 @@ class Reline::LineEditor
739
814
  s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
740
815
  s = padding_space_with_escape_sequences(s, dialog.width)
741
816
  end
742
- @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
817
+ @output.write "\e[0m#{s}\e[0m"
743
818
  move_cursor_down(1) if i < (line_num - 1)
744
819
  end
745
820
  move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
@@ -759,7 +834,7 @@ class Reline::LineEditor
759
834
  s = padding_space_with_escape_sequences(s, dialog.width)
760
835
  end
761
836
  Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
762
- @output.write "\e[39m\e[49m#{s}\e[39m\e[49m"
837
+ @output.write "\e[0m#{s}\e[0m"
763
838
  move_cursor_down(1) if i < (line_num - 1)
764
839
  end
765
840
  move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
@@ -774,6 +849,7 @@ class Reline::LineEditor
774
849
  end
775
850
 
776
851
  private def clear_each_dialog(dialog)
852
+ dialog.trap_key = nil
777
853
  return unless dialog.contents
778
854
  prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
779
855
  visual_lines = []
@@ -798,10 +874,10 @@ class Reline::LineEditor
798
874
  Reline::IOGate.move_cursor_column(dialog.column)
799
875
  str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
800
876
  str = padding_space_with_escape_sequences(str, dialog.width)
801
- @output.write "\e[39m\e[49m#{str}\e[39m\e[49m"
877
+ @output.write "\e[0m#{str}\e[0m"
802
878
  else
803
879
  Reline::IOGate.move_cursor_column(dialog.column)
804
- @output.write "\e[39m\e[49m#{' ' * dialog.width}\e[39m\e[49m"
880
+ @output.write "\e[0m#{' ' * dialog.width}\e[0m"
805
881
  end
806
882
  move_cursor_down(1) if i < (dialog_vertical_size - 1)
807
883
  end
@@ -1151,7 +1227,7 @@ class Reline::LineEditor
1151
1227
  height = render_partial(prompt, prompt_width, line, back, with_control: false)
1152
1228
  end
1153
1229
  if index < (@buffer_of_lines.size - 1)
1154
- move_cursor_down(height)
1230
+ move_cursor_down(1)
1155
1231
  back += height
1156
1232
  end
1157
1233
  end
@@ -1334,7 +1410,10 @@ class Reline::LineEditor
1334
1410
  end
1335
1411
  @waiting_operator_proc = nil
1336
1412
  @waiting_operator_vi_arg = nil
1337
- @vi_arg = nil
1413
+ if @vi_arg
1414
+ @rerender_all = true
1415
+ @vi_arg = nil
1416
+ end
1338
1417
  else
1339
1418
  block.(false)
1340
1419
  end
@@ -1385,7 +1464,10 @@ class Reline::LineEditor
1385
1464
  wrap_method_call(method_symbol, method_obj, key) if method_obj
1386
1465
  end
1387
1466
  @kill_ring.process
1388
- @vi_arg = nil
1467
+ if @vi_arg
1468
+ @rerender_al = true
1469
+ @vi_arg = nil
1470
+ end
1389
1471
  elsif @vi_arg
1390
1472
  if key.chr =~ /[0-9]/
1391
1473
  ed_argument_digit(key)
@@ -1402,7 +1484,10 @@ class Reline::LineEditor
1402
1484
  ed_insert(key) unless @config.editing_mode_is?(:vi_command)
1403
1485
  end
1404
1486
  @kill_ring.process
1405
- @vi_arg = nil
1487
+ if @vi_arg
1488
+ @rerender_all = true
1489
+ @vi_arg = nil
1490
+ end
1406
1491
  end
1407
1492
  elsif @waiting_proc
1408
1493
  @waiting_proc.(key)
@@ -1460,6 +1545,13 @@ class Reline::LineEditor
1460
1545
  end
1461
1546
 
1462
1547
  def input_key(key)
1548
+ @last_key = key
1549
+ @config.reset_oneshot_key_bindings
1550
+ @dialogs.each do |dialog|
1551
+ if key.char.instance_of?(Symbol) and key.char == dialog.name
1552
+ return
1553
+ end
1554
+ end
1463
1555
  @just_cursor_moving = nil
1464
1556
  if key.char.nil?
1465
1557
  if @first_char
@@ -1834,6 +1926,8 @@ class Reline::LineEditor
1834
1926
  end
1835
1927
  end
1836
1928
 
1929
+ # Editline:: +ed-unassigned+ This editor command always results in an error.
1930
+ # GNU Readline:: There is no corresponding macro.
1837
1931
  private def ed_unassigned(key) end # do nothing
1838
1932
 
1839
1933
  private def process_insert(force: false)
@@ -1851,6 +1945,19 @@ class Reline::LineEditor
1851
1945
  @continuous_insertion_buffer.clear
1852
1946
  end
1853
1947
 
1948
+ # Editline:: +ed-insert+ (vi input: almost all; emacs: printable characters)
1949
+ # In insert mode, insert the input character left of the cursor
1950
+ # position. In replace mode, overwrite the character at the
1951
+ # cursor and move the cursor to the right by one character
1952
+ # position. Accept an argument to do this repeatedly. It is an
1953
+ # error if the input character is the NUL character (+Ctrl-@+).
1954
+ # Failure to enlarge the edit buffer also results in an error.
1955
+ # Editline:: +ed-digit+ (emacs: 0 to 9) If in argument input mode, append
1956
+ # the input digit to the argument being read. Otherwise, call
1957
+ # +ed-insert+. It is an error if the input character is not a
1958
+ # digit or if the existing argument is already greater than a
1959
+ # million.
1960
+ # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
1854
1961
  private def ed_insert(key)
1855
1962
  str = nil
1856
1963
  width = nil
@@ -1901,6 +2008,8 @@ class Reline::LineEditor
1901
2008
  arg.times do
1902
2009
  if key == "\C-j".ord or key == "\C-m".ord
1903
2010
  key_newline(key)
2011
+ elsif key == 0
2012
+ # Ignore NUL.
1904
2013
  else
1905
2014
  ed_insert(key)
1906
2015
  end
@@ -2335,6 +2444,7 @@ class Reline::LineEditor
2335
2444
  arg -= 1
2336
2445
  ed_prev_history(key, arg: arg) if arg > 0
2337
2446
  end
2447
+ alias_method :previous_history, :ed_prev_history
2338
2448
 
2339
2449
  private def ed_next_history(key, arg: 1)
2340
2450
  if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
@@ -2382,6 +2492,7 @@ class Reline::LineEditor
2382
2492
  arg -= 1
2383
2493
  ed_next_history(key, arg: arg) if arg > 0
2384
2494
  end
2495
+ alias_method :next_history, :ed_next_history
2385
2496
 
2386
2497
  private def ed_newline(key)
2387
2498
  process_insert(force: true)
@@ -2416,7 +2527,7 @@ class Reline::LineEditor
2416
2527
  end
2417
2528
  end
2418
2529
 
2419
- private def em_delete_prev_char(key)
2530
+ private def em_delete_prev_char(key, arg: 1)
2420
2531
  if @is_multiline and @cursor == 0 and @line_index > 0
2421
2532
  @buffer_of_lines[@line_index] = @line
2422
2533
  @cursor = calculate_width(@buffer_of_lines[@line_index - 1])
@@ -2434,9 +2545,16 @@ class Reline::LineEditor
2434
2545
  @cursor -= width
2435
2546
  @cursor_max -= width
2436
2547
  end
2548
+ arg -= 1
2549
+ em_delete_prev_char(key, arg: arg) if arg > 0
2437
2550
  end
2438
2551
  alias_method :backward_delete_char, :em_delete_prev_char
2439
2552
 
2553
+ # Editline:: +ed-kill-line+ (vi command: +D+, +Ctrl-K+; emacs: +Ctrl-K+,
2554
+ # +Ctrl-U+) + Kill from the cursor to the end of the line.
2555
+ # GNU Readline:: +kill-line+ (+C-k+) Kill the text from point to the end of
2556
+ # the line. With a negative numeric argument, kill backward
2557
+ # from the cursor to the beginning of the current line.
2440
2558
  private def ed_kill_line(key)
2441
2559
  if @line.bytesize > @byte_pointer
2442
2560
  @line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
@@ -2453,8 +2571,14 @@ class Reline::LineEditor
2453
2571
  @rest_height += 1
2454
2572
  end
2455
2573
  end
2574
+ alias_method :kill_line, :ed_kill_line
2456
2575
 
2457
- private def em_kill_line(key)
2576
+ # Editline:: +vi-kill-line-prev+ (vi: +Ctrl-U+) Delete the string from the
2577
+ # beginning of the edit buffer to the cursor and save it to the
2578
+ # cut buffer.
2579
+ # GNU Readline:: +unix-line-discard+ (+C-u+) Kill backward from the cursor
2580
+ # to the beginning of the current line.
2581
+ private def vi_kill_line_prev(key)
2458
2582
  if @byte_pointer > 0
2459
2583
  @line, deleted = byteslice!(@line, 0, @byte_pointer)
2460
2584
  @byte_pointer = 0
@@ -2463,7 +2587,22 @@ class Reline::LineEditor
2463
2587
  @cursor = 0
2464
2588
  end
2465
2589
  end
2466
- alias_method :kill_line, :em_kill_line
2590
+ alias_method :unix_line_discard, :vi_kill_line_prev
2591
+
2592
+ # Editline:: +em-kill-line+ (not bound) Delete the entire contents of the
2593
+ # edit buffer and save it to the cut buffer. +vi-kill-line-prev+
2594
+ # GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the
2595
+ # current line, no matter where point is.
2596
+ private def em_kill_line(key)
2597
+ if @line.size > 0
2598
+ @kill_ring.append(@line.dup, true)
2599
+ @line.clear
2600
+ @byte_pointer = 0
2601
+ @cursor_max = 0
2602
+ @cursor = 0
2603
+ end
2604
+ end
2605
+ alias_method :kill_whole_line, :em_kill_line
2467
2606
 
2468
2607
  private def em_delete(key)
2469
2608
  if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
@@ -2968,7 +3107,14 @@ class Reline::LineEditor
2968
3107
 
2969
3108
  private def ed_argument_digit(key)
2970
3109
  if @vi_arg.nil?
2971
- unless key.chr.to_i.zero?
3110
+ if key.chr.to_i.zero?
3111
+ if key.anybits?(0b10000000)
3112
+ unescaped_key = key ^ 0b10000000
3113
+ unless unescaped_key.chr.to_i.zero?
3114
+ @vi_arg = unescaped_key.chr.to_i
3115
+ end
3116
+ end
3117
+ else
2972
3118
  @vi_arg = key.chr.to_i
2973
3119
  end
2974
3120
  else
@@ -71,7 +71,7 @@ module Reline::Terminfo
71
71
  def self.setupterm(term, fildes)
72
72
  errret_int = String.new("\x00" * 8, encoding: 'ASCII-8BIT')
73
73
  ret = @setupterm.(term, fildes, errret_int)
74
- errret = errret_int.unpack('i')[0]
74
+ errret = errret_int.unpack1('i')
75
75
  case ret
76
76
  when 0 # OK
77
77
  0
@@ -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.6'
2
+ VERSION = '0.2.8.pre.10'
3
3
  end
@@ -184,7 +184,7 @@ class Reline::Windows
184
184
  # DWORD FileNameLength;
185
185
  # WCHAR FileName[1];
186
186
  # } FILE_NAME_INFO
187
- len = p_buffer[0, 4].unpack("L")[0]
187
+ len = p_buffer[0, 4].unpack1("L")
188
188
  name = p_buffer[4, len].encode(Encoding::UTF_8, Encoding::UTF_16LE, invalid: :replace)
189
189
 
190
190
  # Check if this could be a MSYS2 pty pipe ('\msys-XXXX-ptyN-XX')
@@ -226,28 +226,31 @@ class Reline::Windows
226
226
  # no char, only control keys
227
227
  return if key.char_code == 0 and key.control_keys.any?
228
228
 
229
+ @@output_buf.push("\e".ord) if key.control_keys.include?(:ALT)
230
+
229
231
  @@output_buf.concat(key.char.bytes)
230
232
  end
231
233
 
232
234
  def self.check_input_event
233
235
  num_of_events = 0.chr * 8
234
236
  while @@output_buf.empty? #or true
237
+ Reline.core.line_editor.resize
235
238
  next if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
236
- next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack('L').first == 0
239
+ next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack1('L') == 0
237
240
  input_record = 0.chr * 18
238
241
  read_event = 0.chr * 4
239
242
  if @@ReadConsoleInputW.(@@hConsoleInputHandle, input_record, 1, read_event) != 0
240
- event = input_record[0, 2].unpack('s*').first
243
+ event = input_record[0, 2].unpack1('s*')
241
244
  case event
242
245
  when WINDOW_BUFFER_SIZE_EVENT
243
246
  @@winch_handler.()
244
247
  when KEY_EVENT
245
- key_down = input_record[4, 4].unpack('l*').first
246
- repeat_count = input_record[8, 2].unpack('s*').first
247
- virtual_key_code = input_record[10, 2].unpack('s*').first
248
- virtual_scan_code = input_record[12, 2].unpack('s*').first
249
- char_code = input_record[14, 2].unpack('S*').first
250
- control_key_state = input_record[16, 2].unpack('S*').first
248
+ key_down = input_record[4, 4].unpack1('l*')
249
+ repeat_count = input_record[8, 2].unpack1('s*')
250
+ virtual_key_code = input_record[10, 2].unpack1('s*')
251
+ virtual_scan_code = input_record[12, 2].unpack1('s*')
252
+ char_code = input_record[14, 2].unpack1('S*')
253
+ control_key_state = input_record[16, 2].unpack1('S*')
251
254
  is_key_down = key_down.zero? ? false : true
252
255
  if is_key_down
253
256
  process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
@@ -289,8 +292,8 @@ class Reline::Windows
289
292
  def self.cursor_pos
290
293
  csbi = 0.chr * 22
291
294
  @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
292
- x = csbi[4, 2].unpack('s*').first
293
- y = csbi[6, 2].unpack('s*').first
295
+ x = csbi[4, 2].unpack1('s*')
296
+ y = csbi[6, 2].unpack1('s*')
294
297
  Reline::CursorPos.new(x, y)
295
298
  end
296
299
 
@@ -322,7 +325,7 @@ class Reline::Windows
322
325
  def self.erase_after_cursor
323
326
  csbi = 0.chr * 24
324
327
  @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
325
- cursor = csbi[4, 4].unpack('L').first
328
+ cursor = csbi[4, 4].unpack1('L')
326
329
  written = 0.chr * 4
327
330
  @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, get_screen_size.last - cursor_pos.x, cursor, written)
328
331
  @@FillConsoleOutputAttribute.call(@@hConsoleHandle, 0, get_screen_size.last - cursor_pos.x, cursor, written)
@@ -341,8 +344,8 @@ class Reline::Windows
341
344
  def self.clear_screen
342
345
  csbi = 0.chr * 22
343
346
  return if @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) == 0
344
- buffer_width = csbi[0, 2].unpack('S').first
345
- attributes = csbi[8, 2].unpack('S').first
347
+ buffer_width = csbi[0, 2].unpack1('S')
348
+ attributes = csbi[8, 2].unpack1('S')
346
349
  _window_left, window_top, _window_right, window_bottom = *csbi[10,8].unpack('S*')
347
350
  fill_length = buffer_width * (window_bottom - window_top + 1)
348
351
  screen_topleft = window_top * 65536
data/lib/reline.rb CHANGED
@@ -16,9 +16,24 @@ module Reline
16
16
 
17
17
  class ConfigEncodingConversionError < StandardError; end
18
18
 
19
- Key = Struct.new('Key', :char, :combined_char, :with_meta)
19
+ Key = Struct.new('Key', :char, :combined_char, :with_meta) do
20
+ def match?(other)
21
+ case other
22
+ when Reline::Key
23
+ (other.char.nil? or char.nil? or char == other.char) and
24
+ (other.combined_char.nil? or combined_char.nil? or combined_char == other.combined_char) and
25
+ (other.with_meta.nil? or with_meta.nil? or with_meta == other.with_meta)
26
+ when Integer, Symbol
27
+ (combined_char and combined_char == other) or
28
+ (combined_char.nil? and char and char == other)
29
+ else
30
+ false
31
+ end
32
+ end
33
+ alias_method :==, :match?
34
+ end
20
35
  CursorPos = Struct.new(:x, :y)
21
- DialogRenderInfo = Struct.new(:pos, :contents, :pointer, :bg_color, :width, :height, keyword_init: true)
36
+ DialogRenderInfo = Struct.new(:pos, :contents, :bg_color, :width, :height, :scrollbar, keyword_init: true)
22
37
 
23
38
  class Core
24
39
  ATTR_READER_NAMES = %i(
@@ -195,7 +210,7 @@ module Reline
195
210
  return nil
196
211
  end
197
212
  pre, target, post = retrieve_completion_block(true)
198
- if target.nil? or target.empty?# or target.size <= 3
213
+ if target.nil? or target.empty? or (completion_journey_data&.pointer == -1 and target.size <= 3)
199
214
  return nil
200
215
  end
201
216
  if completion_journey_data and completion_journey_data.list
@@ -206,7 +221,7 @@ module Reline
206
221
  result = call_completion_proc_with_checking_args(pre, target, post)
207
222
  pointer = nil
208
223
  end
209
- if result and result.size == 1 and result[0] == target
224
+ if result and result.size == 1 and result[0] == target and pointer != 0
210
225
  result = nil
211
226
  end
212
227
  target_width = Reline::Unicode.calculate_width(target)
@@ -222,7 +237,8 @@ module Reline
222
237
  context.clear
223
238
  context.push(cursor_pos_to_render, result, pointer, dialog)
224
239
  end
225
- DialogRenderInfo.new(pos: cursor_pos_to_render, contents: result, pointer: pointer, height: 15)
240
+ dialog.pointer = pointer
241
+ DialogRenderInfo.new(pos: cursor_pos_to_render, contents: result, scrollbar: true, height: 15)
226
242
  }
227
243
  Reline::DEFAULT_DIALOG_CONTEXT = Array.new
228
244
 
@@ -362,25 +378,9 @@ module Reline
362
378
  break
363
379
  when :matching
364
380
  if buffer.size == 1
365
- begin
366
- succ_c = nil
367
- Timeout.timeout(keyseq_timeout / 1000.0) {
368
- succ_c = Reline::IOGate.getc
369
- }
370
- rescue Timeout::Error # cancel matching only when first byte
371
- block.([Reline::Key.new(c, c, false)])
372
- break
373
- else
374
- if key_stroke.match_status(buffer.dup.push(succ_c)) == :unmatched
375
- if c == "\e".ord
376
- block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
377
- else
378
- block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
379
- end
380
- break
381
- else
382
- Reline::IOGate.ungetc(succ_c)
383
- end
381
+ case read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
382
+ when :break then break
383
+ when :next then next
384
384
  end
385
385
  end
386
386
  when :unmatched
@@ -397,6 +397,38 @@ module Reline
397
397
  end
398
398
  end
399
399
 
400
+ private def read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
401
+ begin
402
+ succ_c = nil
403
+ Timeout.timeout(keyseq_timeout / 1000.0) {
404
+ succ_c = Reline::IOGate.getc
405
+ }
406
+ rescue Timeout::Error # cancel matching only when first byte
407
+ block.([Reline::Key.new(c, c, false)])
408
+ return :break
409
+ else
410
+ case key_stroke.match_status(buffer.dup.push(succ_c))
411
+ when :unmatched
412
+ if c == "\e".ord
413
+ block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
414
+ else
415
+ block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
416
+ end
417
+ return :break
418
+ when :matching
419
+ Reline::IOGate.ungetc(succ_c)
420
+ return :next
421
+ when :matched
422
+ buffer << succ_c
423
+ expanded = key_stroke.expand(buffer).map{ |expanded_c|
424
+ Reline::Key.new(expanded_c, expanded_c, false)
425
+ }
426
+ block.(expanded)
427
+ return :break
428
+ end
429
+ end
430
+ end
431
+
400
432
  private def read_escaped_key(keyseq_timeout, c, block)
401
433
  begin
402
434
  escaped_c = nil
@@ -448,7 +480,7 @@ module Reline
448
480
  #--------------------------------------------------------
449
481
 
450
482
  (Core::ATTR_READER_NAMES).each { |name|
451
- def_single_delegators :core, "#{name}", "#{name}="
483
+ def_single_delegators :core, :"#{name}", :"#{name}="
452
484
  }
453
485
  def_single_delegators :core, :input=, :output=
454
486
  def_single_delegators :core, :vi_editing_mode, :emacs_editing_mode
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.6
4
+ version: 0.2.8.pre.10
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-02 00:00:00.000000000 Z
11
+ date: 2021-10-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console