reline 0.2.7 → 0.2.8.pre.1

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: 1bb2255429504a1beb1a5a9f863f7cba5c4c65781b7cd8b2aa843b0df639fb26
4
- data.tar.gz: c9372db87ad831e06d0e99d20981cb14a08e8b15ca3011f4b6df2a05aaeef7b3
3
+ metadata.gz: 2c1dd7202ef6de3ebf89e2ed39c83af1e48bcd46d5f967037edc0097301b8286
4
+ data.tar.gz: 4d2047341862d83b785903974e4766b5a3d481a033208bfcc1d8891a0b116e65
5
5
  SHA512:
6
- metadata.gz: 63e8b6f4a9de8a69a8f22c979b6fee92026143be658b9e928da43dddb868fb6b641b5f140de436c1dd5ed1f3cdbc3c42c7c41084fcc1d811cd713c684aedda8e
7
- data.tar.gz: 7a459412f528f43d5e65f4c7c6a5c74055e2c5334f21a65411bc4f187b55b649c15fd612ed27f2dc0032f7fdc5973c7fa57b674b0a323ac2d9537b23fff19664
6
+ metadata.gz: a9109bb68de735bd5dba6de8dffecfd23fecd8f09aa0b1f92d0bca6b008ef7c3d3785c16292db8ea90c9baf4d275ef382cd6d0d15487fbe6e491c74a670004bd
7
+ data.tar.gz: af8f8b99ceb34416253857caf4d87a07bde1fddf72d80ff14caeb2a5940178566bd63fc4a6884eddd60959ac3f352c056bb73bbe646915582b5b3b4d091beeed
data/lib/reline/ansi.rb CHANGED
@@ -274,6 +274,22 @@ class Reline::ANSI
274
274
  end
275
275
  end
276
276
 
277
+ def self.hide_cursor
278
+ if Reline::Terminfo.enabled?
279
+ @@output.write Reline::Terminfo.tigetstr('civis')
280
+ else
281
+ # ignored
282
+ end
283
+ end
284
+
285
+ def self.show_cursor
286
+ if Reline::Terminfo.enabled?
287
+ @@output.write Reline::Terminfo.tigetstr('cnorm')
288
+ else
289
+ # ignored
290
+ end
291
+ end
292
+
277
293
  def self.erase_after_cursor
278
294
  @@output.write "\e[K"
279
295
  end
@@ -295,8 +311,6 @@ class Reline::ANSI
295
311
 
296
312
  def self.prep
297
313
  retrieve_keybuffer
298
- int_handle = Signal.trap('INT', 'IGNORE')
299
- Signal.trap('INT', int_handle)
300
314
  nil
301
315
  end
302
316
 
@@ -151,6 +151,7 @@ class Reline::LineEditor
151
151
  @screen_height = @screen_size.first
152
152
  reset_variables(prompt, encoding: encoding)
