reline 0.2.8.pre.11 → 0.3.0

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: 5d16269b004e83a04194509a08f401942324ca51a216897f84a7c79bf0c1e0a4
4
- data.tar.gz: dc7fe8a18875d588b45f6e5ef37bba5495e40d30ab0268d7895f4dfb36b6e3bc
3
+ metadata.gz: 2e8c2181e5eb21934546d6b768715cb47eab6e1ea1edb2fd2300f43af62dc2b1
4
+ data.tar.gz: 99a61e2729d4b5d150f6f59d99d6de235ea4f1aab6ac262c92fff6a77101b85f
5
5
  SHA512:
6
- metadata.gz: 59c0d2c3361faedafaa0266b8951d9dd5b2776aac5b37cb139b2e1a28b2c4a10b825aa79f90054d79eb5f8c5aa1d0969ee63d8e105605c66ec991e4527d2e35f
7
- data.tar.gz: 43fbf984721138fe3c52c3ddda17d06f8a2d5f95a0216120d2b62134c3f99d7c8798e23bac06d7d6cef7748360e5ad35cca5804cd81d31e11c429a24e0d087f7
6
+ metadata.gz: 96ceefe0bf71e7e44ccd2a5970b35fc44019af9d7da4d2e04ac1d827eb6bc6a201e679a5e40c4db976554159286f16e39cdeac68bcf69c2c5abeb00a2045e563
7
+ data.tar.gz: 251c2029df6b3073010ad6a591fb216990124ed4f9e00f533e79461be66c6a2383ea56c1554dd273e4af3cfc9358dee37b76a1b4bff25a1867972201bfebace3
data/lib/reline/ansi.rb CHANGED
@@ -4,6 +4,19 @@ require 'timeout'
4
4
  require_relative 'terminfo'
5
5
 
6
6
  class Reline::ANSI
7
+ CAPNAME_KEY_BINDINGS = {
8
+ 'khome' => :ed_move_to_beg,
9
+ 'kend' => :ed_move_to_end,
10
+ 'kcuu1' => :ed_prev_history,
11
+ 'kcud1' => :ed_next_history,
12
+ 'kcuf1' => :ed_next_char,
13
+ 'kcub1' => :ed_prev_char,
14
+ 'cuu' => :ed_prev_history,
15
+ 'cud' => :ed_next_history,
16
+ 'cuf' => :ed_next_char,
17
+ 'cub' => :ed_prev_char,
18
+ }
19
+
7
20
  if Reline::Terminfo.enabled?
8
21
  Reline::Terminfo.setupterm(0, 2)
9
22
  end
@@ -49,20 +62,23 @@ class Reline::ANSI
49
62
  end
50
63
 
51
64
  def self.set_default_key_bindings_terminfo(config)
52
- {
53
- Reline::Terminfo.tigetstr('khome').bytes => :ed_move_to_beg,
54
- Reline::Terminfo.tigetstr('kend').bytes => :ed_move_to_end,
55
- Reline::Terminfo.tigetstr('kcuu1').bytes => :ed_prev_history,
56
- Reline::Terminfo.tigetstr('kcud1').bytes => :ed_next_history,
57
- Reline::Terminfo.tigetstr('kcuf1').bytes => :ed_next_char,
58
- Reline::Terminfo.tigetstr('kcub1').bytes => :ed_prev_char,
59
- # Escape sequences that omit the move distance and are set to defaults
60
- # value 1 may be sometimes sent by pressing the arrow-key.
61
- Reline::Terminfo.tigetstr('cuu').sub(/%p1%d/, '').bytes => :ed_prev_history,
62
- Reline::Terminfo.tigetstr('cud').sub(/%p1%d/, '').bytes => :ed_next_history,
63
- Reline::Terminfo.tigetstr('cuf').sub(/%p1%d/, '').bytes => :ed_next_char,
64
- Reline::Terminfo.tigetstr('cub').sub(/%p1%d/, '').bytes => :ed_prev_char,
65
- }.each_pair do |key, func|
65
+ key_bindings = CAPNAME_KEY_BINDINGS.map do |capname, key_binding|
66
+ begin
67
+ key_code = Reline::Terminfo.tigetstr(capname)
68
+ case capname
69
+ # Escape sequences that omit the move distance and are set to defaults
70
+ # value 1 may be sometimes sent by pressing the arrow-key.
71
+ when 'cuu', 'cud', 'cuf', 'cub'
72
+ [ key_code.sub(/%p1%d/, '').bytes, key_binding ]
73
+ else
74
+ [ key_code.bytes, key_binding ]
75
+ end
76
+ rescue Reline::Terminfo::TerminfoError
77
+ # capname is undefined
78
+ end
79
+ end.compact.to_h
80
+
81
+ key_bindings.each_pair do |key, func|
66
82
  config.add_default_key_binding_by_keymap(:emacs, key, func)
