reline 0.2.6 → 0.2.8.pre.3
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 +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.
|