reline 0.2.7 → 0.2.8.pre.1

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