reline 0.2.7 → 0.2.8.pre.4
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 +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:
|