67
83
  config.add_default_key_binding_by_keymap(:vi_insert, key, func)
68
84
  config.add_default_key_binding_by_keymap(:vi_command, key, func)
@@ -131,7 +147,7 @@ class Reline::ANSI
131
147
  unless @@buf.empty?
132
148
  return @@buf.shift
133
149
  end
134
- until c = @@input.raw(intr: true) { select([@@input], [], [], 0.1) && @@input.getbyte }
150
+ until c = @@input.raw(intr: true) { @@input.wait_readable(0.1) && @@input.getbyte }
135
151
  Reline.core.line_editor.resize
136
152
  end
137
153
  (c == 0x16 && @@input.raw(min: 0, tim: 0, &:getbyte)) || c
@@ -266,7 +282,7 @@ class Reline::ANSI
266
282
 
267
283
  def self.move_cursor_up(x)
268
284
  if x > 0
269
- @@output.write "\e[#{x}A" if x > 0
285
+ @@output.write "\e[#{x}A"
270
286
  elsif x < 0
271
287
  move_cursor_down(-x)
272
288
  end
@@ -274,7 +290,7 @@ class Reline::ANSI
274
290
 
275
291
  def self.move_cursor_down(x)
276
292
  if x > 0
277
- @@output.write "\e[#{x}B" if x > 0
293
+ @@output.write "\e[#{x}B"
278
294
  elsif x < 0
279
295
  move_cursor_up(-x)
280
296
  end
@@ -282,7 +298,11 @@ class Reline::ANSI
282
298
 
283
299
  def self.hide_cursor
284
300
  if Reline::Terminfo.enabled?
285
- @@output.write Reline::Terminfo.tigetstr('civis')
301
+ begin
302
+ @@output.write Reline::Terminfo.tigetstr('civis')
303
+ rescue Reline::Terminfo::TerminfoError
304
+ # civis is undefined
305
+ end
286
306
  else
287
307
  # ignored
288
308
  end
@@ -290,7 +310,11 @@ class Reline::ANSI
290
310
 
291
311
  def self.show_cursor
292
312
  if Reline::Terminfo.enabled?
293
- @@output.write Reline::Terminfo.tigetstr('cnorm')
313
+ begin
314
+ @@output.write Reline::Terminfo.tigetstr('cnorm')
315
+ rescue Reline::Terminfo::TerminfoError
316
+ # cnorm is undefined
317
+ end
294
318
  else
295
319
  # ignored
296
320
  end
@@ -321,8 +345,6 @@ class Reline::ANSI
321
345
  end
322
346
 
323
347
  def self.deprep(otio)
324
- int_handle = Signal.trap('INT', 'IGNORE')
325
- Signal.trap('INT', int_handle)
326
348
  Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler
327
349
  end
328
350
  end
data/lib/reline/config.rb CHANGED
@@ -67,6 +67,7 @@ class Reline::Config
67
67
  @keyseq_timeout = 500
68
68
  @test_mode = false
69
69
  @autocompletion = false
70
+ @convert_meta = true if seven_bit_encoding?(Reline::IOGate.encoding)
70
71
  end
71
72
 
72
73
  def reset
@@ -387,4 +388,8 @@ class Reline::Config
387
388
  end
388
389
  ret
389
390
  end
391
+
392
+ private def seven_bit_encoding?(encoding)
393
+ encoding == Encoding::US_ASCII
394
+ end
390
395
  end
@@ -1,4 +1,5 @@
1
1
  require 'timeout'
2
+ require 'io/wait'
2
3
 
3
4
  class Reline::GeneralIO
4
5
  def self.reset(encoding: nil)
@@ -36,7 +37,7 @@ class Reline::GeneralIO
36
37
  end
37
38
  c = nil
38
39
  loop do
39
- result = select([@@input], [], [], 0.1)
40
+ result = @@input.wait_readable(0.1)
40
41
  next if result.nil?
41
42
  c = @@input.read(1)
42
43
  break
@@ -4,6 +4,7 @@ class Reline::KeyStroke
4
4
  end
5
5
 
6
6
  def compress_meta_key(ary)