153
153
  @old_trap = Signal.trap(:INT) {
154
+ clear_dialog
154
155
  if @scroll_partial_screen
155
156
  move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
156
157
  else
@@ -249,6 +250,7 @@ class Reline::LineEditor
249
250
  @drop_terminate_spaces = false
250
251
  @in_pasting = false
251
252
  @auto_indent_proc = nil
253
+ @dialogs = []
252
254
  reset_line
253
255
  end
254
256
 
@@ -414,6 +416,7 @@ class Reline::LineEditor
414
416
  Reline::IOGate.erase_after_cursor
415
417
  end
416
418
  @output.flush
419
+ clear_dialog
417
420
  return
418
421
  end
419
422
  new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
@@ -424,6 +427,7 @@ class Reline::LineEditor
424
427
  else
425
428
  if @just_cursor_moving and not @rerender_all
426
429
  rendered = just_move_cursor
430
+ render_dialog((prompt_width + @cursor) % @screen_size.last)
427
431
  @just_cursor_moving = false
428
432
  return
429
433
  elsif @previous_line_index or new_highest_in_this != @highest_in_this
@@ -446,18 +450,20 @@ class Reline::LineEditor
446
450
  new_lines = whole_lines
447
451
  end
448
452
  line = modify_lines(new_lines)[@line_index]
453
+ clear_dialog
449
454
  prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
450
455
  render_partial(prompt, prompt_width, line, @first_line_started_from)
451
456
  move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
452
457
  scroll_down(1)
453
458
  Reline::IOGate.move_cursor_column(0)
454
459
  Reline::IOGate.erase_after_cursor
455
- elsif not rendered
456
- unless @in_pasting
460
+ else
461
+ if not rendered and not @in_pasting
457
462
  line = modify_lines(whole_lines)[@line_index]
458
463
  prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
459
464
  render_partial(prompt, prompt_width, line, @first_line_started_from)
460
465
  end
466
+ render_dialog((prompt_width + @cursor) % @screen_size.last)
461
467
  end
462
468
  @buffer_of_lines[@line_index] = @line
463
469
  @rest_height = 0 if @scroll_partial_screen
@@ -472,6 +478,289 @@ class Reline::LineEditor
472
478
  end
473
479
  end
474
480
 
481
+ class DialogProcScope
482
+ def initialize(line_editor, proc_to_exec, context)
483
+ @line_editor = line_editor
484
+ @proc_to_exec = proc_to_exec
485
+ @context = context
486
+ @cursor_pos = Reline::CursorPos.new
487
+ end
488
+
489
+ def context
490
+ @context
491
+ end
492
+
493
+ def retrieve_completion_block(set_completion_quote_character = false)
494
+ @line_editor.retrieve_completion_block(set_completion_quote_character)
495
+ end
496
+
497
+ def call_completion_proc_with_checking_args(pre, target, post)
498
+ @line_editor.call_completion_proc_with_checking_args(pre, target, post)
499
+ end
500
+
501
+ def set_cursor_pos(col, row)
502
+ @cursor_pos.x = col
503
+ @cursor_pos.y = row
504
+ end
505
+
506
+ def cursor_pos
507
+ @cursor_pos
508
+ end
509
+
510
+ def just_cursor_moving
511
+ @line_editor.instance_variable_get(:@just_cursor_moving)
512
+ end
513
+
514
+ def screen_width
515
+ @line_editor.instance_variable_get(:@screen_size).last
516
+ end
517
+
518
+ def completion_journey_data
519
+ @line_editor.instance_variable_get(:@completion_journey_data)
520
+ end
521
+
522
+ def call
523
+ instance_exec(&@proc_to_exec)
524
+ end
525
+ end
526
+
527
+ class Dialog
528
+ attr_reader :name
529
+ attr_accessor :column, :vertical_offset, :contents, :lines_backup
530
+
531
+ def initialize(name, proc_scope)
532
+ @name = name
533
+ @proc_scope = proc_scope
534
+ end
535
+
536
+ def set_cursor_pos(col, row)
537
+ @proc_scope.set_cursor_pos(col, row)
538
+ end
539
+
540
+ def call
541
+ @proc_scope.call
542
+ end
543
+ end
544
+
545
+ def add_dialog_proc(name, p, context = nil)
546
+ return if @dialogs.any? { |d| d.name == name }
547
+ @dialogs << Dialog.new(name, DialogProcScope.new(self, p, context))
548
+ end
549
+
550
+ DIALOG_HEIGHT = 20
551
+ DIALOG_WIDTH = 40
552
+ private def render_dialog(cursor_column)
553
+ @dialogs.each do |dialog|
554
+ render_each_dialog(dialog, cursor_column)
555
+ end
556
+ end
557
+
558
+ private def render_each_dialog(dialog, cursor_column)
559
+ if @in_pasting
560
+ dialog.contents = nil
561
+ return
562
+ end
563
+ dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
564
+ pos, result, pointer, bg = dialog.call
565
+ old_dialog_contents = dialog.contents
566
+ old_dialog_column = dialog.column
567
+ old_dialog_vertical_offset = dialog.vertical_offset
568
+ if result and not result.empty?
569
+ dialog.contents = result
570
+ dialog.contents = dialog.contents[0...DIALOG_HEIGHT] if dialog.contents.size > DIALOG_HEIGHT
571
+ else
572
+ dialog.lines_backup = {
573
+ lines: modify_lines(whole_lines),
574
+ line_index: @line_index,
575
+ first_line_started_from: @first_line_started_from,
576
+ started_from: @started_from,
577
+ byte_pointer: @byte_pointer
578
+ }
579
+ clear_each_dialog(dialog)
580
+ dialog.contents = nil
581
+ return
582
+ end
583
+ upper_space = @first_line_started_from - @started_from
584
+ lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
585
+ dialog.column = pos.x
586
+ diff = (dialog.column + DIALOG_WIDTH) - (@screen_size.last - 1)
587
+ if diff > 0
588
+ dialog.column -= diff
589
+ end
590
+ if (lower_space + @rest_height) >= DIALOG_HEIGHT
591
+ dialog.vertical_offset = pos.y + 1
592
+ elsif upper_space >= DIALOG_HEIGHT
593
+ dialog.vertical_offset = pos.y + -(DIALOG_HEIGHT + 1)
594
+ else
595
+ if (lower_space + @rest_height) < DIALOG_HEIGHT
596
+ scroll_down(DIALOG_HEIGHT)
597
+ move_cursor_up(DIALOG_HEIGHT)
598
+ end
599
+ dialog.vertical_offset = pos.y + 1
600
+ end
601
+ Reline::IOGate.hide_cursor
602
+ reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
603
+ move_cursor_down(dialog.vertical_offset)
604
+ Reline::IOGate.move_cursor_column(dialog.column)
605
+ dialog.contents.each_with_index do |item, i|
606
+ if i == pointer
607
+ bg_color = '45'
608
+ else
609
+ if bg
610
+ bg_color = bg
611
+ else
612
+ bg_color = '46'
613
+ end
614
+ end
615
+ @output.write "\e[#{bg_color}m%-#{DIALOG_WIDTH}s\e[49m" % item.slice(0, DIALOG_WIDTH)
616
+ Reline::IOGate.move_cursor_column(dialog.column)
617
+ move_cursor_down(1) if i < (dialog.contents.size - 1)
618
+ end
619
+ Reline::IOGate.move_cursor_column(cursor_column)
620
+ move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
621
+ Reline::IOGate.show_cursor
622
+ dialog.lines_backup = {
623
+ lines: modify_lines(whole_lines),
624
+ line_index: @line_index,
625
+ first_line_started_from: @first_line_started_from,
626
+ started_from: @started_from,
627
+ byte_pointer: @byte_pointer
628
+ }
629
+ end
630
+
631
+ private def reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
632
+ return if dialog.lines_backup.nil? or old_dialog_contents.nil?
633
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
634
+ visual_lines = []
635
+ visual_start = nil
636
+ dialog.lines_backup[:lines].each_with_index { |l, i|
637
+ pr = prompt_list ? prompt_list[i] : prompt
638
+ vl, _ = split_by_width(pr + l, @screen_size.last)
639
+ vl.compact!
640
+ if i == dialog.lines_backup[:line_index]
641
+ visual_start = visual_lines.size + dialog.lines_backup[:started_from]
642
+ end
643
+ visual_lines.concat(vl)
644
+ }
645
+ old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
646
+ y = @first_line_started_from + @started_from
647
+ y_diff = y - old_y
648
+ if (old_y + old_dialog_vertical_offset) < (y + dialog.vertical_offset)
649
+ # rerender top
650
+ move_cursor_down(old_dialog_vertical_offset - y_diff)
651
+ start = visual_start + old_dialog_vertical_offset
652
+ line_num = dialog.vertical_offset - old_dialog_vertical_offset
653
+ line_num.times do |i|
654
+ Reline::IOGate.move_cursor_column(old_dialog_column)
655
+ if visual_lines[start + i].nil?
656
+ s = ' ' * DIALOG_WIDTH
657
+ else
658
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
659
+ end
660
+ @output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
661
+ move_cursor_down(1) if i < (line_num - 1)
662
+ end
663
+ move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
664
+ end
665
+ if (old_y + old_dialog_vertical_offset + old_dialog_contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
666
+ # rerender bottom
667
+ move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
668
+ start = visual_start + dialog.vertical_offset + dialog.contents.size
669
+ line_num = (old_dialog_vertical_offset + old_dialog_contents.size) - (dialog.vertical_offset + dialog.contents.size)
670
+ line_num.times do |i|
671
+ Reline::IOGate.move_cursor_column(old_dialog_column)
672
+ if visual_lines[start + i].nil?
673
+ s = ' ' * DIALOG_WIDTH
674
+ else
675
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
676
+ end
677
+ @output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
678
+ move_cursor_down(1) if i < (line_num - 1)
679
+ end
680
+ move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
681
+ end
682
+ if old_dialog_column < dialog.column
683
+ # rerender left
684
+ move_cursor_down(old_dialog_vertical_offset - y_diff)
685
+ width = dialog.column - old_dialog_column
686
+ start = visual_start + old_dialog_vertical_offset
687
+ line_num = old_dialog_contents.size
688
+ line_num.times do |i|
689
+ Reline::IOGate.move_cursor_column(old_dialog_column)
690
+ if visual_lines[start + i].nil?
691
+ s = ' ' * width
692
+ else
693
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, width)
694
+ end
695
+ @output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
696
+ move_cursor_down(1) if i < (line_num - 1)
697
+ end
698
+ move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
699
+ end
700
+ if (old_dialog_column + DIALOG_WIDTH) > (dialog.column + DIALOG_WIDTH)
701
+ # rerender right
702
+ move_cursor_down(old_dialog_vertical_offset + y_diff)
703
+ width = (old_dialog_column + DIALOG_WIDTH) - (dialog.column + DIALOG_WIDTH)
704
+ start = visual_start + old_dialog_vertical_offset
705
+ line_num = old_dialog_contents.size
706
+ line_num.times do |i|
707
+ Reline::IOGate.move_cursor_column(old_dialog_column + DIALOG_WIDTH)
708
+ if visual_lines[start + i].nil?
709
+ s = ' ' * width
710
+ else
711
+ s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column + DIALOG_WIDTH, width)
712
+ end
713
+ Reline::IOGate.move_cursor_column(dialog.column + DIALOG_WIDTH)
714
+ @output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
715
+ move_cursor_down(1) if i < (line_num - 1)
716
+ end
717
+ move_cursor_up(old_dialog_vertical_offset + line_num - 1 + y_diff)
718
+ end
719
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
720
+ end
721
+
722
+ private def clear_dialog
723
+ @dialogs.each do |dialog|
724
+ clear_each_dialog(dialog)
725
+ end
726
+ end
727
+
728
+ private def clear_each_dialog(dialog)
729
+ return unless dialog.contents
730
+ prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
731
+ visual_lines = []
732
+ visual_lines_under_dialog = []
733
+ visual_start = nil
734
+ dialog.lines_backup[:lines].each_with_index { |l, i|
735
+ pr = prompt_list ? prompt_list[i] : prompt
736
+ vl, _ = split_by_width(pr + l, @screen_size.last)
737
+ vl.compact!
738
+ if i == dialog.lines_backup[:line_index]
739
+ visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
740
+ end
741
+ visual_lines.concat(vl)
742
+ }
743
+ visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
744
+ visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
745
+ Reline::IOGate.hide_cursor
746
+ move_cursor_down(dialog.vertical_offset)
747
+ dialog_vertical_size = dialog.contents.size
748
+ dialog_vertical_size.times do |i|
749
+ if i < visual_lines_under_dialog.size
750
+ Reline::IOGate.move_cursor_column(0)
751
+ @output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % visual_lines_under_dialog[i]
752
+ else
753
+ Reline::IOGate.move_cursor_column(dialog.column)
754
+ @output.write "\e[39m\e[49m#{' ' * DIALOG_WIDTH}\e[39m\e[49m"
755
+ end
756
+ Reline::IOGate.erase_after_cursor
757
+ move_cursor_down(1) if i < (dialog_vertical_size - 1)
758
+ end
759
+ move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
760
+ Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
761
+ Reline::IOGate.show_cursor
762
+ end
763
+
475
764
  private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
