reline 0.2.7 → 0.2.8.pre.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/reline/ansi.rb +17 -2
- data/lib/reline/config.rb +9 -0
- data/lib/reline/line_editor.rb +361 -13
- data/lib/reline/unicode.rb +30 -0
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +29 -0
- data/lib/reline.rb +62 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5e7c5785424de4deda942cdd54cab39bc7d96fd79b1451095626996b875a32d9
|
4
|
+
data.tar.gz: 3afcf3fc84d2b3d4dd48481bca613a8fc08de049983619772e9fcd502caffa83
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1be959cdf8fc499ec71184bc3768964dbc1eb078085bbd8303a6d9b4b0ade3ccd25a8d90486c620316b38c4dfe7b7fb224d99912ff855bd8bc3fc64946f1d5d
|
7
|
+
data.tar.gz: cfe31c94298208f8cda2c7fddeba8940e71589c60b06167fcb81e8c0d278055ffd94261713d1aa3a2d6031d6572c3dc9194412d15e57f83d0fb81e87361496c0
|
data/lib/reline/ansi.rb
CHANGED
@@ -37,6 +37,7 @@ class Reline::ANSI
|
|
37
37
|
# default bindings
|
38
38
|
[27, 32] => :em_set_mark, # M-<space>
|
39
39
|
[24, 24] => :em_exchange_mark, # C-x C-x
|
40
|
+
[27, 91, 90] => :completion_journey_up, # S-Tab
|
40
41
|
}.each_pair do |key, func|
|
41
42
|
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
42
43
|
end
|
@@ -274,6 +275,22 @@ class Reline::ANSI
|
|
274
275
|
end
|
275
276
|
end
|
276
277
|
|
278
|
+
def self.hide_cursor
|
279
|
+
if Reline::Terminfo.enabled?
|
280
|
+
@@output.write Reline::Terminfo.tigetstr('civis')
|
281
|
+
else
|
282
|
+
# ignored
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def self.show_cursor
|
287
|
+
if Reline::Terminfo.enabled?
|
288
|
+
@@output.write Reline::Terminfo.tigetstr('cnorm')
|
289
|
+
else
|
290
|
+
# ignored
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
277
294
|
def self.erase_after_cursor
|
278
295
|
@@output.write "\e[K"
|
279
296
|
end
|
@@ -295,8 +312,6 @@ class Reline::ANSI
|
|
295
312
|
|
296
313
|
def self.prep
|
297
314
|
retrieve_keybuffer
|
298
|
-
int_handle = Signal.trap('INT', 'IGNORE')
|
299
|
-
Signal.trap('INT', int_handle)
|
300
315
|
nil
|
301
316
|
end
|
302
317
|
|
data/lib/reline/config.rb
CHANGED
@@ -65,6 +65,7 @@ class Reline::Config
|
|
65
65
|
@history_size = -1 # unlimited
|
66
66
|
@keyseq_timeout = 500
|
67
67
|
@test_mode = false
|
68
|
+
@autocompletion = false
|
68
69
|
end
|
69
70
|
|
70
71
|
def reset
|
@@ -89,6 +90,14 @@ class Reline::Config
|
|
89
90
|
(val.respond_to?(:any?) ? val : [val]).any?(@editing_mode_label)
|
90
91
|
end
|
91
92
|
|
93
|
+
def autocompletion=(val)
|
94
|
+
@autocompletion = val
|
95
|
+
end
|
96
|
+
|
97
|
+
def autocompletion
|
98
|
+
@autocompletion
|
99
|
+
end
|
100
|
+
|
92
101
|
def keymap
|
93
102
|
@key_actors[@keymap_label]
|
94
103
|
end
|
data/lib/reline/line_editor.rb
CHANGED
@@ -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
|
-
|
456
|
-
|
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,319 @@ class Reline::LineEditor
|
|
472
478
|
end
|
473
479
|
end
|
474
480
|
|
481
|
+
class DialogProcScope
|
482
|
+
def initialize(line_editor, config, proc_to_exec, context)
|
483
|
+
@line_editor = line_editor
|
484
|
+
@config = config
|
485
|
+
@proc_to_exec = proc_to_exec
|
486
|
+
@context = context
|
487
|
+
@cursor_pos = Reline::CursorPos.new
|
488
|
+
end
|
489
|
+
|
490
|
+
def context
|
491
|
+
@context
|
492
|
+
end
|
493
|
+
|
494
|
+
def retrieve_completion_block(set_completion_quote_character = false)
|
495
|
+
@line_editor.retrieve_completion_block(set_completion_quote_character)
|
496
|
+
end
|
497
|
+
|
498
|
+
def call_completion_proc_with_checking_args(pre, target, post)
|
499
|
+
@line_editor.call_completion_proc_with_checking_args(pre, target, post)
|
500
|
+
end
|
501
|
+
|
502
|
+
def set_dialog(dialog)
|
503
|
+
@dialog = dialog
|
504
|
+
end
|
505
|
+
|
506
|
+
def dialog
|
507
|
+
@dialog
|
508
|
+
end
|
509
|
+
|
510
|
+
def set_cursor_pos(col, row)
|
511
|
+
@cursor_pos.x = col
|
512
|
+
@cursor_pos.y = row
|
513
|
+
end
|
514
|
+
|
515
|
+
def cursor_pos
|
516
|
+
@cursor_pos
|
517
|
+
end
|
518
|
+
|
519
|
+
def just_cursor_moving
|
520
|
+
@line_editor.instance_variable_get(:@just_cursor_moving)
|
521
|
+
end
|
522
|
+
|
523
|
+
def screen_width
|
524
|
+
@line_editor.instance_variable_get(:@screen_size).last
|
525
|
+
end
|
526
|
+
|
527
|
+
def completion_journey_data
|
528
|
+
@line_editor.instance_variable_get(:@completion_journey_data)
|
529
|
+
end
|
530
|
+
|
531
|
+
def config
|
532
|
+
@config
|
533
|
+
end
|
534
|
+
|
535
|
+
def call
|
536
|
+
instance_exec(&@proc_to_exec)
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
class Dialog
|
541
|
+
attr_reader :name
|
542
|
+
attr_accessor :scroll_top, :column, :vertical_offset, :contents, :lines_backup
|
543
|
+
|
544
|
+
def initialize(name, proc_scope)
|
545
|
+
@name = name
|
546
|
+
@proc_scope = proc_scope
|
547
|
+
@scroll_top = 0
|
548
|
+
end
|
549
|
+
|
550
|
+
def set_cursor_pos(col, row)
|
551
|
+
@proc_scope.set_cursor_pos(col, row)
|
552
|
+
end
|
553
|
+
|
554
|
+
def call
|
555
|
+
@proc_scope.set_dialog(self)
|
556
|
+
@proc_scope.call
|
557
|
+
end
|
558
|
+
end
|
559
|
+
|
560
|
+
def add_dialog_proc(name, p, context = nil)
|
561
|
+
return if @dialogs.any? { |d| d.name == name }
|
562
|
+
@dialogs << Dialog.new(name, DialogProcScope.new(self, @config, p, context))
|
563
|
+
end
|
564
|
+
|
565
|
+
DIALOG_HEIGHT = 20
|
566
|
+
DIALOG_WIDTH = 40
|
567
|
+
private def render_dialog(cursor_column)
|
568
|
+
@dialogs.each do |dialog|
|
569
|
+
render_each_dialog(dialog, cursor_column)
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
private def render_each_dialog(dialog, cursor_column)
|
574
|
+
if @in_pasting
|
575
|
+
dialog.contents = nil
|
576
|
+
return
|
577
|
+
end
|
578
|
+
dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
|
579
|
+
dialog_render_info = dialog.call
|
580
|
+
old_dialog_contents = dialog.contents
|
581
|
+
old_dialog_column = dialog.column
|
582
|
+
old_dialog_vertical_offset = dialog.vertical_offset
|
583
|
+
start = 0
|
584
|
+
if dialog_render_info and dialog_render_info.contents and not dialog_render_info.contents.empty?
|
585
|
+
height = dialog_render_info.height || DIALOG_HEIGHT
|
586
|
+
pointer = dialog_render_info.pointer
|
587
|
+
dialog.contents = dialog_render_info.contents
|
588
|
+
if dialog.contents.size > height
|
589
|
+
if dialog_render_info.pointer
|
590
|
+
if dialog_render_info.pointer < 0
|
591
|
+
dialog.scroll_top = 0
|
592
|
+
elsif (dialog_render_info.pointer - dialog.scroll_top) >= (height - 1)
|
593
|
+
dialog.scroll_top = dialog_render_info.pointer - (height - 1)
|
594
|
+
elsif (dialog_render_info.pointer - dialog.scroll_top) < 0
|
595
|
+
dialog.scroll_top = dialog_render_info.pointer
|
596
|
+
end
|
597
|
+
pointer = dialog_render_info.pointer - dialog.scroll_top
|
598
|
+
end
|
599
|
+
dialog.contents = dialog.contents[dialog.scroll_top, height]
|
600
|
+
end
|
601
|
+
else
|
602
|
+
dialog.lines_backup = {
|
603
|
+
lines: modify_lines(whole_lines),
|
604
|
+
line_index: @line_index,
|
605
|
+
first_line_started_from: @first_line_started_from,
|
606
|
+
started_from: @started_from,
|
607
|
+
byte_pointer: @byte_pointer
|
608
|
+
}
|
609
|
+
clear_each_dialog(dialog)
|
610
|
+
dialog.contents = nil
|
611
|
+
return
|
612
|
+
end
|
613
|
+
upper_space = @first_line_started_from - @started_from
|
614
|
+
lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
|
615
|
+
dialog.column = dialog_render_info.pos.x
|
616
|
+
diff = (dialog.column + DIALOG_WIDTH) - (@screen_size.last - 1)
|
617
|
+
if diff > 0
|
618
|
+
dialog.column -= diff
|
619
|
+
end
|
620
|
+
if (lower_space + @rest_height - dialog_render_info.pos.y) >= height
|
621
|
+
dialog.vertical_offset = dialog_render_info.pos.y + 1
|
622
|
+
elsif upper_space >= height
|
623
|
+
dialog.vertical_offset = dialog_render_info.pos.y + -(height + 1)
|
624
|
+
else
|
625
|
+
if (lower_space + @rest_height - dialog_render_info.pos.y) < height
|
626
|
+
scroll_down(height + dialog_render_info.pos.y)
|
627
|
+
move_cursor_up(height + dialog_render_info.pos.y)
|
628
|
+
end
|
629
|
+
dialog.vertical_offset = dialog_render_info.pos.y + 1
|
630
|
+
end
|
631
|
+
Reline::IOGate.hide_cursor
|
632
|
+
reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
|
633
|
+
move_cursor_down(dialog.vertical_offset)
|
634
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
635
|
+
dialog.contents.each_with_index do |item, i|
|
636
|
+
if i == pointer
|
637
|
+
bg_color = '45'
|
638
|
+
else
|
639
|
+
if dialog_render_info.bg_color
|
640
|
+
bg_color = dialog_render_info.bg_color
|
641
|
+
else
|
642
|
+
bg_color = '46'
|
643
|
+
end
|
644
|
+
end
|
645
|
+
@output.write "\e[#{bg_color}m%-#{DIALOG_WIDTH}s\e[49m" % item.slice(0, DIALOG_WIDTH)
|
646
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
647
|
+
move_cursor_down(1) if i < (dialog.contents.size - 1)
|
648
|
+
end
|
649
|
+
Reline::IOGate.move_cursor_column(cursor_column)
|
650
|
+
move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
|
651
|
+
Reline::IOGate.show_cursor
|
652
|
+
dialog.lines_backup = {
|
653
|
+
lines: modify_lines(whole_lines),
|
654
|
+
line_index: @line_index,
|
655
|
+
first_line_started_from: @first_line_started_from,
|
656
|
+
started_from: @started_from,
|
657
|
+
byte_pointer: @byte_pointer
|
658
|
+
}
|
659
|
+
end
|
660
|
+
|
661
|
+
private def reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
|
662
|
+
return if dialog.lines_backup.nil? or old_dialog_contents.nil?
|
663
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
|
664
|
+
visual_lines = []
|
665
|
+
visual_start = nil
|
666
|
+
dialog.lines_backup[:lines].each_with_index { |l, i|
|
667
|
+
pr = prompt_list ? prompt_list[i] : prompt
|
668
|
+
vl, _ = split_by_width(pr + l, @screen_size.last)
|
669
|
+
vl.compact!
|
670
|
+
if i == dialog.lines_backup[:line_index]
|
671
|
+
visual_start = visual_lines.size + dialog.lines_backup[:started_from]
|
672
|
+
end
|
673
|
+
visual_lines.concat(vl)
|
674
|
+
}
|
675
|
+
old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
|
676
|
+
y = @first_line_started_from + @started_from
|
677
|
+
y_diff = y - old_y
|
678
|
+
if (old_y + old_dialog_vertical_offset) < (y + dialog.vertical_offset)
|
679
|
+
# rerender top
|
680
|
+
move_cursor_down(old_dialog_vertical_offset - y_diff)
|
681
|
+
start = visual_start + old_dialog_vertical_offset
|
682
|
+
line_num = dialog.vertical_offset - old_dialog_vertical_offset
|
683
|
+
line_num.times do |i|
|
684
|
+
Reline::IOGate.move_cursor_column(old_dialog_column)
|
685
|
+
if visual_lines[start + i].nil?
|
686
|
+
s = ' ' * DIALOG_WIDTH
|
687
|
+
else
|
688
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
|
689
|
+
end
|
690
|
+
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
|
691
|
+
move_cursor_down(1) if i < (line_num - 1)
|
692
|
+
end
|
693
|
+
move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
|
694
|
+
end
|
695
|
+
if (old_y + old_dialog_vertical_offset + old_dialog_contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
|
696
|
+
# rerender bottom
|
697
|
+
move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
|
698
|
+
start = visual_start + dialog.vertical_offset + dialog.contents.size
|
699
|
+
line_num = (old_dialog_vertical_offset + old_dialog_contents.size) - (dialog.vertical_offset + dialog.contents.size)
|
700
|
+
line_num.times do |i|
|
701
|
+
Reline::IOGate.move_cursor_column(old_dialog_column)
|
702
|
+
if visual_lines[start + i].nil?
|
703
|
+
s = ' ' * DIALOG_WIDTH
|
704
|
+
else
|
705
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
|
706
|
+
end
|
707
|
+
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
|
708
|
+
move_cursor_down(1) if i < (line_num - 1)
|
709
|
+
end
|
710
|
+
move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
|
711
|
+
end
|
712
|
+
if old_dialog_column < dialog.column
|
713
|
+
# rerender left
|
714
|
+
move_cursor_down(old_dialog_vertical_offset - y_diff)
|
715
|
+
width = dialog.column - old_dialog_column
|
716
|
+
start = visual_start + old_dialog_vertical_offset
|
717
|
+
line_num = old_dialog_contents.size
|
718
|
+
line_num.times do |i|
|
719
|
+
Reline::IOGate.move_cursor_column(old_dialog_column)
|
720
|
+
if visual_lines[start + i].nil?
|
721
|
+
s = ' ' * width
|
722
|
+
else
|
723
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, width)
|
724
|
+
end
|
725
|
+
@output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
|
726
|
+
move_cursor_down(1) if i < (line_num - 1)
|
727
|
+
end
|
728
|
+
move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
|
729
|
+
end
|
730
|
+
if (old_dialog_column + DIALOG_WIDTH) > (dialog.column + DIALOG_WIDTH)
|
731
|
+
# rerender right
|
732
|
+
move_cursor_down(old_dialog_vertical_offset + y_diff)
|
733
|
+
width = (old_dialog_column + DIALOG_WIDTH) - (dialog.column + DIALOG_WIDTH)
|
734
|
+
start = visual_start + old_dialog_vertical_offset
|
735
|
+
line_num = old_dialog_contents.size
|
736
|
+
line_num.times do |i|
|
737
|
+
Reline::IOGate.move_cursor_column(old_dialog_column + DIALOG_WIDTH)
|
738
|
+
if visual_lines[start + i].nil?
|
739
|
+
s = ' ' * width
|
740
|
+
else
|
741
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column + DIALOG_WIDTH, width)
|
742
|
+
end
|
743
|
+
Reline::IOGate.move_cursor_column(dialog.column + DIALOG_WIDTH)
|
744
|
+
@output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
|
745
|
+
move_cursor_down(1) if i < (line_num - 1)
|
746
|
+
end
|
747
|
+
move_cursor_up(old_dialog_vertical_offset + line_num - 1 + y_diff)
|
748
|
+
end
|
749
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
750
|
+
end
|
751
|
+
|
752
|
+
private def clear_dialog
|
753
|
+
@dialogs.each do |dialog|
|
754
|
+
clear_each_dialog(dialog)
|
755
|
+
end
|
756
|
+
end
|
757
|
+
|
758
|
+
private def clear_each_dialog(dialog)
|
759
|
+
return unless dialog.contents
|
760
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
|
761
|
+
visual_lines = []
|
762
|
+
visual_lines_under_dialog = []
|
763
|
+
visual_start = nil
|
764
|
+
dialog.lines_backup[:lines].each_with_index { |l, i|
|
765
|
+
pr = prompt_list ? prompt_list[i] : prompt
|
766
|
+
vl, _ = split_by_width(pr + l, @screen_size.last)
|
767
|
+
vl.compact!
|
768
|
+
if i == dialog.lines_backup[:line_index]
|
769
|
+
visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
|
770
|
+
end
|
771
|
+
visual_lines.concat(vl)
|
772
|
+
}
|
773
|
+
visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
|
774
|
+
visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
|
775
|
+
Reline::IOGate.hide_cursor
|
776
|
+
move_cursor_down(dialog.vertical_offset)
|
777
|
+
dialog_vertical_size = dialog.contents.size
|
778
|
+
dialog_vertical_size.times do |i|
|
779
|
+
if i < visual_lines_under_dialog.size
|
780
|
+
Reline::IOGate.move_cursor_column(0)
|
781
|
+
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % visual_lines_under_dialog[i]
|
782
|
+
else
|
783
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
784
|
+
@output.write "\e[39m\e[49m#{' ' * DIALOG_WIDTH}\e[39m\e[49m"
|
785
|
+
end
|
786
|
+
Reline::IOGate.erase_after_cursor
|
787
|
+
move_cursor_down(1) if i < (dialog_vertical_size - 1)
|
788
|
+
end
|
789
|
+
move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
|
790
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
791
|
+
Reline::IOGate.show_cursor
|
792
|
+
end
|
793
|
+
|
475
794
|
private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
|
476
795
|
if @screen_height < highest_in_all
|
477
796
|
old_scroll_partial_screen = @scroll_partial_screen
|
@@ -932,6 +1251,16 @@ class Reline::LineEditor
|
|
932
1251
|
@completion_journey_data = CompletionJourneyData.new(
|
933
1252
|
preposing, postposing,
|
934
1253
|
[target] + list.select{ |item| item.start_with?(target) }, 0)
|
1254
|
+
if @completion_journey_data.list.size == 1
|
1255
|
+
@completion_journey_data.pointer = 0
|
1256
|
+
else
|
1257
|
+
case direction
|
1258
|
+
when :up
|
1259
|
+
@completion_journey_data.pointer = @completion_journey_data.list.size - 1
|
1260
|
+
when :down
|
1261
|
+
@completion_journey_data.pointer = 1
|
1262
|
+
end
|
1263
|
+
end
|
935
1264
|
@completion_state = CompletionState::JOURNEY
|
936
1265
|
else
|
937
1266
|
case direction
|
@@ -946,13 +1275,13 @@ class Reline::LineEditor
|
|
946
1275
|
@completion_journey_data.pointer = 0
|
947
1276
|
end
|
948
1277
|
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
1278
|
end
|
1279
|
+
completed = @completion_journey_data.list[@completion_journey_data.pointer]
|
1280
|
+
@line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
|
1281
|
+
line_to_pointer = @completion_journey_data.preposing + completed
|
1282
|
+
@cursor_max = calculate_width(@line)
|
1283
|
+
@cursor = calculate_width(line_to_pointer)
|
1284
|
+
@byte_pointer = line_to_pointer.bytesize
|
956
1285
|
end
|
957
1286
|
|
958
1287
|
private def run_for_operators(key, method_symbol, &block)
|
@@ -1127,7 +1456,20 @@ class Reline::LineEditor
|
|
1127
1456
|
if result.is_a?(Array)
|
1128
1457
|
completion_occurs = true
|
1129
1458
|
process_insert
|
1130
|
-
|
1459
|
+
if @config.autocompletion
|
1460
|
+
move_completed_list(result, :down)
|
1461
|
+
else
|
1462
|
+
complete(result)
|
1463
|
+
end
|
1464
|
+
end
|
1465
|
+
end
|
1466
|
+
elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up
|
1467
|
+
if not @config.disable_completion and @config.autocompletion
|
1468
|
+
result = call_completion_proc
|
1469
|
+
if result.is_a?(Array)
|
1470
|
+
completion_occurs = true
|
1471
|
+
process_insert
|
1472
|
+
move_completed_list(result, :up)
|
1131
1473
|
end
|
1132
1474
|
end
|
1133
1475
|
elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
|
@@ -1146,6 +1488,7 @@ class Reline::LineEditor
|
|
1146
1488
|
end
|
1147
1489
|
unless completion_occurs
|
1148
1490
|
@completion_state = CompletionState::NORMAL
|
1491
|
+
@completion_journey_data = nil
|
1149
1492
|
end
|
1150
1493
|
if not @in_pasting and @just_cursor_moving.nil?
|
1151
1494
|
if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
|
@@ -1165,7 +1508,13 @@ class Reline::LineEditor
|
|
1165
1508
|
|
1166
1509
|
def call_completion_proc
|
1167
1510
|
result = retrieve_completion_block(true)
|
1168
|
-
|
1511
|
+
pre, target, post = result
|
1512
|
+
result = call_completion_proc_with_checking_args(pre, target, post)
|
1513
|
+
Reline.core.instance_variable_set(:@completion_quote_character, nil)
|
1514
|
+
result
|
1515
|
+
end
|
1516
|
+
|
1517
|
+
def call_completion_proc_with_checking_args(pre, target, post)
|
1169
1518
|
if @completion_proc and target
|
1170
1519
|
argnum = @completion_proc.parameters.inject(0) { |result, item|
|
1171
1520
|
case item.first
|
@@ -1179,12 +1528,11 @@ class Reline::LineEditor
|
|
1179
1528
|
when 1
|
1180
1529
|
result = @completion_proc.(target)
|
1181
1530
|
when 2
|
1182
|
-
result = @completion_proc.(target,
|
1531
|
+
result = @completion_proc.(target, pre)
|
1183
1532
|
when 3..Float::INFINITY
|
1184
|
-
result = @completion_proc.(target,
|
1533
|
+
result = @completion_proc.(target, pre, post)
|
1185
1534
|
end
|
1186
1535
|
end
|
1187
|
-
Reline.core.instance_variable_set(:@completion_quote_character, nil)
|
1188
1536
|
result
|
1189
1537
|
end
|
1190
1538
|
|
data/lib/reline/unicode.rb
CHANGED
@@ -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
|
data/lib/reline/version.rb
CHANGED
data/lib/reline/windows.rb
CHANGED
@@ -42,6 +42,14 @@ class Reline::Windows
|
|
42
42
|
}.each_pair do |key, func|
|
43
43
|
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
44
44
|
end
|
45
|
+
|
46
|
+
# Emulate ANSI key sequence.
|
47
|
+
{
|
48
|
+
[27, 91, 90] => :completion_journey_up, # S-Tab
|
49
|
+
}.each_pair do |key, func|
|
50
|
+
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
51
|
+
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
|
52
|
+
end
|
45
53
|
end
|
46
54
|
|
47
55
|
if defined? JRUBY_VERSION
|
@@ -106,6 +114,7 @@ class Reline::Windows
|
|
106
114
|
SCROLLLOCK_ON = 0x0040
|
107
115
|
SHIFT_PRESSED = 0x0010
|
108
116
|
|
117
|
+
VK_TAB = 0x09
|
109
118
|
VK_END = 0x23
|
110
119
|
VK_HOME = 0x24
|
111
120
|
VK_LEFT = 0x25
|
@@ -133,9 +142,11 @@ class Reline::Windows
|
|
133
142
|
@@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
|
134
143
|
@@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
|
135
144
|
@@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
|
145
|
+
@@SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L')
|
136
146
|
|
137
147
|
@@GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L')
|
138
148
|
@@SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
|
149
|
+
@@WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L')
|
139
150
|
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
|
140
151
|
|
141
152
|
private_class_method def self.getconsolemode
|
@@ -197,6 +208,9 @@ class Reline::Windows
|
|
197
208
|
[ { control_keys: [], virtual_key_code: VK_DELETE }, [0, 83] ],
|
198
209
|
[ { control_keys: [], virtual_key_code: VK_HOME }, [0, 71] ],
|
199
210
|
[ { control_keys: [], virtual_key_code: VK_END }, [0, 79] ],
|
211
|
+
|
212
|
+
# Emulate ANSI key sequence.
|
213
|
+
[ { control_keys: :SHIFT, virtual_key_code: VK_TAB }, [27, 91, 90] ],
|
200
214
|
]
|
201
215
|
|
202
216
|
def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
|
@@ -218,6 +232,7 @@ class Reline::Windows
|
|
218
232
|
def self.check_input_event
|
219
233
|
num_of_events = 0.chr * 8
|
220
234
|
while @@output_buf.empty? #or true
|
235
|
+
next if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
|
221
236
|
next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack('L').first == 0
|
222
237
|
input_record = 0.chr * 18
|
223
238
|
read_event = 0.chr * 4
|
@@ -341,6 +356,20 @@ class Reline::Windows
|
|
341
356
|
raise NotImplementedError
|
342
357
|
end
|
343
358
|
|
359
|
+
def self.hide_cursor
|
360
|
+
size = 100
|
361
|
+
visible = 0 # 0 means false
|
362
|
+
cursor_info = [size, visible].pack('Li')
|
363
|
+
@@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info)
|
364
|
+
end
|
365
|
+
|
366
|
+
def self.show_cursor
|
367
|
+
size = 100
|
368
|
+
visible = 1 # 1 means true
|
369
|
+
cursor_info = [size, visible].pack('Li')
|
370
|
+
@@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info)
|
371
|
+
end
|
372
|
+
|
344
373
|
def self.set_winch_handler(&handler)
|
345
374
|
@@winch_handler = handler
|
346
375
|
end
|
data/lib/reline.rb
CHANGED
@@ -18,6 +18,7 @@ module Reline
|
|
18
18
|
|
19
19
|
Key = Struct.new('Key', :char, :combined_char, :with_meta)
|
20
20
|
CursorPos = Struct.new(:x, :y)
|
21
|
+
DialogRenderInfo = Struct.new(:pos, :contents, :pointer, :bg_color, :height, keyword_init: true)
|
21
22
|
|
22
23
|
class Core
|
23
24
|
ATTR_READER_NAMES = %i(
|
@@ -44,6 +45,7 @@ module Reline
|
|
44
45
|
|
45
46
|
def initialize
|
46
47
|
self.output = STDOUT
|
48
|
+
@dialog_proc_list = []
|
47
49
|
yield self
|
48
50
|
@completion_quote_character = nil
|
49
51
|
@bracketed_paste_finished = false
|
@@ -106,6 +108,14 @@ module Reline
|
|
106
108
|
@completion_proc = p
|
107
109
|
end
|
108
110
|
|
111
|
+
def autocompletion
|
112
|
+
@config.autocompletion
|
113
|
+
end
|
114
|
+
|
115
|
+
def autocompletion=(val)
|
116
|
+
@config.autocompletion = val
|
117
|
+
end
|
118
|
+
|
109
119
|
def output_modifier_proc=(p)
|
110
120
|
raise ArgumentError unless p.respond_to?(:call) or p.nil?
|
111
121
|
@output_modifier_proc = p
|
@@ -130,6 +140,12 @@ module Reline
|
|
130
140
|
@dig_perfect_match_proc = p
|
131
141
|
end
|
132
142
|
|
143
|
+
def add_dialog_proc(name_sym, p, context = nil)
|
144
|
+
raise ArgumentError unless p.respond_to?(:call) or p.nil?
|
145
|
+
raise ArgumentError unless name_sym.instance_of?(Symbol)
|
146
|
+
@dialog_proc_list << [name_sym, p, context]
|
147
|
+
end
|
148
|
+
|
133
149
|
def input=(val)
|
134
150
|
raise TypeError unless val.respond_to?(:getc) or val.nil?
|
135
151
|
if val.respond_to?(:getc)
|
@@ -171,6 +187,45 @@ module Reline
|
|
171
187
|
Reline::IOGate.get_screen_size
|
172
188
|
end
|
173
189
|
|
190
|
+
Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE = ->() {
|
191
|
+
# autocomplete
|
192
|
+
return nil unless config.autocompletion
|
193
|
+
if just_cursor_moving and completion_journey_data.nil?
|
194
|
+
# Auto complete starts only when edited
|
195
|
+
return nil
|
196
|
+
end
|
197
|
+
pre, target, post= retrieve_completion_block(true)
|
198
|
+
if target.nil? or target.empty?# or target.size <= 3
|
199
|
+
return nil
|
200
|
+
end
|
201
|
+
if completion_journey_data and completion_journey_data.list
|
202
|
+
result = completion_journey_data.list.dup
|
203
|
+
result.shift
|
204
|
+
pointer = completion_journey_data.pointer - 1
|
205
|
+
else
|
206
|
+
result = call_completion_proc_with_checking_args(pre, target, post)
|
207
|
+
pointer = nil
|
208
|
+
end
|
209
|
+
if result and result.size == 1 and result[0] == target
|
210
|
+
result = nil
|
211
|
+
end
|
212
|
+
target_width = Reline::Unicode.calculate_width(target)
|
213
|
+
x = cursor_pos.x - target_width
|
214
|
+
if x < 0
|
215
|
+
x = screen_width + x
|
216
|
+
y = -1
|
217
|
+
else
|
218
|
+
y = 0
|
219
|
+
end
|
220
|
+
cursor_pos_to_render = Reline::CursorPos.new(x, y)
|
221
|
+
if context and context.is_a?(Array)
|
222
|
+
context.clear
|
223
|
+
context.push(cursor_pos_to_render, result, pointer, dialog)
|
224
|
+
end
|
225
|
+
DialogRenderInfo.new(pos: cursor_pos_to_render, contents: result, pointer: pointer, height: 15)
|
226
|
+
}
|
227
|
+
Reline::DEFAULT_DIALOG_CONTEXT = Array.new
|
228
|
+
|
174
229
|
def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
|
175
230
|
unless confirm_multiline_termination
|
176
231
|
raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
|
@@ -230,6 +285,10 @@ module Reline
|
|
230
285
|
line_editor.auto_indent_proc = auto_indent_proc
|
231
286
|
line_editor.dig_perfect_match_proc = dig_perfect_match_proc
|
232
287
|
line_editor.pre_input_hook = pre_input_hook
|
288
|
+
@dialog_proc_list.each do |d|
|
289
|
+
name_sym, dialog_proc, context = d
|
290
|
+
line_editor.add_dialog_proc(name_sym, dialog_proc, context)
|
291
|
+
end
|
233
292
|
|
234
293
|
unless config.test_mode
|
235
294
|
config.read
|
@@ -424,6 +483,8 @@ module Reline
|
|
424
483
|
def_single_delegators :core, :ambiguous_width
|
425
484
|
def_single_delegators :core, :last_incremental_search
|
426
485
|
def_single_delegators :core, :last_incremental_search=
|
486
|
+
def_single_delegators :core, :add_dialog_proc
|
487
|
+
def_single_delegators :core, :autocompletion, :autocompletion=
|
427
488
|
|
428
489
|
def_single_delegators :core, :readmultiline
|
429
490
|
def_instance_delegators self, :readmultiline
|
@@ -445,6 +506,7 @@ module Reline
|
|
445
506
|
core.completer_quote_characters = '"\''
|
446
507
|
core.filename_quote_characters = ""
|
447
508
|
core.special_prefixes = ""
|
509
|
+
core.add_dialog_proc(:autocomplete, Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE, Reline::DEFAULT_DIALOG_CONTEXT)
|
448
510
|
}
|
449
511
|
end
|
450
512
|
|
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.
|
4
|
+
version: 0.2.8.pre.4
|
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-
|
11
|
+
date: 2021-08-30 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:
|
74
|
+
version: 1.3.1
|
75
75
|
requirements: []
|
76
76
|
rubygems_version: 3.2.22
|
77
77
|
signing_key:
|