7
+ return ary unless @config.convert_meta
7
8
  ary.inject([]) { |result, key|
8
9
  if result.size > 0 and result.last == "\e".ord
9
10
  result[result.size - 1] = Reline::Key.new(key, key | 0b10000000, true)
@@ -93,7 +93,7 @@ class Reline::LineEditor
93
93
  mode_string
94
94
  end
95
95
 
96
- private def check_multiline_prompt(buffer, prompt)
96
+ private def check_multiline_prompt(buffer)
97
97
  if @vi_arg
98
98
  prompt = "(arg: #{@vi_arg}) "
99
99
  @rerender_all = true
@@ -121,7 +121,7 @@ class Reline::LineEditor
121
121
  if use_cached_prompt_list
122
122
  prompt_list = @cached_prompt_list
123
123
  else
124
- prompt_list = @cached_prompt_list = @prompt_proc.(buffer)
124
+ prompt_list = @cached_prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
125
125
  @prompt_cache_time = Time.now.to_f
126
126
  end
127
127
  prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
@@ -151,33 +151,6 @@ class Reline::LineEditor
151
151
  @screen_size = Reline::IOGate.get_screen_size
152
152
  @screen_height = @screen_size.first
153
153
  reset_variables(prompt, encoding: encoding)
154
- @old_trap = Signal.trap('INT') {
155
- clear_dialog
156
- if @scroll_partial_screen
157
- move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
158
- else
159
- move_cursor_down(@highest_in_all - @line_index - 1)
160
- end
161
- Reline::IOGate.move_cursor_column(0)
162
- scroll_down(1)
163
- case @old_trap
164
- when 'DEFAULT', 'SYSTEM_DEFAULT'
165
- raise Interrupt
166
- when 'IGNORE'
167
- # Do nothing
168
- when 'EXIT'
169
- exit
170
- else
171
- @old_trap.call
172
- end
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
181
154
  Reline::IOGate.set_winch_handler do
182
155
  @resized = true
183
156
  end
@@ -217,7 +190,7 @@ class Reline::LineEditor
217
190
  else
218
191
  back = 0
219
192
  new_buffer = whole_lines
220
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
193
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
221
194
  new_buffer.each_with_index do |line, index|
222
195
  prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
223
196
  width = prompt_width + calculate_width(line)
@@ -244,6 +217,36 @@ class Reline::LineEditor
244
217
  end
245
218
  end
246
219
 
220
+ def set_signal_handlers
221
+ @old_trap = Signal.trap('INT') {
222
+ clear_dialog
223
+ if @scroll_partial_screen
224
+ move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
225
+ else
226
+ move_cursor_down(@highest_in_all - @line_index - 1)
227
+ end
228
+ Reline::IOGate.move_cursor_column(0)
229
+ scroll_down(1)
230
+ case @old_trap
231
+ when 'DEFAULT', 'SYSTEM_DEFAULT'
232
+ raise Interrupt
233
+ when 'IGNORE'
234
+ # Do nothing
235
+ when 'EXIT'
236
+ exit
237
+ else
238
+ @old_trap.call if @old_trap.respond_to?(:call)
239
+ end
240
+ }
241
+ begin
242
+ @old_tstp_trap = Signal.trap('TSTP') {
243
+ Reline::IOGate.ungetc("\C-z".ord)
244
+ @old_tstp_trap.call if @old_tstp_trap.respond_to?(:call)
245
+ }
246
+ rescue ArgumentError
247
+ end
248
+ end
249
+
247
250
  def finalize
248
251
  Signal.trap('INT', @old_trap)
249
252
  begin
@@ -257,7 +260,7 @@ class Reline::LineEditor
257
260
  end
258
261
 
259
262
  def reset_variables(prompt = '', encoding:)
260
- @prompt = prompt
263
+ @prompt = prompt.gsub("\n", "\\n")
261
264
  @mark_pointer = nil
262
265
  @encoding = encoding
263
266
  @is_multiline = false
@@ -435,7 +438,7 @@ class Reline::LineEditor
435
438
  show_menu
436
439
  @menu_info = nil
437
440
  end
438
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
441
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
439
442
  if @cleared
440
443
  clear_screen_buffer(prompt, prompt_list, prompt_width)
441
444
  @cleared = false
@@ -446,7 +449,7 @@ class Reline::LineEditor
446
449
  Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
447
450
  Reline::IOGate.move_cursor_column(0)
448
451
  @scroll_partial_screen = nil
449
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
452
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
450
453
  if @previous_line_index
451
454
  new_lines = whole_lines(index: @previous_line_index, line: @line)
452
455
  else
@@ -492,7 +495,7 @@ class Reline::LineEditor
492
495
  end
493
496
  line = modify_lines(new_lines)[@line_index]
494
497
  clear_dialog
495
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
498
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
496
499
  render_partial(prompt, prompt_width, line, @first_line_started_from)
497
500
  move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
498
501
  scroll_down(1)
@@ -501,7 +504,7 @@ class Reline::LineEditor
501
504
  else
502
505
  if not rendered and not @in_pasting
503
506
  line = modify_lines(whole_lines)[@line_index]
504
- prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
507
+ prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
505
508
  render_partial(prompt, prompt_width, line, @first_line_started_from)
506
509
  end
507
510
  render_dialog((prompt_width + @cursor) % @screen_size.last)
@@ -634,8 +637,12 @@ class Reline::LineEditor
634
637
  end
635
638
 
636
639
  def add_dialog_proc(name, p, context = nil)
637
- return if @dialogs.any? { |d| d.name == name }
638
- @dialogs << Dialog.new(name, @config, DialogProcScope.new(self, @config, p, context))
640
+ dialog = Dialog.new(name, @config, DialogProcScope.new(self, @config, p, context))
641
+ if index = @dialogs.find_index { |d| d.name == name }
642
+ @dialogs[index] = dialog
643
+ else
644
+ @dialogs << dialog
645
+ end
639
646
  end
640
647
 
641
648
  DIALOG_DEFAULT_HEIGHT = 20
@@ -651,6 +658,7 @@ class Reline::LineEditor
651
658
 
652
659
  private def render_each_dialog(dialog, cursor_column)
653
660
  if @in_pasting
661
+ clear_each_dialog(dialog)
654
662
  dialog.contents = nil
655
663
  dialog.trap_key = nil
656
664
  return
@@ -706,19 +714,18 @@ class Reline::LineEditor
706
714
  dialog.scrollbar_pos = nil
707
715
  end
708
716
  upper_space = @first_line_started_from - @started_from
709
- lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
710
717
  dialog.column = dialog_render_info.pos.x
711
718
  dialog.width += @block_elem_width if dialog.scrollbar_pos
712
719
  diff = (dialog.column + dialog.width) - (@screen_size.last)
713
720
  if diff > 0
714
721
  dialog.column -= diff
715
722
  end
716
- if (lower_space + @rest_height - dialog_render_info.pos.y) >= height
723
+ if (@rest_height - dialog_render_info.pos.y) >= height
717
724
  dialog.vertical_offset = dialog_render_info.pos.y + 1
718
725
  elsif upper_space >= height
719
726
  dialog.vertical_offset = dialog_render_info.pos.y - height
720
727
  else
721
- if (lower_space + @rest_height - dialog_render_info.pos.y) < height
728
+ if (@rest_height - dialog_render_info.pos.y) < height
722
729
  scroll_down(height + dialog_render_info.pos.y)
723
730
  move_cursor_up(height + dialog_render_info.pos.y)
724
731
  end
@@ -776,7 +783,7 @@ class Reline::LineEditor
776
783
 
777
784
  private def reset_dialog(dialog, old_dialog)
778
785
  return if dialog.lines_backup.nil? or old_dialog.contents.nil?
779
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
786
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
780
787
  visual_lines = []
781
788
  visual_start = nil
782
789
  dialog.lines_backup[:lines].each_with_index { |l, i|
@@ -858,7 +865,8 @@ class Reline::LineEditor
858
865
  s = ' ' * width
859
866
  else
860
867
  s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
861
- s = padding_space_with_escape_sequences(s, dialog.width)
868
+ rerender_width = old_dialog.width - dialog.width
869
+ s = padding_space_with_escape_sequences(s, rerender_width)
862
870
  end
863
871
  Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
864
872
  @output.write "\e[0m#{s}\e[0m"
@@ -878,7 +886,7 @@ class Reline::LineEditor
878
886
  private def clear_each_dialog(dialog)
879
887
  dialog.trap_key = nil
880
888
  return unless dialog.contents
881
- prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
889
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
882
890
  visual_lines = []
883
891
  visual_lines_under_dialog = []
884
892
  visual_start = nil
@@ -963,7 +971,7 @@ class Reline::LineEditor
963
971
  end
964
972
 
965
973
  def just_move_cursor
966
- prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines, prompt)
974
+ prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines)
967
975
  move_cursor_up(@started_from)
968
976
  new_first_line_started_from =
969
977
  if @line_index.zero?
@@ -1000,7 +1008,7 @@ class Reline::LineEditor
1000
1008
  else
1001
1009
  new_lines = whole_lines
1002
1010
  end
1003
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
1011
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
1004
1012
  all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
1005
1013
  diff = all_height - @highest_in_all
1006
1014
  move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
@@ -1047,7 +1055,7 @@ class Reline::LineEditor
1047
1055
  Reline::IOGate.move_cursor_column(0)
1048
1056
  back = 0
1049
1057
  new_buffer = whole_lines
1050
- prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer, prompt)
1058
+ prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
1051
1059
  new_buffer.each_with_index do |line, index|