476
765
  if @screen_height < highest_in_all
477
766
  old_scroll_partial_screen = @scroll_partial_screen
@@ -932,6 +1221,16 @@ class Reline::LineEditor
932
1221
  @completion_journey_data = CompletionJourneyData.new(
933
1222
  preposing, postposing,
934
1223
  [target] + list.select{ |item| item.start_with?(target) }, 0)
1224
+ if @completion_journey_data.list.size == 1
1225
+ @completion_journey_data.pointer = 0
1226
+ else
1227
+ case direction
1228
+ when :up
1229
+ @completion_journey_data.pointer = @completion_journey_data.list.size - 1
1230
+ when :down
1231
+ @completion_journey_data.pointer = 1
1232
+ end
1233
+ end
935
1234
  @completion_state = CompletionState::JOURNEY
936
1235
  else
937
1236
  case direction
@@ -946,13 +1245,13 @@ class Reline::LineEditor
946
1245
  @completion_journey_data.pointer = 0
947
1246
  end
948
1247
  end
949
- completed = @completion_journey_data.list[@completion_journey_data.pointer]
950
- @line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
951
- line_to_pointer = @completion_journey_data.preposing + completed
952
- @cursor_max = calculate_width(@line)
953
- @cursor = calculate_width(line_to_pointer)
954
- @byte_pointer = line_to_pointer.bytesize
955
1248
  end
