reline 0.2.8.pre.11 → 0.3.0

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