1052
1060
  prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
1053
1061
  width = prompt_width + calculate_width(line)
@@ -2021,8 +2029,16 @@ class Reline::LineEditor
2021
2029
  last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
2022
2030
  @byte_pointer += bytesize
2023
2031
  last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
2024
- if last_byte_size != 0 and (last_mbchar + str).grapheme_clusters.size == 1
2025
- width = 0
2032
+ combined_char = last_mbchar + str
2033
+ if last_byte_size != 0 and combined_char.grapheme_clusters.size == 1
2034
+ # combined char
2035
+ last_mbchar_width = Reline::Unicode.get_mbchar_width(last_mbchar)
2036
+ combined_char_width = Reline::Unicode.get_mbchar_width(combined_char)
2037
+ if combined_char_width > last_mbchar_width
2038
+ width = combined_char_width - last_mbchar_width
2039
+ else
2040
+ width = 0
2041
+ end
2026
2042
  end
2027
2043
  @cursor += width
2028
2044
  @cursor_max += width
@@ -1,5 +1,13 @@
1
- require 'fiddle'
2
- require 'fiddle/import'
1
+ begin
2
+ require 'fiddle'
3
+ require 'fiddle/import'
4
+ rescue LoadError
5
+ module Reline::Terminfo
6
+ def self.curses_dl
7
+ false
8
+ end
9
+ end
10
+ end
3
11
 