1249
+ completed = @completion_journey_data.list[@completion_journey_data.pointer]
1250
+ @line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
1251
+ line_to_pointer = @completion_journey_data.preposing + completed
1252
+ @cursor_max = calculate_width(@line)
1253
+ @cursor = calculate_width(line_to_pointer)
1254
+ @byte_pointer = line_to_pointer.bytesize
956
1255
  end
957
1256
 
958
1257
  private def run_for_operators(key, method_symbol, &block)
@@ -1146,6 +1445,7 @@ class Reline::LineEditor
1146
1445
  end
1147
1446
  unless completion_occurs
1148
1447
  @completion_state = CompletionState::NORMAL
1448
+ @completion_journey_data = nil
1149
1449
  end
1150
1450
  if not @in_pasting and @just_cursor_moving.nil?
1151
1451
  if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
@@ -1165,7 +1465,13 @@ class Reline::LineEditor
1165
1465
 
1166
1466
  def call_completion_proc
1167
1467
  result = retrieve_completion_block(true)
1168
- preposing, target, postposing = result
1468
+ pre, target, post = result
1469
+ result = call_completion_proc_with_checking_args(pre, target, post)
1470
+ Reline.core.instance_variable_set(:@completion_quote_character, nil)
1471
+ result
1472
+ end
1473
+
1474
+ def call_completion_proc_with_checking_args(pre, target, post)
1169
1475
  if @completion_proc and target
