reline 0.2.6 → 0.2.8.pre.3
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 +20 -10
- data/lib/reline/config.rb +19 -2
- data/lib/reline/line_editor.rb +347 -16
- data/lib/reline/terminfo.rb +52 -10
- data/lib/reline/unicode.rb +30 -0
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +99 -38
- data/lib/reline.rb +64 -0
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92eb26edb27bc79f95c9f54a4396b86e764acb928a4d704db8b8d1016331aa67
|
4
|
+
data.tar.gz: 04b7af1f6107bc8cd302494a057e6cfd812c60ec3768b3cd475105d8e119d20c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz: '
|
6
|
+
metadata.gz: 40503d95634408feb981bcb3448679ed6c847123465b5b965399c75c4bbc9ac0ddbb5422386d209613e45b34a85f2ecb275fac7587272da88de7a16b2feb40b0
|
7
|
+
data.tar.gz: '030383e1c77101f296344c794443439af32c6fd7f2f86f0230666287df901e5aea9f25d78242ab9ea16253ab17faa97c02fc234137e166a71edbac2de2f44f16'
|
data/lib/reline/ansi.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'io/console'
|
2
|
+
require 'io/wait'
|
2
3
|
require 'timeout'
|
3
4
|
require_relative 'terminfo'
|
4
5
|
|
@@ -36,6 +37,7 @@ class Reline::ANSI
|
|
36
37
|
# default bindings
|
37
38
|
[27, 32] => :em_set_mark, # M-<space>
|
38
39
|
[24, 24] => :em_exchange_mark, # C-x C-x
|
40
|
+
[27, 91, 90] => :completion_journey_up, # S-Tab
|
39
41
|
}.each_pair do |key, func|
|
40
42
|
config.add_default_key_binding_by_keymap(:emacs, key, func)
|
41
43
|
end
|
@@ -183,12 +185,7 @@ class Reline::ANSI
|
|
183
185
|
unless @@buf.empty?
|
184
186
|
return false
|
185
187
|
end
|
186
|
-
|
187
|
-
if rs and rs[0]
|
188
|
-
false
|
189
|
-
else
|
190
|
-
true
|
191
|
-
end
|
188
|
+
!@@input.wait_readable(0)
|
192
189
|
end
|
193
190
|
|
194
191
|
def self.ungetc(c)
|
@@ -197,8 +194,7 @@ class Reline::ANSI
|
|
197
194
|
|
198
195
|
def self.retrieve_keybuffer
|
199
196
|
begin
|
200
|
-
|
201
|
-
return if result.nil?
|
197
|
+
return unless @@input.wait_readable(0.001)
|
202
198
|
str = @@input.read_nonblock(1024)
|
203
199
|
str.bytes.each do |c|
|
204
200
|
@@buf.push(c)
|
@@ -279,6 +275,22 @@ class Reline::ANSI
|
|
279
275
|
end
|
280
276
|
end
|
281
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
|
+
|
282
294
|
def self.erase_after_cursor
|
283
295
|
@@output.write "\e[K"
|
284
296
|
end
|
@@ -300,8 +312,6 @@ class Reline::ANSI
|
|
300
312
|
|
301
313
|
def self.prep
|
302
314
|
retrieve_keybuffer
|
303
|
-
int_handle = Signal.trap('INT', 'IGNORE')
|
304
|
-
Signal.trap('INT', int_handle)
|
305
315
|
nil
|
306
316
|
end
|
307
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
|
@@ -74,6 +75,7 @@ class Reline::Config
|
|
74
75
|
@additional_key_bindings.keys.each do |key|
|
75
76
|
@additional_key_bindings[key].clear
|
76
77
|
end
|
78
|
+
reset_default_key_bindings
|
77
79
|
end
|
78
80
|
|
79
81
|
def editing_mode
|
@@ -88,6 +90,14 @@ class Reline::Config
|
|
88
90
|
(val.respond_to?(:any?) ? val : [val]).any?(@editing_mode_label)
|
89
91
|
end
|
90
92
|
|
93
|
+
def autocompletion=(val)
|
94
|
+
@autocompletion = val
|
95
|
+
end
|
96
|
+
|
97
|
+
def autocompletion
|
98
|
+
@autocompletion
|
99
|
+
end
|
100
|
+
|
91
101
|
def keymap
|
92
102
|
@key_actors[@keymap_label]
|
93
103
|
end
|
@@ -157,8 +167,15 @@ class Reline::Config
|
|
157
167
|
end
|
158
168
|
|
159
169
|
def read_lines(lines, file = nil)
|
160
|
-
if lines.first.encoding != Reline.encoding_system_needs
|
161
|
-
|
170
|
+
if not lines.empty? and lines.first.encoding != Reline.encoding_system_needs
|
171
|
+
begin
|
172
|
+
lines = lines.map do |l|
|
173
|
+
l.encode(Reline.encoding_system_needs)
|
174
|
+
rescue Encoding::UndefinedConversionError
|
175
|
+
mes = "The inputrc encoded in #{lines.first.encoding.name} can't be converted to the locale #{Reline.encoding_system_needs.name}."
|
176
|
+
raise Reline::ConfigEncodingConversionError.new(mes)
|
177
|
+
end
|
178
|
+
end
|
162
179
|
end
|
163
180
|
conditions = [@skip_section, @if_stack]
|
164
181
|
@skip_section = nil
|
data/lib/reline/line_editor.rb
CHANGED
@@ -150,7 +150,8 @@ class Reline::LineEditor
|
|
150
150
|
@screen_size = Reline::IOGate.get_screen_size
|
151
151
|
@screen_height = @screen_size.first
|
152
152
|
reset_variables(prompt, encoding: encoding)
|
153
|
-
@old_trap = Signal.trap(
|
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
|
@@ -158,8 +159,16 @@ class Reline::LineEditor
|
|
158
159
|
end
|
159
160
|
Reline::IOGate.move_cursor_column(0)
|
160
161
|
scroll_down(1)
|
161
|
-
|
162
|
-
|
162
|
+
case @old_trap
|
163
|
+
when 'DEFAULT', 'SYSTEM_DEFAULT'
|
164
|
+
raise Interrupt
|
165
|
+
when 'IGNORE'
|
166
|
+
# Do nothing
|
167
|
+
when 'EXIT'
|
168
|
+
exit
|
169
|
+
else
|
170
|
+
@old_trap.call
|
171
|
+
end
|
163
172
|
}
|
164
173
|
Reline::IOGate.set_winch_handler do
|
165
174
|
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
|
@@ -241,6 +250,7 @@ class Reline::LineEditor
|
|
241
250
|
@drop_terminate_spaces = false
|
242
251
|
@in_pasting = false
|
243
252
|
@auto_indent_proc = nil
|
253
|
+
@dialogs = []
|
244
254
|
reset_line
|
245
255
|
end
|
246
256
|
|
@@ -406,6 +416,7 @@ class Reline::LineEditor
|
|
406
416
|
Reline::IOGate.erase_after_cursor
|
407
417
|
end
|
408
418
|
@output.flush
|
419
|
+
clear_dialog
|
409
420
|
return
|
410
421
|
end
|
411
422
|
new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
|
@@ -416,6 +427,7 @@ class Reline::LineEditor
|
|
416
427
|
else
|
417
428
|
if @just_cursor_moving and not @rerender_all
|
418
429
|
rendered = just_move_cursor
|
430
|
+
render_dialog((prompt_width + @cursor) % @screen_size.last)
|
419
431
|
@just_cursor_moving = false
|
420
432
|
return
|
421
433
|
elsif @previous_line_index or new_highest_in_this != @highest_in_this
|
@@ -438,18 +450,20 @@ class Reline::LineEditor
|
|
438
450
|
new_lines = whole_lines
|
439
451
|
end
|
440
452
|
line = modify_lines(new_lines)[@line_index]
|
453
|
+
clear_dialog
|
441
454
|
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
|
442
455
|
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
443
456
|
move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
|
444
457
|
scroll_down(1)
|
445
458
|
Reline::IOGate.move_cursor_column(0)
|
446
459
|
Reline::IOGate.erase_after_cursor
|
447
|
-
|
448
|
-
|
460
|
+
else
|
461
|
+
if not rendered and not @in_pasting
|
449
462
|
line = modify_lines(whole_lines)[@line_index]
|
450
463
|
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines, prompt)
|
451
464
|
render_partial(prompt, prompt_width, line, @first_line_started_from)
|
452
465
|
end
|
466
|
+
render_dialog((prompt_width + @cursor) % @screen_size.last)
|
453
467
|
end
|
454
468
|
@buffer_of_lines[@line_index] = @line
|
455
469
|
@rest_height = 0 if @scroll_partial_screen
|
@@ -464,6 +478,294 @@ class Reline::LineEditor
|
|
464
478
|
end
|
465
479
|
end
|
466
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_cursor_pos(col, row)
|
503
|
+
@cursor_pos.x = col
|
504
|
+
@cursor_pos.y = row
|
505
|
+
end
|
506
|
+
|
507
|
+
def cursor_pos
|
508
|
+
@cursor_pos
|
509
|
+
end
|
510
|
+
|
511
|
+
def just_cursor_moving
|
512
|
+
@line_editor.instance_variable_get(:@just_cursor_moving)
|
513
|
+
end
|
514
|
+
|
515
|
+
def screen_width
|
516
|
+
@line_editor.instance_variable_get(:@screen_size).last
|
517
|
+
end
|
518
|
+
|
519
|
+
def completion_journey_data
|
520
|
+
@line_editor.instance_variable_get(:@completion_journey_data)
|
521
|
+
end
|
522
|
+
|
523
|
+
def config
|
524
|
+
@config
|
525
|
+
end
|
526
|
+
|
527
|
+
def call
|
528
|
+
instance_exec(&@proc_to_exec)
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
class Dialog
|
533
|
+
attr_reader :name
|
534
|
+
attr_accessor :column, :vertical_offset, :contents, :lines_backup
|
535
|
+
|
536
|
+
def initialize(name, proc_scope)
|
537
|
+
@name = name
|
538
|
+
@proc_scope = proc_scope
|
539
|
+
end
|
540
|
+
|
541
|
+
def set_cursor_pos(col, row)
|
542
|
+
@proc_scope.set_cursor_pos(col, row)
|
543
|
+
end
|
544
|
+
|
545
|
+
def call
|
546
|
+
@proc_scope.call
|
547
|
+
end
|
548
|
+
end
|
549
|
+
|
550
|
+
def add_dialog_proc(name, p, context = nil)
|
551
|
+
return if @dialogs.any? { |d| d.name == name }
|
552
|
+
@dialogs << Dialog.new(name, DialogProcScope.new(self, @config, p, context))
|
553
|
+
end
|
554
|
+
|
555
|
+
DIALOG_HEIGHT = 20
|
556
|
+
DIALOG_WIDTH = 40
|
557
|
+
private def render_dialog(cursor_column)
|
558
|
+
@dialogs.each do |dialog|
|
559
|
+
render_each_dialog(dialog, cursor_column)
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
private def render_each_dialog(dialog, cursor_column)
|
564
|
+
if @in_pasting
|
565
|
+
dialog.contents = nil
|
566
|
+
return
|
567
|
+
end
|
568
|
+
dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
|
569
|
+
pos, result, pointer, bg = dialog.call
|
570
|
+
old_dialog_contents = dialog.contents
|
571
|
+
old_dialog_column = dialog.column
|
572
|
+
old_dialog_vertical_offset = dialog.vertical_offset
|
573
|
+
if result and not result.empty?
|
574
|
+
dialog.contents = result
|
575
|
+
dialog.contents = dialog.contents[0...DIALOG_HEIGHT] if dialog.contents.size > DIALOG_HEIGHT
|
576
|
+
else
|
577
|
+
dialog.lines_backup = {
|
578
|
+
lines: modify_lines(whole_lines),
|
579
|
+
line_index: @line_index,
|
580
|
+
first_line_started_from: @first_line_started_from,
|
581
|
+
started_from: @started_from,
|
582
|
+
byte_pointer: @byte_pointer
|
583
|
+
}
|
584
|
+
clear_each_dialog(dialog)
|
585
|
+
dialog.contents = nil
|
586
|
+
return
|
587
|
+
end
|
588
|
+
upper_space = @first_line_started_from - @started_from
|
589
|
+
lower_space = @highest_in_all - @first_line_started_from - @started_from - 1
|
590
|
+
dialog.column = pos.x
|
591
|
+
diff = (dialog.column + DIALOG_WIDTH) - (@screen_size.last - 1)
|
592
|
+
if diff > 0
|
593
|
+
dialog.column -= diff
|
594
|
+
end
|
595
|
+
if (lower_space + @rest_height) >= DIALOG_HEIGHT
|
596
|
+
dialog.vertical_offset = pos.y + 1
|
597
|
+
elsif upper_space >= DIALOG_HEIGHT
|
598
|
+
dialog.vertical_offset = pos.y + -(DIALOG_HEIGHT + 1)
|
599
|
+
else
|
600
|
+
if (lower_space + @rest_height) < DIALOG_HEIGHT
|
601
|
+
scroll_down(DIALOG_HEIGHT)
|
602
|
+
move_cursor_up(DIALOG_HEIGHT)
|
603
|
+
end
|
604
|
+
dialog.vertical_offset = pos.y + 1
|
605
|
+
end
|
606
|
+
Reline::IOGate.hide_cursor
|
607
|
+
reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
|
608
|
+
move_cursor_down(dialog.vertical_offset)
|
609
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
610
|
+
dialog.contents.each_with_index do |item, i|
|
611
|
+
if i == pointer
|
612
|
+
bg_color = '45'
|
613
|
+
else
|
614
|
+
if bg
|
615
|
+
bg_color = bg
|
616
|
+
else
|
617
|
+
bg_color = '46'
|
618
|
+
end
|
619
|
+
end
|
620
|
+
@output.write "\e[#{bg_color}m%-#{DIALOG_WIDTH}s\e[49m" % item.slice(0, DIALOG_WIDTH)
|
621
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
622
|
+
move_cursor_down(1) if i < (dialog.contents.size - 1)
|
623
|
+
end
|
624
|
+
Reline::IOGate.move_cursor_column(cursor_column)
|
625
|
+
move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
|
626
|
+
Reline::IOGate.show_cursor
|
627
|
+
dialog.lines_backup = {
|
628
|
+
lines: modify_lines(whole_lines),
|
629
|
+
line_index: @line_index,
|
630
|
+
first_line_started_from: @first_line_started_from,
|
631
|
+
started_from: @started_from,
|
632
|
+
byte_pointer: @byte_pointer
|
633
|
+
}
|
634
|
+
end
|
635
|
+
|
636
|
+
private def reset_dialog(dialog, old_dialog_contents, old_dialog_column, old_dialog_vertical_offset)
|
637
|
+
return if dialog.lines_backup.nil? or old_dialog_contents.nil?
|
638
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
|
639
|
+
visual_lines = []
|
640
|
+
visual_start = nil
|
641
|
+
dialog.lines_backup[:lines].each_with_index { |l, i|
|
642
|
+
pr = prompt_list ? prompt_list[i] : prompt
|
643
|
+
vl, _ = split_by_width(pr + l, @screen_size.last)
|
644
|
+
vl.compact!
|
645
|
+
if i == dialog.lines_backup[:line_index]
|
646
|
+
visual_start = visual_lines.size + dialog.lines_backup[:started_from]
|
647
|
+
end
|
648
|
+
visual_lines.concat(vl)
|
649
|
+
}
|
650
|
+
old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
|
651
|
+
y = @first_line_started_from + @started_from
|
652
|
+
y_diff = y - old_y
|
653
|
+
if (old_y + old_dialog_vertical_offset) < (y + dialog.vertical_offset)
|
654
|
+
# rerender top
|
655
|
+
move_cursor_down(old_dialog_vertical_offset - y_diff)
|
656
|
+
start = visual_start + old_dialog_vertical_offset
|
657
|
+
line_num = dialog.vertical_offset - old_dialog_vertical_offset
|
658
|
+
line_num.times do |i|
|
659
|
+
Reline::IOGate.move_cursor_column(old_dialog_column)
|
660
|
+
if visual_lines[start + i].nil?
|
661
|
+
s = ' ' * DIALOG_WIDTH
|
662
|
+
else
|
663
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
|
664
|
+
end
|
665
|
+
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
|
666
|
+
move_cursor_down(1) if i < (line_num - 1)
|
667
|
+
end
|
668
|
+
move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
|
669
|
+
end
|
670
|
+
if (old_y + old_dialog_vertical_offset + old_dialog_contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
|
671
|
+
# rerender bottom
|
672
|
+
move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
|
673
|
+
start = visual_start + dialog.vertical_offset + dialog.contents.size
|
674
|
+
line_num = (old_dialog_vertical_offset + old_dialog_contents.size) - (dialog.vertical_offset + dialog.contents.size)
|
675
|
+
line_num.times do |i|
|
676
|
+
Reline::IOGate.move_cursor_column(old_dialog_column)
|
677
|
+
if visual_lines[start + i].nil?
|
678
|
+
s = ' ' * DIALOG_WIDTH
|
679
|
+
else
|
680
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, DIALOG_WIDTH)
|
681
|
+
end
|
682
|
+
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % s
|
683
|
+
move_cursor_down(1) if i < (line_num - 1)
|
684
|
+
end
|
685
|
+
move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
|
686
|
+
end
|
687
|
+
if old_dialog_column < dialog.column
|
688
|
+
# rerender left
|
689
|
+
move_cursor_down(old_dialog_vertical_offset - y_diff)
|
690
|
+
width = dialog.column - old_dialog_column
|
691
|
+
start = visual_start + old_dialog_vertical_offset
|
692
|
+
line_num = old_dialog_contents.size
|
693
|
+
line_num.times do |i|
|
694
|
+
Reline::IOGate.move_cursor_column(old_dialog_column)
|
695
|
+
if visual_lines[start + i].nil?
|
696
|
+
s = ' ' * width
|
697
|
+
else
|
698
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column, width)
|
699
|
+
end
|
700
|
+
@output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
|
701
|
+
move_cursor_down(1) if i < (line_num - 1)
|
702
|
+
end
|
703
|
+
move_cursor_up(old_dialog_vertical_offset + line_num - 1 - y_diff)
|
704
|
+
end
|
705
|
+
if (old_dialog_column + DIALOG_WIDTH) > (dialog.column + DIALOG_WIDTH)
|
706
|
+
# rerender right
|
707
|
+
move_cursor_down(old_dialog_vertical_offset + y_diff)
|
708
|
+
width = (old_dialog_column + DIALOG_WIDTH) - (dialog.column + DIALOG_WIDTH)
|
709
|
+
start = visual_start + old_dialog_vertical_offset
|
710
|
+
line_num = old_dialog_contents.size
|
711
|
+
line_num.times do |i|
|
712
|
+
Reline::IOGate.move_cursor_column(old_dialog_column + DIALOG_WIDTH)
|
713
|
+
if visual_lines[start + i].nil?
|
714
|
+
s = ' ' * width
|
715
|
+
else
|
716
|
+
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog_column + DIALOG_WIDTH, width)
|
717
|
+
end
|
718
|
+
Reline::IOGate.move_cursor_column(dialog.column + DIALOG_WIDTH)
|
719
|
+
@output.write "\e[39m\e[49m%-#{width}s\e[39m\e[49m" % s
|
720
|
+
move_cursor_down(1) if i < (line_num - 1)
|
721
|
+
end
|
722
|
+
move_cursor_up(old_dialog_vertical_offset + line_num - 1 + y_diff)
|
723
|
+
end
|
724
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
725
|
+
end
|
726
|
+
|
727
|
+
private def clear_dialog
|
728
|
+
@dialogs.each do |dialog|
|
729
|
+
clear_each_dialog(dialog)
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
private def clear_each_dialog(dialog)
|
734
|
+
return unless dialog.contents
|
735
|
+
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines], prompt)
|
736
|
+
visual_lines = []
|
737
|
+
visual_lines_under_dialog = []
|
738
|
+
visual_start = nil
|
739
|
+
dialog.lines_backup[:lines].each_with_index { |l, i|
|
740
|
+
pr = prompt_list ? prompt_list[i] : prompt
|
741
|
+
vl, _ = split_by_width(pr + l, @screen_size.last)
|
742
|
+
vl.compact!
|
743
|
+
if i == dialog.lines_backup[:line_index]
|
744
|
+
visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
|
745
|
+
end
|
746
|
+
visual_lines.concat(vl)
|
747
|
+
}
|
748
|
+
visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
|
749
|
+
visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
|
750
|
+
Reline::IOGate.hide_cursor
|
751
|
+
move_cursor_down(dialog.vertical_offset)
|
752
|
+
dialog_vertical_size = dialog.contents.size
|
753
|
+
dialog_vertical_size.times do |i|
|
754
|
+
if i < visual_lines_under_dialog.size
|
755
|
+
Reline::IOGate.move_cursor_column(0)
|
756
|
+
@output.write "\e[39m\e[49m%-#{DIALOG_WIDTH}s\e[39m\e[49m" % visual_lines_under_dialog[i]
|
757
|
+
else
|
758
|
+
Reline::IOGate.move_cursor_column(dialog.column)
|
759
|
+
@output.write "\e[39m\e[49m#{' ' * DIALOG_WIDTH}\e[39m\e[49m"
|
760
|
+
end
|
761
|
+
Reline::IOGate.erase_after_cursor
|
762
|
+
move_cursor_down(1) if i < (dialog_vertical_size - 1)
|
763
|
+
end
|
764
|
+
move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
|
765
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
766
|
+
Reline::IOGate.show_cursor
|
767
|
+
end
|
768
|
+
|
467
769
|
private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
|
468
770
|
if @screen_height < highest_in_all
|
469
771
|
old_scroll_partial_screen = @scroll_partial_screen
|
@@ -924,6 +1226,16 @@ class Reline::LineEditor
|
|
924
1226
|
@completion_journey_data = CompletionJourneyData.new(
|
925
1227
|
preposing, postposing,
|
926
1228
|
[target] + list.select{ |item| item.start_with?(target) }, 0)
|
1229
|
+
if @completion_journey_data.list.size == 1
|
1230
|
+
@completion_journey_data.pointer = 0
|
1231
|
+
else
|
1232
|
+
case direction
|
1233
|
+
when :up
|
1234
|
+
@completion_journey_data.pointer = @completion_journey_data.list.size - 1
|
1235
|
+
when :down
|
1236
|
+
@completion_journey_data.pointer = 1
|
1237
|
+
end
|
1238
|
+
end
|
927
1239
|
@completion_state = CompletionState::JOURNEY
|
928
1240
|
else
|
929
1241
|
case direction
|
@@ -938,13 +1250,13 @@ class Reline::LineEditor
|
|
938
1250
|
@completion_journey_data.pointer = 0
|
939
1251
|
end
|
940
1252
|
end
|
941
|
-
completed = @completion_journey_data.list[@completion_journey_data.pointer]
|
942
|
-
@line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
|
943
|
-
line_to_pointer = @completion_journey_data.preposing + completed
|
944
|
-
@cursor_max = calculate_width(@line)
|
945
|
-
@cursor = calculate_width(line_to_pointer)
|
946
|
-
@byte_pointer = line_to_pointer.bytesize
|
947
1253
|
end
|
1254
|
+
completed = @completion_journey_data.list[@completion_journey_data.pointer]
|
1255
|
+
@line = @completion_journey_data.preposing + completed + @completion_journey_data.postposing
|
1256
|
+
line_to_pointer = @completion_journey_data.preposing + completed
|
1257
|
+
@cursor_max = calculate_width(@line)
|
1258
|
+
@cursor = calculate_width(line_to_pointer)
|
1259
|
+
@byte_pointer = line_to_pointer.bytesize
|
948
1260
|
end
|
949
1261
|
|
950
1262
|
private def run_for_operators(key, method_symbol, &block)
|
@@ -1119,7 +1431,20 @@ class Reline::LineEditor
|
|
1119
1431
|
if result.is_a?(Array)
|
1120
1432
|
completion_occurs = true
|
1121
1433
|
process_insert
|
1122
|
-
|
1434
|
+
if @config.autocompletion
|
1435
|
+
move_completed_list(result, :down)
|
1436
|
+
else
|
1437
|
+
complete(result)
|
1438
|
+
end
|
1439
|
+
end
|
1440
|
+
end
|
1441
|
+
elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up
|
1442
|
+
if not @config.disable_completion and @config.autocompletion
|
1443
|
+
result = call_completion_proc
|
1444
|
+
if result.is_a?(Array)
|
1445
|
+
completion_occurs = true
|
1446
|
+
process_insert
|
1447
|
+
move_completed_list(result, :up)
|
1123
1448
|
end
|
1124
1449
|
end
|
1125
1450
|
elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
|
@@ -1138,6 +1463,7 @@ class Reline::LineEditor
|
|
1138
1463
|
end
|
1139
1464
|
unless completion_occurs
|
1140
1465
|
@completion_state = CompletionState::NORMAL
|
1466
|
+
@completion_journey_data = nil
|
1141
1467
|
end
|
1142
1468
|
if not @in_pasting and @just_cursor_moving.nil?
|
1143
1469
|
if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
|
@@ -1157,7 +1483,13 @@ class Reline::LineEditor
|
|
1157
1483
|
|
1158
1484
|
def call_completion_proc
|
1159
1485
|
result = retrieve_completion_block(true)
|
1160
|
-
|
1486
|
+
pre, target, post = result
|
1487
|
+
result = call_completion_proc_with_checking_args(pre, target, post)
|
1488
|
+
Reline.core.instance_variable_set(:@completion_quote_character, nil)
|
1489
|
+
result
|
1490
|
+
end
|
1491
|
+
|
1492
|
+
def call_completion_proc_with_checking_args(pre, target, post)
|
1161
1493
|
if @completion_proc and target
|
1162
1494
|
argnum = @completion_proc.parameters.inject(0) { |result, item|
|
1163
1495
|
case item.first
|
@@ -1171,12 +1503,11 @@ class Reline::LineEditor
|
|
1171
1503
|
when 1
|
1172
1504
|
result = @completion_proc.(target)
|
1173
1505
|
when 2
|
1174
|
-
result = @completion_proc.(target,
|
1506
|
+
result = @completion_proc.(target, pre)
|
1175
1507
|
when 3..Float::INFINITY
|
1176
|
-
result = @completion_proc.(target,
|
1508
|
+
result = @completion_proc.(target, pre, post)
|
1177
1509
|
end
|
1178
1510
|
end
|
1179
|
-
Reline.core.instance_variable_set(:@completion_quote_character, nil)
|
1180
1511
|
result
|
1181
1512
|
end
|
1182
1513
|
|
data/lib/reline/terminfo.rb
CHANGED
@@ -6,12 +6,39 @@ module Reline::Terminfo
|
|
6
6
|
|
7
7
|
class TerminfoError < StandardError; end
|
8
8
|
|
9
|
-
|
9
|
+
def self.curses_dl_files
|
10
|
+
case RUBY_PLATFORM
|
11
|
+
when /mingw/, /mswin/
|
12
|
+
# aren't supported
|
13
|
+
[]
|
14
|
+
when /cygwin/
|
15
|
+
%w[cygncursesw-10.dll cygncurses-10.dll]
|
16
|
+
when /darwin/
|
17
|
+
%w[libncursesw.dylib libcursesw.dylib libncurses.dylib libcurses.dylib]
|
18
|
+
else
|
19
|
+
%w[libncursesw.so libcursesw.so libncurses.so libcurses.so]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
@curses_dl = false
|
10
24
|
def self.curses_dl
|
11
|
-
return @curses_dl
|
12
|
-
if
|
25
|
+
return @curses_dl unless @curses_dl == false
|
26
|
+
if RUBY_VERSION >= '3.0.0'
|
27
|
+
# Gem module isn't defined in test-all of the Ruby repository, and
|
28
|
+
# Fiddle in Ruby 3.0.0 or later supports Fiddle::TYPE_VARIADIC.
|
29
|
+
fiddle_supports_variadic = true
|
30
|
+
elsif Fiddle.const_defined?(:VERSION) and Gem::Version.create(Fiddle::VERSION) >= Gem::Version.create('1.0.1')
|
13
31
|
# Fiddle::TYPE_VARIADIC is supported from Fiddle 1.0.1.
|
14
|
-
|
32
|
+
fiddle_supports_variadic = true
|
33
|
+
else
|
34
|
+
fiddle_supports_variadic = false
|
35
|
+
end
|
36
|
+
if fiddle_supports_variadic and not Fiddle.const_defined?(:TYPE_VARIADIC)
|
37
|
+
# If the libffi version is not 3.0.5 or higher, there isn't TYPE_VARIADIC.
|
38
|
+
fiddle_supports_variadic = false
|
39
|
+
end
|
40
|
+
if fiddle_supports_variadic
|
41
|
+
curses_dl_files.each do |curses_name|
|
15
42
|
result = Fiddle::Handle.new(curses_name)
|
16
43
|
rescue Fiddle::DLError
|
17
44
|
next
|
@@ -20,6 +47,7 @@ module Reline::Terminfo
|
|
20
47
|
break
|
21
48
|
end
|
22
49
|
end
|
50
|
+
@curses_dl = nil if @curses_dl == false
|
23
51
|
@curses_dl
|
24
52
|
end
|
25
53
|
end
|
@@ -30,8 +58,15 @@ module Reline::Terminfo
|
|
30
58
|
@setupterm = Fiddle::Function.new(curses_dl['setupterm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
|
31
59
|
#extern 'char *tigetstr(char *capname)'
|
32
60
|
@tigetstr = Fiddle::Function.new(curses_dl['tigetstr'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOIDP)
|
33
|
-
|
34
|
-
|
61
|
+
begin
|
62
|
+
#extern 'char *tiparm(const char *str, ...)'
|
63
|
+
@tiparm = Fiddle::Function.new(curses_dl['tiparm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VARIADIC], Fiddle::TYPE_VOIDP)
|
64
|
+
rescue Fiddle::DLError
|
65
|
+
# OpenBSD lacks tiparm
|
66
|
+
#extern 'char *tparm(const char *str, ...)'
|
67
|
+
@tiparm = Fiddle::Function.new(curses_dl['tparm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VARIADIC], Fiddle::TYPE_VOIDP)
|
68
|
+
end
|
69
|
+
# TODO: add int tigetflag(char *capname) and int tigetnum(char *capname)
|
35
70
|
|
36
71
|
def self.setupterm(term, fildes)
|
37
72
|
errret_int = String.new("\x00" * 8, encoding: 'ASCII-8BIT')
|
@@ -56,12 +91,19 @@ module Reline::Terminfo
|
|
56
91
|
end
|
57
92
|
end
|
58
93
|
|
59
|
-
|
60
|
-
|
61
|
-
def result.tiparm(*args) # for method chain
|
94
|
+
class StringWithTiparm < String
|
95
|
+
def tiparm(*args) # for method chain
|
62
96
|
Reline::Terminfo.tiparm(self, *args)
|
63
97
|
end
|
64
|
-
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.tigetstr(capname)
|
101
|
+
capability = @tigetstr.(capname)
|
102
|
+
case capability.to_i
|
103
|
+
when 0, -1
|
104
|
+
raise TerminfoError, "can't find capability: #{capname}"
|
105
|
+
end
|
106
|
+
StringWithTiparm.new(capability.to_s)
|
65
107
|
end
|
66
108
|
|
67
109
|
def self.tiparm(str, *args)
|
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
|
@@ -91,6 +99,7 @@ class Reline::Windows
|
|
91
99
|
VK_LMENU = 0xA4
|
92
100
|
VK_CONTROL = 0x11
|
93
101
|
VK_SHIFT = 0x10
|
102
|
+
VK_DIVIDE = 0x6F
|
94
103
|
|
95
104
|
KEY_EVENT = 0x01
|
96
105
|
WINDOW_BUFFER_SIZE_EVENT = 0x04
|
@@ -105,6 +114,7 @@ class Reline::Windows
|
|
105
114
|
SCROLLLOCK_ON = 0x0040
|
106
115
|
SHIFT_PRESSED = 0x0010
|
107
116
|
|
117
|
+
VK_TAB = 0x09
|
108
118
|
VK_END = 0x23
|
109
119
|
VK_HOME = 0x24
|
110
120
|
VK_LEFT = 0x25
|
@@ -132,9 +142,11 @@ class Reline::Windows
|
|
132
142
|
@@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
|
133
143
|
@@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
|
134
144
|
@@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
|
145
|
+
@@SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L')
|
135
146
|
|
136
147
|
@@GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L')
|
137
148
|
@@SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
|
149
|
+
@@WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L')
|
138
150
|
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
|
139
151
|
|
140
152
|
private_class_method def self.getconsolemode
|
@@ -180,51 +192,47 @@ class Reline::Windows
|
|
180
192
|
name =~ /(msys-|cygwin-).*-pty/ ? true : false
|
181
193
|
end
|
182
194
|
|
195
|
+
KEY_MAP = [
|
196
|
+
# It's treated as Meta+Enter on Windows.
|
197
|
+
[ { control_keys: :CTRL, virtual_key_code: 0x0D }, "\e\r".bytes ],
|
198
|
+
[ { control_keys: :SHIFT, virtual_key_code: 0x0D }, "\e\r".bytes ],
|
199
|
+
|
200
|
+
# It's treated as Meta+Space on Windows.
|
201
|
+
[ { control_keys: :CTRL, char_code: 0x20 }, "\e ".bytes ],
|
202
|
+
|
203
|
+
# Emulate getwch() key sequences.
|
204
|
+
[ { control_keys: [], virtual_key_code: VK_UP }, [0, 72] ],
|
205
|
+
[ { control_keys: [], virtual_key_code: VK_DOWN }, [0, 80] ],
|
206
|
+
[ { control_keys: [], virtual_key_code: VK_RIGHT }, [0, 77] ],
|
207
|
+
[ { control_keys: [], virtual_key_code: VK_LEFT }, [0, 75] ],
|
208
|
+
[ { control_keys: [], virtual_key_code: VK_DELETE }, [0, 83] ],
|
209
|
+
[ { control_keys: [], virtual_key_code: VK_HOME }, [0, 71] ],
|
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] ],
|
214
|
+
]
|
215
|
+
|
183
216
|
def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
@@output_buf.push("\e".ord)
|
192
|
-
@@output_buf.push(char_code)
|
193
|
-
elsif control_key_state.anybits?(LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED)
|
194
|
-
@@output_buf.push("\e".ord)
|
195
|
-
@@output_buf.concat(char.bytes)
|
196
|
-
elsif control_key_state.anybits?(ENHANCED_KEY)
|
197
|
-
case virtual_key_code # Emulate getwch() key sequences.
|
198
|
-
when VK_END
|
199
|
-
@@output_buf.push(0, 79)
|
200
|
-
when VK_HOME
|
201
|
-
@@output_buf.push(0, 71)
|
202
|
-
when VK_LEFT
|
203
|
-
@@output_buf.push(0, 75)
|
204
|
-
when VK_UP
|
205
|
-
@@output_buf.push(0, 72)
|
206
|
-
when VK_RIGHT
|
207
|
-
@@output_buf.push(0, 77)
|
208
|
-
when VK_DOWN
|
209
|
-
@@output_buf.push(0, 80)
|
210
|
-
when VK_DELETE
|
211
|
-
@@output_buf.push(0, 83)
|
212
|
-
end
|
213
|
-
elsif char_code == 0 and control_key_state != 0
|
214
|
-
# unknown
|
215
|
-
else
|
216
|
-
case virtual_key_code
|
217
|
-
when VK_RETURN
|
218
|
-
@@output_buf.push("\n".ord)
|
219
|
-
else
|
220
|
-
@@output_buf.concat(char.bytes)
|
221
|
-
end
|
217
|
+
|
218
|
+
key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state)
|
219
|
+
|
220
|
+
match = KEY_MAP.find { |args,| key.matches?(**args) }
|
221
|
+
unless match.nil?
|
222
|
+
@@output_buf.concat(match.last)
|
223
|
+
return
|
222
224
|
end
|
225
|
+
|
226
|
+
# no char, only control keys
|
227
|
+
return if key.char_code == 0 and key.control_keys.any?
|
228
|
+
|
229
|
+
@@output_buf.concat(key.char.bytes)
|
223
230
|
end
|
224
231
|
|
225
232
|
def self.check_input_event
|
226
233
|
num_of_events = 0.chr * 8
|
227
234
|
while @@output_buf.empty? #or true
|
235
|
+
next if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
|
228
236
|
next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack('L').first == 0
|
229
237
|
input_record = 0.chr * 18
|
230
238
|
read_event = 0.chr * 4
|
@@ -348,6 +356,20 @@ class Reline::Windows
|
|
348
356
|
raise NotImplementedError
|
349
357
|
end
|
350
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
|
+
|
351
373
|
def self.set_winch_handler(&handler)
|
352
374
|
@@winch_handler = handler
|
353
375
|
end
|
@@ -360,4 +382,43 @@ class Reline::Windows
|
|
360
382
|
def self.deprep(otio)
|
361
383
|
# do nothing
|
362
384
|
end
|
385
|
+
|
386
|
+
class KeyEventRecord
|
387
|
+
|
388
|
+
attr_reader :virtual_key_code, :char_code, :control_key_state, :control_keys
|
389
|
+
|
390
|
+
def initialize(virtual_key_code, char_code, control_key_state)
|
391
|
+
@virtual_key_code = virtual_key_code
|
392
|
+
@char_code = char_code
|
393
|
+
@control_key_state = control_key_state
|
394
|
+
@enhanced = control_key_state & ENHANCED_KEY != 0
|
395
|
+
|
396
|
+
(@control_keys = []).tap do |control_keys|
|
397
|
+
# symbols must be sorted to make comparison is easier later on
|
398
|
+
control_keys << :ALT if control_key_state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) != 0
|
399
|
+
control_keys << :CTRL if control_key_state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0
|
400
|
+
control_keys << :SHIFT if control_key_state & SHIFT_PRESSED != 0
|
401
|
+
end.freeze
|
402
|
+
end
|
403
|
+
|
404
|
+
def char
|
405
|
+
@char_code.chr(Encoding::UTF_8)
|
406
|
+
end
|
407
|
+
|
408
|
+
def enhanced?
|
409
|
+
@enhanced
|
410
|
+
end
|
411
|
+
|
412
|
+
# Verifies if the arguments match with this key event.
|
413
|
+
# Nil arguments are ignored, but at least one must be passed as non-nil.
|
414
|
+
# To verify that no control keys were pressed, pass an empty array: `control_keys: []`.
|
415
|
+
def matches?(control_keys: nil, virtual_key_code: nil, char_code: nil)
|
416
|
+
raise ArgumentError, 'No argument was passed to match key event' if control_keys.nil? && virtual_key_code.nil? && char_code.nil?
|
417
|
+
|
418
|
+
(control_keys.nil? || [*control_keys].sort == @control_keys) &&
|
419
|
+
(virtual_key_code.nil? || @virtual_key_code == virtual_key_code) &&
|
420
|
+
(char_code.nil? || char_code == @char_code)
|
421
|
+
end
|
422
|
+
|
423
|
+
end
|
363
424
|
end
|
data/lib/reline.rb
CHANGED
@@ -7,12 +7,15 @@ require 'reline/key_actor'
|
|
7
7
|
require 'reline/key_stroke'
|
8
8
|
require 'reline/line_editor'
|
9
9
|
require 'reline/history'
|
10
|
+
require 'reline/terminfo'
|
10
11
|
require 'rbconfig'
|
11
12
|
|
12
13
|
module Reline
|
13
14
|
FILENAME_COMPLETION_PROC = nil
|
14
15
|
USERNAME_COMPLETION_PROC = nil
|
15
16
|
|
17
|
+
class ConfigEncodingConversionError < StandardError; end
|
18
|
+
|
16
19
|
Key = Struct.new('Key', :char, :combined_char, :with_meta)
|
17
20
|
CursorPos = Struct.new(:x, :y)
|
18
21
|
|
@@ -41,6 +44,7 @@ module Reline
|
|
41
44
|
|
42
45
|
def initialize
|
43
46
|
self.output = STDOUT
|
47
|
+
@dialog_proc_list = []
|
44
48
|
yield self
|
45
49
|
@completion_quote_character = nil
|
46
50
|
@bracketed_paste_finished = false
|
@@ -103,6 +107,14 @@ module Reline
|
|
103
107
|
@completion_proc = p
|
104
108
|
end
|
105
109
|
|
110
|
+
def autocompletion
|
111
|
+
@config.autocompletion
|
112
|
+
end
|
113
|
+
|
114
|
+
def autocompletion=(val)
|
115
|
+
@config.autocompletion = val
|
116
|
+
end
|
117
|
+
|
106
118
|
def output_modifier_proc=(p)
|
107
119
|
raise ArgumentError unless p.respond_to?(:call) or p.nil?
|
108
120
|
@output_modifier_proc = p
|
@@ -127,6 +139,12 @@ module Reline
|
|
127
139
|
@dig_perfect_match_proc = p
|
128
140
|
end
|
129
141
|
|
142
|
+
def add_dialog_proc(name_sym, p, context = nil)
|
143
|
+
raise ArgumentError unless p.respond_to?(:call) or p.nil?
|
144
|
+
raise ArgumentError unless name_sym.instance_of?(Symbol)
|
145
|
+
@dialog_proc_list << [name_sym, p, context]
|
146
|
+
end
|
147
|
+
|
130
148
|
def input=(val)
|
131
149
|
raise TypeError unless val.respond_to?(:getc) or val.nil?
|
132
150
|
if val.respond_to?(:getc)
|
@@ -168,6 +186,45 @@ module Reline
|
|
168
186
|
Reline::IOGate.get_screen_size
|
169
187
|
end
|
170
188
|
|
189
|
+
Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE = ->() {
|
190
|
+
# autocomplete
|
191
|
+
return nil unless config.autocompletion
|
192
|
+
if just_cursor_moving and completion_journey_data.nil?
|
193
|
+
# Auto complete starts only when edited
|
194
|
+
return nil
|
195
|
+
end
|
196
|
+
pre, target, post= retrieve_completion_block(true)
|
197
|
+
if target.nil? or target.empty?# or target.size <= 3
|
198
|
+
return nil
|
199
|
+
end
|
200
|
+
if completion_journey_data and completion_journey_data.list
|
201
|
+
result = completion_journey_data.list.dup
|
202
|
+
result.shift
|
203
|
+
pointer = completion_journey_data.pointer - 1
|
204
|
+
else
|
205
|
+
result = call_completion_proc_with_checking_args(pre, target, post)
|
206
|
+
pointer = nil
|
207
|
+
end
|
208
|
+
if result and result.size == 1 and result[0] == target
|
209
|
+
result = nil
|
210
|
+
end
|
211
|
+
target_width = Reline::Unicode.calculate_width(target)
|
212
|
+
x = cursor_pos.x - target_width
|
213
|
+
if x < 0
|
214
|
+
x = screen_width + x
|
215
|
+
y = -1
|
216
|
+
else
|
217
|
+
y = 0
|
218
|
+
end
|
219
|
+
cursor_pos_to_render = Reline::CursorPos.new(x, y)
|
220
|
+
if context and context.is_a?(Array)
|
221
|
+
context.clear
|
222
|
+
context.push(cursor_pos_to_render, result, pointer)
|
223
|
+
end
|
224
|
+
[cursor_pos_to_render, result, pointer, nil]
|
225
|
+
}
|
226
|
+
Reline::DEFAULT_DIALOG_CONTEXT = Array.new
|
227
|
+
|
171
228
|
def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
|
172
229
|
unless confirm_multiline_termination
|
173
230
|
raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
|
@@ -227,6 +284,10 @@ module Reline
|
|
227
284
|
line_editor.auto_indent_proc = auto_indent_proc
|
228
285
|
line_editor.dig_perfect_match_proc = dig_perfect_match_proc
|
229
286
|
line_editor.pre_input_hook = pre_input_hook
|
287
|
+
@dialog_proc_list.each do |d|
|
288
|
+
name_sym, dialog_proc, context = d
|
289
|
+
line_editor.add_dialog_proc(name_sym, dialog_proc, context)
|
290
|
+
end
|
230
291
|
|
231
292
|
unless config.test_mode
|
232
293
|
config.read
|
@@ -421,6 +482,8 @@ module Reline
|
|
421
482
|
def_single_delegators :core, :ambiguous_width
|
422
483
|
def_single_delegators :core, :last_incremental_search
|
423
484
|
def_single_delegators :core, :last_incremental_search=
|
485
|
+
def_single_delegators :core, :add_dialog_proc
|
486
|
+
def_single_delegators :core, :autocompletion, :autocompletion=
|
424
487
|
|
425
488
|
def_single_delegators :core, :readmultiline
|
426
489
|
def_instance_delegators self, :readmultiline
|
@@ -442,6 +505,7 @@ module Reline
|
|
442
505
|
core.completer_quote_characters = '"\''
|
443
506
|
core.filename_quote_characters = ""
|
444
507
|
core.special_prefixes = ""
|
508
|
+
core.add_dialog_proc(:autocomplete, Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE, Reline::DEFAULT_DIALOG_CONTEXT)
|
445
509
|
}
|
446
510
|
end
|
447
511
|
|
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.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- aycabta
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
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,11 +69,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
69
69
|
version: '2.5'
|
70
70
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
71
|
requirements:
|
72
|
-
- - "
|
72
|
+
- - ">"
|
73
73
|
- !ruby/object:Gem::Version
|
74
|
-
version:
|
74
|
+
version: 1.3.1
|
75
75
|
requirements: []
|
76
|
-
rubygems_version: 3.2.
|
76
|
+
rubygems_version: 3.2.22
|
77
77
|
signing_key:
|
78
78
|
specification_version: 4
|
79
79
|
summary: Alternative GNU Readline or Editline implementation by pure Ruby.
|