4
12
  module Reline::Terminfo
5
13
  extend Fiddle::Importer
@@ -50,7 +58,7 @@ module Reline::Terminfo
50
58
  @curses_dl = nil if @curses_dl == false
51
59
  @curses_dl
52
60
  end
53
- end
61
+ end if not Reline.const_defined?(:Terminfo) or not Reline::Terminfo.respond_to?(:curses_dl)
54
62
 
55
63
  module Reline::Terminfo
56
64
  dlload curses_dl
@@ -79,6 +79,8 @@ class Reline::Unicode
79
79
 
80
80
  require 'reline/unicode/east_asian_width'
81
81
 
82
+ HalfwidthDakutenHandakuten = /[\u{FF9E}\u{FF9F}]/
83
+
82
84
  MBCharWidthRE = /
83
85
  (?<width_2_1>
84
86
  [#{ EscapedChars.map {|c| "\\x%02x" % c.ord }.join }] (?# ^ + char, such as ^M, ^H, ^[, ...)
@@ -93,6 +95,12 @@ class Reline::Unicode
93
95
  #{ EastAsianWidth::TYPE_H }
94
96
  | #{ EastAsianWidth::TYPE_NA }
95
97
  | #{ EastAsianWidth::TYPE_N }
98
+ )(?!#{ HalfwidthDakutenHandakuten })
99
+ | (?<width_2_3>
100
+ (?: #{ EastAsianWidth::TYPE_H }
101
+ | #{ EastAsianWidth::TYPE_NA }
102
+ | #{ EastAsianWidth::TYPE_N })
103
+ #{ HalfwidthDakutenHandakuten }
96
104
  )
97
105
  | (?<ambiguous_width>
98
106
  #{EastAsianWidth::TYPE_A}
@@ -109,7 +117,7 @@ class Reline::Unicode
109
117
  m = mbchar.encode(Encoding::UTF_8).match(MBCharWidthRE)
110
118
  case
111
119
  when m.nil? then 1 # TODO should be U+FFFD � REPLACEMENT CHARACTER
112
- when m[:width_2_1], m[:width_2_2] then 2
120
+ when m[:width_2_1], m[:width_2_2], m[:width_2_3] then 2
113
121
  when m[:width_3] then 3
114
122
  when m[:width_0] then 0
115
123
  when m[:width_1] then 1
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.2.8.pre.11'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -168,7 +168,9 @@ class Reline::Windows
168
168
  @@input_buf = []
169
169
  @@output_buf = []
170
170
 
171
- def self.msys_tty?(io=@@hConsoleInputHandle)
171
+ @@output = STDOUT
172
+
173
+ def self.msys_tty?(io = @@hConsoleInputHandle)
172
174
  # check if fd is a pipe
173
175
  if @@GetFileType.call(io) != FILE_TYPE_PIPE
174
176
  return false
@@ -213,8 +215,29 @@ class Reline::Windows
213
215
  [ { control_keys: :SHIFT, virtual_key_code: VK_TAB }, [27, 91, 90] ],
214
216
  ]
215
217
 
218
+ @@hsg = nil
219
+
216
220
  def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
217
221
 
222
+ # high-surrogate
223
+ if 0xD800 <= char_code and char_code <= 0xDBFF
224
+ @@hsg = char_code
225
+ return
226
+ end
227
+ # low-surrogate
228
+ if 0xDC00 <= char_code and char_code <= 0xDFFF
229
+ if @@hsg
230
+ char_code = 0x10000 + (@@hsg - 0xD800) * 0x400 + char_code - 0xDC00
231
+ @@hsg = nil
232
+ else
233
+ # no high-surrogate. ignored.
234
+ return
235
+ end
236
+ else
237
+ # ignore high-surrogate without low-surrogate if there
238
+ @@hsg = nil
239
+ end
240
+
218
241
  key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state)
219
242
 
220
243
  match = KEY_MAP.find { |args,| key.matches?(**args) }
@@ -233,27 +256,35 @@ class Reline::Windows
233
256
 
234
257
  def self.check_input_event
235
258
  num_of_events = 0.chr * 8
236
- while @@output_buf.empty? #or true
259
+ while @@output_buf.empty?
237
260
  Reline.core.line_editor.resize
238
- next if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
261
+ if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
262
+ # prevent for background consolemode change
263
+ @@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
264
+ next
265
+ end
239
266
  next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack1('L') == 0
240
- input_record = 0.chr * 18
267
+ input_records = 0.chr * 20 * 80
241
268
  read_event = 0.chr * 4
242
- if @@ReadConsoleInputW.(@@hConsoleInputHandle, input_record, 1, read_event) != 0
243
- event = input_record[0, 2].unpack1('s*')
244
- case event
245
- when WINDOW_BUFFER_SIZE_EVENT
246
- @@winch_handler.()
247
- when KEY_EVENT
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*')
254
- is_key_down = key_down.zero? ? false : true
255
- if is_key_down
256
- process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
269
+ if @@ReadConsoleInputW.(@@hConsoleInputHandle, input_records, 80, read_event) != 0
270
+ read_events = read_event.unpack1('L')
271
+ 0.upto(read_events) do |idx|
272
+ input_record = input_records[idx * 20, 20]
273
+ event = input_record[0, 2].unpack1('s*')
274
+ case event
275
+ when WINDOW_BUFFER_SIZE_EVENT
276
+ @@winch_handler.()
277
+ when KEY_EVENT
278
+ key_down = input_record[4, 4].unpack1('l*')
279
+ repeat_count = input_record[8, 2].unpack1('s*')
280
+ virtual_key_code = input_record[10, 2].unpack1('s*')
281
+ virtual_scan_code = input_record[12, 2].unpack1('s*')
282
+ char_code = input_record[14, 2].unpack1('S*')
283
+ control_key_state = input_record[16, 2].unpack1('S*')
284
+ is_key_down = key_down.zero? ? false : true
285
+ if is_key_down
286
+ process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
287
+ end
257
288
  end
258
289
  end
259
290
  end
@@ -274,7 +305,7 @@ class Reline::Windows
274
305
  end
275
306
 
276
307
  def self.empty_buffer?
277
- if not @@input_buf.empty?
308
+ if not @@output_buf.empty?
278
309
  false
279
310
  elsif @@kbhit.call == 0
280
311
  true
@@ -283,17 +314,37 @@ class Reline::Windows
283
314
  end
284
315
  end
285
316
 
286
- def self.get_screen_size
317
+ def self.get_console_screen_buffer_info
318
+ # CONSOLE_SCREEN_BUFFER_INFO
319
+ # [ 0,2] dwSize.X
320
+ # [ 2,2] dwSize.Y
321
+ # [ 4,2] dwCursorPositions.X
322
+ # [ 6,2] dwCursorPositions.Y
323
+ # [ 8,2] wAttributes
324
+ # [10,2] srWindow.Left
325
+ # [12,2] srWindow.Top
326
+ # [14,2] srWindow.Right
327
+ # [16,2] srWindow.Bottom
328
+ # [18,2] dwMaximumWindowSize.X
329
+ # [20,2] dwMaximumWindowSize.Y
287
330
  csbi = 0.chr * 22
288
- @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
331
+ return if @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) == 0
332
+ csbi
333
+ end
334
+
335
+ def self.get_screen_size
336
+ unless csbi = get_console_screen_buffer_info
337
+ return [1, 1]
338
+ end
289
339
  csbi[0, 4].unpack('SS').reverse
290
340
  end
291
341
 
292
342
  def self.cursor_pos
293
- csbi = 0.chr * 22
294
- @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
295
- x = csbi[4, 2].unpack1('s*')
296
- y = csbi[6, 2].unpack1('s*')
343
+ unless csbi = get_console_screen_buffer_info
344
+ return Reline::CursorPos.new(0, 0)
345
+ end
346
+ x = csbi[4, 2].unpack1('s')
347
+ y = csbi[6, 2].unpack1('s')
297
348
  Reline::CursorPos.new(x, y)
298
349
  end
299
350
 
@@ -313,6 +364,7 @@ class Reline::Windows
313
364
 
314
365
  def self.move_cursor_down(val)
315
366
  if val > 0
367
+ return unless csbi = get_console_screen_buffer_info
316
368
  screen_height = get_screen_size.first
317
369
  y = cursor_pos.y + val
318
370
  y = screen_height - 1 if y > (screen_height - 1)
@@ -323,8 +375,7 @@ class Reline::Windows
323
375
  end
324
376
 
325
377
  def self.erase_after_cursor
326
- csbi = 0.chr * 22
327
- @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi)
378
+ return unless csbi = get_console_screen_buffer_info
328
379
  attributes = csbi[8, 2].unpack1('S')