1170
1476
  argnum = @completion_proc.parameters.inject(0) { |result, item|
1171
1477
  case item.first
@@ -1179,12 +1485,11 @@ class Reline::LineEditor
1179
1485
  when 1
1180
1486
  result = @completion_proc.(target)
1181
1487
  when 2
1182
- result = @completion_proc.(target, preposing)
1488
+ result = @completion_proc.(target, pre)
1183
1489
  when 3..Float::INFINITY
1184
- result = @completion_proc.(target, preposing, postposing)
1490
+ result = @completion_proc.(target, pre, post)
1185
1491
  end
1186
1492
  end
1187
- Reline.core.instance_variable_set(:@completion_quote_character, nil)
1188
1493
  result
1189
1494
  end
1190
1495
 
@@ -185,6 +185,36 @@ class Reline::Unicode
185
185
  [lines, height]
186
186
  end
187
187
 
188
+ # Take a chunk of a String with escape sequences.
189
+ def self.take_range(str, col, length, encoding = str.encoding)
190
+ chunk = String.new(encoding: encoding)
191
+ width = 0
192
+ rest = str.encode(Encoding::UTF_8)
193
+ in_zero_width = false
194
+ rest.scan(WIDTH_SCANNER) do |gc|
195
+ case
196
+ when gc[NON_PRINTING_START_INDEX]
197
+ in_zero_width = true
198
+ when gc[NON_PRINTING_END_INDEX]
199
+ in_zero_width = false
200
+ when gc[CSI_REGEXP_INDEX]
201
+ chunk << gc[CSI_REGEXP_INDEX]
202
+ when gc[OSC_REGEXP_INDEX]
203
+ chunk << gc[OSC_REGEXP_INDEX]
204
+ when gc[GRAPHEME_CLUSTER_INDEX]
205
+ gc = gc[GRAPHEME_CLUSTER_INDEX]
206
+ if in_zero_width
207
+ chunk << gc
208
+ else
209
+ width = get_mbchar_width(gc)
210
+ break if (width + length) <= col
211
+ chunk << gc if col <= width
212
+ end
213
+ end
214
+ end
215
+ chunk
216
+ end
217
+
188
218
  def self.get_next_mbchar_size(line, byte_pointer)
189
219
  grapheme = line.byteslice(byte_pointer..-1).grapheme_clusters.first
190
220
  grapheme ? grapheme.bytesize : 0
@@ -1,3 +1,3 @@
1
1
  module Reline
2
- VERSION = '0.2.7'
2
+ VERSION = '0.2.8.pre.1'
3
3
  end
@@ -133,9 +133,11 @@ class Reline::Windows
133
133
  @@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
134
134
  @@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
135
135
  @@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
136
+ @@SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L')
136
137
 
137
138
  @@GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L')
138
139
  @@SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
140
+ @@WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L')
139
141
  ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
140
142
 
141
143
  private_class_method def self.getconsolemode
@@ -218,6 +220,7 @@ class Reline::Windows
218
220
  def self.check_input_event
219
221
  num_of_events = 0.chr * 8
220
222
  while @@output_buf.empty? #or true
223
+ next if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
221
224
  next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack('L').first == 0
222
225
  input_record = 0.chr * 18
223
226
  read_event = 0.chr * 4
@@ -341,6 +344,20 @@ class Reline::Windows
341
344
  raise NotImplementedError
342
345
  end
343
346
 
347
+ def self.hide_cursor
348
+ size = 100
349
+ visible = 0 # 0 means false
350
+ cursor_info = [size, visible].pack('Li')
351
+ @@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info)
352
+ end
353
+
354
+ def self.show_cursor
355
+ size = 100
356
+ visible = 1 # 1 means true
357
+ cursor_info = [size, visible].pack('Li')
358
+ @@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info)
359
+ end
360
+
344
361
  def self.set_winch_handler(&handler)