329
380
  cursor = csbi[4, 4].unpack1('L')
330
381
  written = 0.chr * 4
@@ -333,27 +384,45 @@ class Reline::Windows
333
384
  end
334
385
 
335
386
  def self.scroll_down(val)
336
- return if val.zero?
337
- screen_height = get_screen_size.first
338
- val = screen_height - 1 if val > (screen_height - 1)
339
- scroll_rectangle = [0, val, get_screen_size.last, get_screen_size.first].pack('s4')
340
- destination_origin = 0 # y * 65536 + x
341
- fill = [' '.ord, 0].pack('SS')
342
- @@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill)
387
+ return if val < 0
388
+ return unless csbi = get_console_screen_buffer_info
389
+ buffer_width, x, y, buffer_lines, attributes, window_left, window_top, window_bottom = csbi.unpack('ssssSssx2s')
390
+ screen_height = window_bottom - window_top + 1
391
+ val = screen_height if val > screen_height
392
+
393
+ if @@legacy_console || window_left != 0
394
+ # unless ENABLE_VIRTUAL_TERMINAL,
395
+ # if srWindow.Left != 0 then it's conhost.exe hosted console
396
+ # and puts "\n" causes horizontal scroll. its glitch.
397
+ # FYI irb write from culumn 1, so this gives no gain.
398
+ scroll_rectangle = [0, val, buffer_width, buffer_lines - val].pack('s4')
399
+ destination_origin = 0 # y * 65536 + x
400
+ fill = [' '.ord, attributes].pack('SS')
401
+ @@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill)
402
+ else
403
+ origin_x = x + 1
404
+ origin_y = y - window_top + 1
405
+ @@output.write [
406
+ (origin_y != screen_height) ? "\e[#{screen_height};H" : nil,
407
+ "\n" * val,
408
+ (origin_y != screen_height or !x.zero?) ? "\e[#{origin_y};#{origin_x}H" : nil
409
+ ].join
410
+ end
343
411
  end
344
412
 
345
413
  def self.clear_screen
346
- csbi = 0.chr * 22
347
- return if @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) == 0
348
- buffer_width = csbi[0, 2].unpack1('S')
349
- attributes = csbi[8, 2].unpack1('S')
350
- _window_left, window_top, _window_right, window_bottom = *csbi[10,8].unpack('S*')
351
- fill_length = buffer_width * (window_bottom - window_top + 1)
352
- screen_topleft = window_top * 65536
353
- written = 0.chr * 4
354
- @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, fill_length, screen_topleft, written)
355
- @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, fill_length, screen_topleft, written)
356
- @@SetConsoleCursorPosition.call(@@hConsoleHandle, screen_topleft)
414
+ if @@legacy_console
415
+ return unless csbi = get_console_screen_buffer_info
416
+ buffer_width, _buffer_lines, attributes, window_top, window_bottom = csbi.unpack('ss@8S@12sx2s')
417
+ fill_length = buffer_width * (window_bottom - window_top + 1)
418
+ screen_topleft = window_top * 65536
419
+ written = 0.chr * 4
420
+ @@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, fill_length, screen_topleft, written)
421
+ @@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, fill_length, screen_topleft, written)
422
+ @@SetConsoleCursorPosition.call(@@hConsoleHandle, screen_topleft)
423
+ else
424
+ @@output.write "\e[2J" "\e[H"
425
+ end
357
426
  end