345
362
  @@winch_handler = handler
346
363
  end
data/lib/reline.rb CHANGED
@@ -44,6 +44,7 @@ module Reline
44
44
 
45
45
  def initialize
46
46
  self.output = STDOUT
47
+ @dialog_proc_list = []
47
48
  yield self
48
49
  @completion_quote_character = nil
49
50
  @bracketed_paste_finished = false
@@ -130,6 +131,12 @@ module Reline
130
131
  @dig_perfect_match_proc = p
131
132
  end
132
133
 
134
+ def add_dialog_proc(name_sym, p, context = nil)
135
+ raise ArgumentError unless p.respond_to?(:call) or p.nil?
136
+ raise ArgumentError unless name_sym.instance_of?(Symbol)
137
+ @dialog_proc_list << [name_sym, p, context]
138
+ end
139
+
133
140
  def input=(val)
134
141
  raise TypeError unless val.respond_to?(:getc) or val.nil?
135
142
  if val.respond_to?(:getc)
@@ -171,6 +178,44 @@ module Reline
171
178
  Reline::IOGate.get_screen_size
172
179
  end
173
180
 
181
+ Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE = ->() {
182
+ # autocomplete
183
+ if just_cursor_moving and completion_journey_data.nil?
184
+ # Auto complete starts only when edited
185
+ return nil
186
+ end
187
+ pre, target, post= retrieve_completion_block(true)
188
+ if target.nil? or target.empty?# or target.size <= 3
189
+ return nil
190
+ end
191
+ if completion_journey_data and completion_journey_data.list
192
+ result = completion_journey_data.list.dup
193
+ result.shift
194
+ pointer = completion_journey_data.pointer - 1
195
+ else
196
+ result = call_completion_proc_with_checking_args(pre, target, post)
197
+ pointer = nil
198
+ end
199
+ if result and result.size == 1 and result[0] == target
200
+ result = nil
201
+ end
202
+ target_width = Reline::Unicode.calculate_width(target)
203
+ x = cursor_pos.x - target_width
204
+ if x < 0
205
+ x = screen_width + x
206
+ y = -1
207
+ else
208
+ y = 0
209
+ end
210
+ cursor_pos_to_render = Reline::CursorPos.new(x, y)
211
+ if context and context.is_a?(Array)
212
+ context.clear
213
+ context.push(cursor_pos_to_render, result, pointer)
214
+ end
215
+ [cursor_pos_to_render, result, pointer, nil]
216
+ }
217
+ Reline::DEFAULT_DIALOG_CONTEXT = Array.new
218
+
174
219
  def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