358
427
 
359
428
  def self.set_screen_size(rows, columns)
data/lib/reline.rb CHANGED
@@ -60,7 +60,7 @@ module Reline
60
60
 
61
61
  def initialize
62
62
  self.output = STDOUT
63
- @dialog_proc_list = []
63
+ @dialog_proc_list = {}
64
64
  yield self
65
65
  @completion_quote_character = nil
66
66
  @bracketed_paste_finished = false
@@ -155,10 +155,15 @@ module Reline
155
155
  @dig_perfect_match_proc = p
156
156
  end
157
157
 
158
+ DialogProc = Struct.new(:dialog_proc, :context)
158
159
  def add_dialog_proc(name_sym, p, context = nil)
159
160
  raise ArgumentError unless p.respond_to?(:call) or p.nil?
160
161
  raise ArgumentError unless name_sym.instance_of?(Symbol)
161
- @dialog_proc_list << [name_sym, p, context]
162
+ @dialog_proc_list[name_sym] = DialogProc.new(p, context)
163
+ end
164
+
165
+ def dialog_proc(name_sym)
166
+ @dialog_proc_list[name_sym]
162
167
  end
163
168
 
164
169
  def input=(val)
@@ -301,9 +306,8 @@ module Reline
301
306
  line_editor.auto_indent_proc = auto_indent_proc
302
307
  line_editor.dig_perfect_match_proc = dig_perfect_match_proc
303
308
  line_editor.pre_input_hook = pre_input_hook
304
- @dialog_proc_list.each do |d|
305
- name_sym, dialog_proc, context = d
306
- line_editor.add_dialog_proc(name_sym, dialog_proc, context)
309
+ @dialog_proc_list.each_pair do |name_sym, d|
310
+ line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context)
307
311
  end
308
312
 
309
313
  unless config.test_mode
@@ -315,6 +319,7 @@ module Reline
315
319
  line_editor.rerender
316
320
 
317
321
  begin
322
+ line_editor.set_signal_handlers
318
323
  prev_pasting_state = false
319
324
  loop do
320
325
  prev_pasting_state = Reline::IOGate.in_pasting?
@@ -343,6 +348,11 @@ module Reline
343
348
  line_editor.finalize
344
349
  Reline::IOGate.deprep(otio)
345
350
  raise e
351
+ rescue Exception
352
+ # Including Interrupt
353
+ line_editor.finalize
354
+ Reline::IOGate.deprep(otio)
355
+ raise
346
356
  end
347
357
 
348
358
  line_editor.finalize
@@ -457,7 +467,7 @@ module Reline
457
467
 
458
468
  private def may_req_ambiguous_char_width
459
469
  @ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or STDOUT.is_a?(File)
460
- return if @ambiguous_width
470
+ return if defined? @ambiguous_width
461
471
  Reline::IOGate.move_cursor_column(0)
462
472
  begin
463
473
  output.write "\u{25bd}"
@@ -516,6 +526,7 @@ module Reline
516
526
  def_single_delegators :core, :last_incremental_search
517
527
  def_single_delegators :core, :last_incremental_search=
518
528
  def_single_delegators :core, :add_dialog_proc
529
+ def_single_delegators :core, :dialog_proc
519
530
  def_single_delegators :core, :autocompletion, :autocompletion=
520
531
 
521
532
  def_single_delegators :core, :readmultiline
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.11
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-09 00:00:00.000000000 Z
11
+ date: 2021-12-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console
@@ -69,11 +69,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
69
69
  version: '2.5'
70
70
  required_rubygems_version: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - ">"
72
+ - - ">="
73
73
  - !ruby/object:Gem::Version
74
- version: 1.3.1
74
+ version: '0'
75
75
  requirements: []
76
- rubygems_version: 3.1.6
76
+ rubygems_version: 3.2.22
77
77
  signing_key:
78
78
  specification_version: 4
79
79
  summary: Alternative GNU Readline or Editline implementation by pure Ruby.