175
220
  unless confirm_multiline_termination
176
221
  raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
@@ -230,6 +275,10 @@ module Reline
230
275
  line_editor.auto_indent_proc = auto_indent_proc
231
276
  line_editor.dig_perfect_match_proc = dig_perfect_match_proc
232
277
  line_editor.pre_input_hook = pre_input_hook
278
+ @dialog_proc_list.each do |d|
279
+ name_sym, dialog_proc, context = d
280
+ line_editor.add_dialog_proc(name_sym, dialog_proc, context)
281
+ end
233
282
 
234
283
  unless config.test_mode
235
284
  config.read
@@ -424,6 +473,7 @@ module Reline
424
473
  def_single_delegators :core, :ambiguous_width
425
474
  def_single_delegators :core, :last_incremental_search
426
475
  def_single_delegators :core, :last_incremental_search=
476
+ def_single_delegators :core, :add_dialog_proc
427
477
 
428
478
  def_single_delegators :core, :readmultiline
429
479
  def_instance_delegators self, :readmultiline
@@ -445,6 +495,7 @@ module Reline
445
495
  core.completer_quote_characters = '"\''
446
496
  core.filename_quote_characters = ""
447
497
  core.special_prefixes = ""
498
+ core.add_dialog_proc(:autocomplete, Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE, Reline::DEFAULT_DIALOG_CONTEXT)
448
499
  }
449
500
  end
450
501
 
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.7
4
+ version: 0.2.8.pre.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - aycabta
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-12 00:00:00.000000000 Z
11
+ date: 2021-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: io-console
@@ -69,9 +69,9 @@ 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: '0'
74
+ version: 1.3.1
75
75
  requirements: []
76
76
  rubygems_version: 3.2.22
77
77
  signing_key: