reline 0.1.1 → 0.1.6
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/README.md +4 -0
- data/lib/reline.rb +47 -25
- data/lib/reline/ansi.rb +95 -18
- data/lib/reline/config.rb +69 -10
- data/lib/reline/general_io.rb +12 -0
- data/lib/reline/history.rb +33 -13
- data/lib/reline/key_actor/emacs.rb +6 -6
- data/lib/reline/key_actor/vi_command.rb +2 -2
- data/lib/reline/key_actor/vi_insert.rb +2 -2
- data/lib/reline/key_stroke.rb +2 -0
- data/lib/reline/line_editor.rb +296 -104
- data/lib/reline/sibori.rb +156 -0
- data/lib/reline/unicode.rb +68 -0
- data/lib/reline/unicode/east_asian_width.rb +1149 -1130
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +65 -5
- metadata +22 -7
data/lib/reline/general_io.rb
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
require 'timeout'
|
2
2
|
|
3
3
|
class Reline::GeneralIO
|
4
|
+
def self.encoding
|
5
|
+
RUBY_PLATFORM =~ /mswin|mingw/ ? Encoding::UTF_8 : Encoding::default_external
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.win?
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
4
12
|
RAW_KEYSTROKE_CONFIG = {}
|
5
13
|
|
6
14
|
@@buf = []
|
@@ -59,6 +67,10 @@ class Reline::GeneralIO
|
|
59
67
|
def self.set_winch_handler(&handler)
|
60
68
|
end
|
61
69
|
|
70
|
+
def self.in_pasting?
|
71
|
+
false
|
72
|
+
end
|
73
|
+
|
62
74
|
def self.prep
|
63
75
|
end
|
64
76
|
|
data/lib/reline/history.rb
CHANGED
@@ -19,7 +19,7 @@ class Reline::History < Array
|
|
19
19
|
|
20
20
|
def []=(index, val)
|
21
21
|
index = check_index(index)
|
22
|
-
super(index, String.new(val, encoding:
|
22
|
+
super(index, String.new(val, encoding: Reline.encoding_system_needs))
|
23
23
|
end
|
24
24
|
|
25
25
|
def concat(*val)
|
@@ -29,27 +29,47 @@ class Reline::History < Array
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def push(*val)
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
32
|
+
# If history_size is zero, all histories are dropped.
|
33
|
+
return self if @config.history_size.zero?
|
34
|
+
# If history_size is negative, history size is unlimited.
|
35
|
+
if @config.history_size.positive?
|
36
|
+
diff = size + val.size - @config.history_size
|
37
|
+
if diff > 0
|
38
|
+
if diff <= size
|
39
|
+
shift(diff)
|
40
|
+
else
|
41
|
+
diff -= size
|
42
|
+
clear
|
43
|
+
val.shift(diff)
|
44
|
+
end
|
40
45
|
end
|
41
46
|
end
|
42
|
-
super(*(val.map{ |v|
|
47
|
+
super(*(val.map{ |v|
|
48
|
+
String.new(v, encoding: Reline.encoding_system_needs)
|
49
|
+
}))
|
43
50
|
end
|
44
51
|
|
45
52
|
def <<(val)
|
46
|
-
|
47
|
-
|
53
|
+
# If history_size is zero, all histories are dropped.
|
54
|
+
return self if @config.history_size.zero?
|
55
|
+
# If history_size is negative, history size is unlimited.
|
56
|
+
if @config.history_size.positive?
|
57
|
+
shift if size + 1 > @config.history_size
|
58
|
+
end
|
59
|
+
super(String.new(val, encoding: Reline.encoding_system_needs))
|
48
60
|
end
|
49
61
|
|
50
62
|
private def check_index(index)
|
51
63
|
index += size if index < 0
|
52
|
-
|
64
|
+
if index < -2147483648 or 2147483647 < index
|
65
|
+
raise RangeError.new("integer #{index} too big to convert to `int'")
|
66
|
+
end
|
67
|
+
# If history_size is negative, history size is unlimited.
|
68
|
+
if @config.history_size.positive?
|
69
|
+
if index < -@config.history_size or @config.history_size < index
|
70
|
+
raise RangeError.new("index=<#{index}>")
|
71
|
+
end
|
72
|
+
end
|
53
73
|
raise IndexError.new("index=<#{index}>") if index < 0 or size <= index
|
54
74
|
index
|
55
75
|
end
|
@@ -37,9 +37,9 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
|
|
37
37
|
# 17 ^Q
|
38
38
|
:ed_quoted_insert,
|
39
39
|
# 18 ^R
|
40
|
-
:
|
40
|
+
:vi_search_prev,
|
41
41
|
# 19 ^S
|
42
|
-
:
|
42
|
+
:vi_search_next,
|
43
43
|
# 20 ^T
|
44
44
|
:ed_transpose_chars,
|
45
45
|
# 21 ^U
|
@@ -413,11 +413,11 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
|
|
413
413
|
# 205 M-M
|
414
414
|
:ed_unassigned,
|
415
415
|
# 206 M-N
|
416
|
-
:
|
416
|
+
:vi_search_next,
|
417
417
|
# 207 M-O
|
418
418
|
:ed_sequence_lead_in,
|
419
419
|
# 208 M-P
|
420
|
-
:
|
420
|
+
:vi_search_prev,
|
421
421
|
# 209 M-Q
|
422
422
|
:ed_unassigned,
|
423
423
|
# 210 M-R
|
@@ -477,11 +477,11 @@ class Reline::KeyActor::Emacs < Reline::KeyActor::Base
|
|
477
477
|
# 237 M-m
|
478
478
|
:ed_unassigned,
|
479
479
|
# 238 M-n
|
480
|
-
:
|
480
|
+
:vi_search_next,
|
481
481
|
# 239 M-o
|
482
482
|
:ed_unassigned,
|
483
483
|
# 240 M-p
|
484
|
-
:
|
484
|
+
:vi_search_prev,
|
485
485
|
# 241 M-q
|
486
486
|
:ed_unassigned,
|
487
487
|
# 242 M-r
|
@@ -37,7 +37,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
|
|
37
37
|
# 17 ^Q
|
38
38
|
:ed_ignore,
|
39
39
|
# 18 ^R
|
40
|
-
:
|
40
|
+
:vi_search_prev,
|
41
41
|
# 19 ^S
|
42
42
|
:ed_ignore,
|
43
43
|
# 20 ^T
|
@@ -151,7 +151,7 @@ class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
|
|
151
151
|
# 74 J
|
152
152
|
:vi_join_lines,
|
153
153
|
# 75 K
|
154
|
-
:
|
154
|
+
:vi_search_prev,
|
155
155
|
# 76 L
|
156
156
|
:ed_unassigned,
|
157
157
|
# 77 M
|
data/lib/reline/key_stroke.rb
CHANGED
data/lib/reline/line_editor.rb
CHANGED
@@ -2,7 +2,6 @@ require 'reline/kill_ring'
|
|
2
2
|
require 'reline/unicode'
|
3
3
|
|
4
4
|
require 'tempfile'
|
5
|
-
require 'pathname'
|
6
5
|
|
7
6
|
class Reline::LineEditor
|
8
7
|
# TODO: undo
|
@@ -51,16 +50,18 @@ class Reline::LineEditor
|
|
51
50
|
CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
|
52
51
|
MenuInfo = Struct.new('MenuInfo', :target, :list)
|
53
52
|
|
54
|
-
|
55
|
-
OSC_REGEXP = /\e\]\d+(?:;[^;]+)*\a/
|
56
|
-
NON_PRINTING_START = "\1"
|
57
|
-
NON_PRINTING_END = "\2"
|
58
|
-
WIDTH_SCANNER = /\G(?:#{NON_PRINTING_START}|#{NON_PRINTING_END}|#{CSI_REGEXP}|#{OSC_REGEXP}|\X)/
|
59
|
-
|
60
|
-
def initialize(config)
|
53
|
+
def initialize(config, encoding)
|
61
54
|
@config = config
|
62
55
|
@completion_append_character = ''
|
63
|
-
reset_variables
|
56
|
+
reset_variables(encoding: encoding)
|
57
|
+
end
|
58
|
+
|
59
|
+
def simplified_rendering?
|
60
|
+
if finished?
|
61
|
+
false
|
62
|
+
else
|
63
|
+
not @rerender_all and not finished? and Reline::IOGate.in_pasting?
|
64
|
+
end
|
64
65
|
end
|
65
66
|
|
66
67
|
private def check_multiline_prompt(buffer, prompt)
|
@@ -73,22 +74,47 @@ class Reline::LineEditor
|
|
73
74
|
else
|
74
75
|
prompt = @prompt
|
75
76
|
end
|
77
|
+
return [prompt, calculate_width(prompt, true), [prompt] * buffer.size] if simplified_rendering?
|
76
78
|
if @prompt_proc
|
77
79
|
prompt_list = @prompt_proc.(buffer)
|
78
80
|
prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
|
81
|
+
if @config.show_mode_in_prompt
|
82
|
+
if @config.editing_mode_is?(:vi_command)
|
83
|
+
mode_icon = @config.vi_cmd_mode_icon
|
84
|
+
elsif @config.editing_mode_is?(:vi_insert)
|
85
|
+
mode_icon = @config.vi_ins_mode_icon
|
86
|
+
elsif @config.editing_mode_is?(:emacs)
|
87
|
+
mode_icon = @config.emacs_mode_string
|
88
|
+
else
|
89
|
+
mode_icon = '?'
|
90
|
+
end
|
91
|
+
prompt_list.map!{ |pr| mode_icon + pr }
|
92
|
+
end
|
79
93
|
prompt = prompt_list[@line_index]
|
80
94
|
prompt_width = calculate_width(prompt, true)
|
81
95
|
[prompt, prompt_width, prompt_list]
|
82
96
|
else
|
83
97
|
prompt_width = calculate_width(prompt, true)
|
98
|
+
if @config.show_mode_in_prompt
|
99
|
+
if @config.editing_mode_is?(:vi_command)
|
100
|
+
mode_icon = @config.vi_cmd_mode_icon
|
101
|
+
elsif @config.editing_mode_is?(:vi_insert)
|
102
|
+
mode_icon = @config.vi_ins_mode_icon
|
103
|
+
elsif @config.editing_mode_is?(:emacs)
|
104
|
+
mode_icon = @config.emacs_mode_string
|
105
|
+
else
|
106
|
+
mode_icon = '?'
|
107
|
+
end
|
108
|
+
prompt = mode_icon + prompt
|
109
|
+
end
|
84
110
|
[prompt, prompt_width, nil]
|
85
111
|
end
|
86
112
|
end
|
87
113
|
|
88
|
-
def reset(prompt = '', encoding
|
114
|
+
def reset(prompt = '', encoding:)
|
89
115
|
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
|
90
116
|
@screen_size = Reline::IOGate.get_screen_size
|
91
|
-
reset_variables(prompt, encoding)
|
117
|
+
reset_variables(prompt, encoding: encoding)
|
92
118
|
@old_trap = Signal.trap('SIGINT') {
|
93
119
|
@old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
|
94
120
|
raise Interrupt
|
@@ -116,7 +142,7 @@ class Reline::LineEditor
|
|
116
142
|
if @line_index.zero?
|
117
143
|
0
|
118
144
|
else
|
119
|
-
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
|
145
|
+
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
|
120
146
|
end
|
121
147
|
if @prompt_proc
|
122
148
|
prompt = prompt_list[@line_index]
|
@@ -139,7 +165,7 @@ class Reline::LineEditor
|
|
139
165
|
@eof
|
140
166
|
end
|
141
167
|
|
142
|
-
def reset_variables(prompt = '', encoding
|
168
|
+
def reset_variables(prompt = '', encoding:)
|
143
169
|
@prompt = prompt
|
144
170
|
@mark_pointer = nil
|
145
171
|
@encoding = encoding
|
@@ -190,10 +216,10 @@ class Reline::LineEditor
|
|
190
216
|
@is_multiline = false
|
191
217
|
end
|
192
218
|
|
193
|
-
private def calculate_height_by_lines(lines,
|
219
|
+
private def calculate_height_by_lines(lines, prompt)
|
194
220
|
result = 0
|
221
|
+
prompt_list = prompt.is_a?(Array) ? prompt : nil
|
195
222
|
lines.each_with_index { |line, i|
|
196
|
-
prompt = ''
|
197
223
|
prompt = prompt_list[i] if prompt_list and prompt_list[i]
|
198
224
|
result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
|
199
225
|
}
|
@@ -211,40 +237,8 @@ class Reline::LineEditor
|
|
211
237
|
width.div(@screen_size.last) + 1
|
212
238
|
end
|
213
239
|
|
214
|
-
private def split_by_width(
|
215
|
-
|
216
|
-
height = 1
|
217
|
-
width = 0
|
218
|
-
rest = "#{prompt}#{str}".encode(Encoding::UTF_8)
|
219
|
-
in_zero_width = false
|
220
|
-
rest.scan(WIDTH_SCANNER) do |gc|
|
221
|
-
case gc
|
222
|
-
when NON_PRINTING_START
|
223
|
-
in_zero_width = true
|
224
|
-
when NON_PRINTING_END
|
225
|
-
in_zero_width = false
|
226
|
-
when CSI_REGEXP, OSC_REGEXP
|
227
|
-
lines.last << gc
|
228
|
-
else
|
229
|
-
unless in_zero_width
|
230
|
-
mbchar_width = Reline::Unicode.get_mbchar_width(gc)
|
231
|
-
if (width += mbchar_width) > max_width
|
232
|
-
width = mbchar_width
|
233
|
-
lines << nil
|
234
|
-
lines << String.new(encoding: @encoding)
|
235
|
-
height += 1
|
236
|
-
end
|
237
|
-
end
|
238
|
-
lines.last << gc
|
239
|
-
end
|
240
|
-
end
|
241
|
-
# The cursor moves to next line in first
|
242
|
-
if width == max_width
|
243
|
-
lines << nil
|
244
|
-
lines << String.new(encoding: @encoding)
|
245
|
-
height += 1
|
246
|
-
end
|
247
|
-
[lines, height]
|
240
|
+
private def split_by_width(str, max_width)
|
241
|
+
Reline::Unicode.split_by_width(str, max_width, @encoding)
|
248
242
|
end
|
249
243
|
|
250
244
|
private def scroll_down(val)
|
@@ -312,14 +306,19 @@ class Reline::LineEditor
|
|
312
306
|
@byte_pointer = new_byte_pointer
|
313
307
|
end
|
314
308
|
|
309
|
+
def rerender_all
|
310
|
+
@rerender_all = true
|
311
|
+
rerender
|
312
|
+
end
|
313
|
+
|
315
314
|
def rerender
|
316
315
|
return if @line.nil?
|
317
316
|
if @menu_info
|
318
317
|
scroll_down(@highest_in_all - @first_line_started_from)
|
319
318
|
@rerender_all = true
|
320
|
-
@menu_info.list.each do |item|
|
319
|
+
@menu_info.list.sort!.each do |item|
|
321
320
|
Reline::IOGate.move_cursor_column(0)
|
322
|
-
@output.
|
321
|
+
@output.write item
|
323
322
|
@output.flush
|
324
323
|
scroll_down(1)
|
325
324
|
end
|
@@ -358,7 +357,7 @@ class Reline::LineEditor
|
|
358
357
|
new_lines = whole_lines
|
359
358
|
end
|
360
359
|
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines, prompt)
|
361
|
-
all_height = calculate_height_by_lines(new_lines, prompt_list)
|
360
|
+
all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
|
362
361
|
diff = all_height - @highest_in_all
|
363
362
|
move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
|
364
363
|
if diff > 0
|
@@ -398,7 +397,7 @@ class Reline::LineEditor
|
|
398
397
|
if @line_index.zero?
|
399
398
|
0
|
400
399
|
else
|
401
|
-
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list)
|
400
|
+
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
|
402
401
|
end
|
403
402
|
if @prompt_proc
|
404
403
|
prompt = prompt_list[@line_index]
|
@@ -457,7 +456,7 @@ class Reline::LineEditor
|
|
457
456
|
if @line_index.zero?
|
458
457
|
0
|
459
458
|
else
|
460
|
-
calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list)
|
459
|
+
calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
|
461
460
|
end
|
462
461
|
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
463
462
|
move_cursor_down(@first_line_started_from + @started_from)
|
@@ -488,7 +487,7 @@ class Reline::LineEditor
|
|
488
487
|
end
|
489
488
|
|
490
489
|
private def render_partial(prompt, prompt_width, line_to_render, with_control = true)
|
491
|
-
visual_lines, height = split_by_width(
|
490
|
+
visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
|
492
491
|
if with_control
|
493
492
|
if height > @highest_in_this
|
494
493
|
diff = height - @highest_in_this
|
@@ -507,12 +506,30 @@ class Reline::LineEditor
|
|
507
506
|
Reline::IOGate.move_cursor_column(0)
|
508
507
|
visual_lines.each_with_index do |line, index|
|
509
508
|
if line.nil?
|
510
|
-
Reline::IOGate.
|
511
|
-
|
512
|
-
|
509
|
+
if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
|
510
|
+
# reaches the end of line
|
511
|
+
if Reline::IOGate.win?
|
512
|
+
# A newline is automatically inserted if a character is rendered at
|
513
|
+
# eol on command prompt.
|
514
|
+
else
|
515
|
+
# When the cursor is at the end of the line and erases characters
|
516
|
+
# after the cursor, some terminals delete the character at the
|
517
|
+
# cursor position.
|
518
|
+
move_cursor_down(1)
|
519
|
+
Reline::IOGate.move_cursor_column(0)
|
520
|
+
end
|
521
|
+
else
|
522
|
+
Reline::IOGate.erase_after_cursor
|
523
|
+
move_cursor_down(1)
|
524
|
+
Reline::IOGate.move_cursor_column(0)
|
525
|
+
end
|
513
526
|
next
|
514
527
|
end
|
515
|
-
@output.
|
528
|
+
@output.write line
|
529
|
+
if Reline::IOGate.win? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
|
530
|
+
# A newline is automatically inserted if a character is rendered at eol on command prompt.
|
531
|
+
@rest_height -= 1 if @rest_height > 0
|
532
|
+
end
|
516
533
|
@output.flush
|
517
534
|
if @first_prompt
|
518
535
|
@first_prompt = false
|
@@ -520,22 +537,25 @@ class Reline::LineEditor
|
|
520
537
|
end
|
521
538
|
end
|
522
539
|
Reline::IOGate.erase_after_cursor
|
540
|
+
Reline::IOGate.move_cursor_column(0)
|
523
541
|
if with_control
|
524
|
-
|
542
|
+
# Just after rendring, so the cursor is on the last line.
|
525
543
|
if finished?
|
526
|
-
|
544
|
+
Reline::IOGate.move_cursor_column(0)
|
545
|
+
else
|
546
|
+
# Moves up from bottom of lines to the cursor position.
|
547
|
+
move_cursor_up(height - 1 - @started_from)
|
548
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
527
549
|
end
|
528
|
-
move_cursor_down(@started_from)
|
529
|
-
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
530
550
|
end
|
531
551
|
height
|
532
552
|
end
|
533
553
|
|
534
554
|
private def modify_lines(before)
|
535
|
-
return before if before.nil? || before.empty?
|
555
|
+
return before if before.nil? || before.empty? || simplified_rendering?
|
536
556
|
|
537
557
|
if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
|
538
|
-
after.lines(chomp
|
558
|
+
after.lines("\n").map { |l| l.chomp('') }
|
539
559
|
else
|
540
560
|
before
|
541
561
|
end
|
@@ -560,7 +580,7 @@ class Reline::LineEditor
|
|
560
580
|
else
|
561
581
|
i&.start_with?(target)
|
562
582
|
end
|
563
|
-
}
|
583
|
+
}.uniq
|
564
584
|
if is_menu
|
565
585
|
menu(target, list)
|
566
586
|
return nil
|
@@ -831,7 +851,7 @@ class Reline::LineEditor
|
|
831
851
|
unless completion_occurs
|
832
852
|
@completion_state = CompletionState::NORMAL
|
833
853
|
end
|
834
|
-
if @is_multiline and @auto_indent_proc
|
854
|
+
if @is_multiline and @auto_indent_proc and not simplified_rendering?
|
835
855
|
process_auto_indent
|
836
856
|
end
|
837
857
|
end
|
@@ -905,7 +925,6 @@ class Reline::LineEditor
|
|
905
925
|
quote = nil
|
906
926
|
i += 1
|
907
927
|
rest = nil
|
908
|
-
break_pointer = nil
|
909
928
|
elsif quote and slice.start_with?(escaped_quote)
|
910
929
|
# skip
|
911
930
|
i += 2
|
@@ -915,10 +934,11 @@ class Reline::LineEditor
|
|
915
934
|
closing_quote = /(?!\\)#{Regexp.escape(quote)}/
|
916
935
|
escaped_quote = /\\#{Regexp.escape(quote)}/
|
917
936
|
i += 1
|
918
|
-
break_pointer = i
|
937
|
+
break_pointer = i - 1
|
919
938
|
elsif not quote and slice =~ word_break_regexp
|
920
939
|
rest = $'
|
921
940
|
i += 1
|
941
|
+
before = @line.byteslice(i, @byte_pointer - i)
|
922
942
|
break_pointer = i
|
923
943
|
else
|
924
944
|
i += 1
|
@@ -936,6 +956,11 @@ class Reline::LineEditor
|
|
936
956
|
end
|
937
957
|
else
|
938
958
|
preposing = ''
|
959
|
+
if break_pointer
|
960
|
+
preposing = @line.byteslice(0, break_pointer)
|
961
|
+
else
|
962
|
+
preposing = ''
|
963
|
+
end
|
939
964
|
target = before
|
940
965
|
end
|
941
966
|
[preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
|
@@ -1028,6 +1053,7 @@ class Reline::LineEditor
|
|
1028
1053
|
|
1029
1054
|
def finish
|
1030
1055
|
@finished = true
|
1056
|
+
@rerender_all = true
|
1031
1057
|
@config.reset
|
1032
1058
|
end
|
1033
1059
|
|
@@ -1045,29 +1071,7 @@ class Reline::LineEditor
|
|
1045
1071
|
end
|
1046
1072
|
|
1047
1073
|
private def calculate_width(str, allow_escape_code = false)
|
1048
|
-
|
1049
|
-
width = 0
|
1050
|
-
rest = str.encode(Encoding::UTF_8)
|
1051
|
-
in_zero_width = false
|
1052
|
-
rest.scan(WIDTH_SCANNER) do |gc|
|
1053
|
-
case gc
|
1054
|
-
when NON_PRINTING_START
|
1055
|
-
in_zero_width = true
|
1056
|
-
when NON_PRINTING_END
|
1057
|
-
in_zero_width = false
|
1058
|
-
when CSI_REGEXP, OSC_REGEXP
|
1059
|
-
else
|
1060
|
-
unless in_zero_width
|
1061
|
-
width += Reline::Unicode.get_mbchar_width(gc)
|
1062
|
-
end
|
1063
|
-
end
|
1064
|
-
end
|
1065
|
-
width
|
1066
|
-
else
|
1067
|
-
str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc|
|
1068
|
-
w + Reline::Unicode.get_mbchar_width(gc)
|
1069
|
-
}
|
1070
|
-
end
|
1074
|
+
Reline::Unicode.calculate_width(str, allow_escape_code)
|
1071
1075
|
end
|
1072
1076
|
|
1073
1077
|
private def key_delete(key)
|
@@ -1090,6 +1094,11 @@ class Reline::LineEditor
|
|
1090
1094
|
|
1091
1095
|
private def ed_insert(key)
|
1092
1096
|
if key.instance_of?(String)
|
1097
|
+
begin
|
1098
|
+
key.encode(Encoding::UTF_8)
|
1099
|
+
rescue Encoding::UndefinedConversionError
|
1100
|
+
return
|
1101
|
+
end
|
1093
1102
|
width = Reline::Unicode.get_mbchar_width(key)
|
1094
1103
|
if @cursor == @cursor_max
|
1095
1104
|
@line += key
|
@@ -1100,6 +1109,11 @@ class Reline::LineEditor
|
|
1100
1109
|
@cursor += width
|
1101
1110
|
@cursor_max += width
|
1102
1111
|
else
|
1112
|
+
begin
|
1113
|
+
key.chr.encode(Encoding::UTF_8)
|
1114
|
+
rescue Encoding::UndefinedConversionError
|
1115
|
+
return
|
1116
|
+
end
|
1103
1117
|
if @cursor == @cursor_max
|
1104
1118
|
@line += key.chr
|
1105
1119
|
else
|
@@ -1235,7 +1249,7 @@ class Reline::LineEditor
|
|
1235
1249
|
if search_word.empty? and Reline.last_incremental_search
|
1236
1250
|
search_word = Reline.last_incremental_search
|
1237
1251
|
end
|
1238
|
-
if @history_pointer
|
1252
|
+
if @history_pointer
|
1239
1253
|
case prev_search_key
|
1240
1254
|
when "\C-r".ord
|
1241
1255
|
history_pointer_base = 0
|
@@ -1307,7 +1321,7 @@ class Reline::LineEditor
|
|
1307
1321
|
end
|
1308
1322
|
end
|
1309
1323
|
|
1310
|
-
private def
|
1324
|
+
private def incremental_search_history(key)
|
1311
1325
|
unless @history_pointer
|
1312
1326
|
if @is_multiline
|
1313
1327
|
@line_backup_in_history = whole_buffer
|
@@ -1388,15 +1402,114 @@ class Reline::LineEditor
|
|
1388
1402
|
}
|
1389
1403
|
end
|
1390
1404
|
|
1391
|
-
private def
|
1392
|
-
|
1405
|
+
private def vi_search_prev(key)
|
1406
|
+
incremental_search_history(key)
|
1407
|
+
end
|
1408
|
+
alias_method :reverse_search_history, :vi_search_prev
|
1409
|
+
|
1410
|
+
private def vi_search_next(key)
|
1411
|
+
incremental_search_history(key)
|
1393
1412
|
end
|
1394
|
-
alias_method :
|
1413
|
+
alias_method :forward_search_history, :vi_search_next
|
1395
1414
|
|
1396
|
-
private def
|
1397
|
-
|
1415
|
+
private def ed_search_prev_history(key, arg: 1)
|
1416
|
+
history = nil
|
1417
|
+
h_pointer = nil
|
1418
|
+
line_no = nil
|
1419
|
+
substr = @line.slice(0, @byte_pointer)
|
1420
|
+
if @history_pointer.nil?
|
1421
|
+
return if not @line.empty? and substr.empty?
|
1422
|
+
history = Reline::HISTORY
|
1423
|
+
elsif @history_pointer.zero?
|
1424
|
+
history = nil
|
1425
|
+
h_pointer = nil
|
1426
|
+
else
|
1427
|
+
history = Reline::HISTORY.slice(0, @history_pointer)
|
1428
|
+
end
|
1429
|
+
return if history.nil?
|
1430
|
+
if @is_multiline
|
1431
|
+
h_pointer = history.rindex { |h|
|
1432
|
+
h.split("\n").each_with_index { |l, i|
|
1433
|
+
if l.start_with?(substr)
|
1434
|
+
line_no = i
|
1435
|
+
break
|
1436
|
+
end
|
1437
|
+
}
|
1438
|
+
not line_no.nil?
|
1439
|
+
}
|
1440
|
+
else
|
1441
|
+
h_pointer = history.rindex { |l|
|
1442
|
+
l.start_with?(substr)
|
1443
|
+
}
|
1444
|
+
end
|
1445
|
+
return if h_pointer.nil?
|
1446
|
+
@history_pointer = h_pointer
|
1447
|
+
if @is_multiline
|
1448
|
+
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
|
1449
|
+
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
1450
|
+
@line_index = line_no
|
1451
|
+
@line = @buffer_of_lines.last
|
1452
|
+
@rerender_all = true
|
1453
|
+
else
|
1454
|
+
@line = Reline::HISTORY[@history_pointer]
|
1455
|
+
end
|
1456
|
+
@cursor_max = calculate_width(@line)
|
1457
|
+
arg -= 1
|
1458
|
+
ed_search_prev_history(key, arg: arg) if arg > 0
|
1459
|
+
end
|
1460
|
+
alias_method :history_search_backward, :ed_search_prev_history
|
1461
|
+
|
1462
|
+
private def ed_search_next_history(key, arg: 1)
|
1463
|
+
substr = @line.slice(0, @byte_pointer)
|
1464
|
+
if @history_pointer.nil?
|
1465
|
+
return
|
1466
|
+
elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
|
1467
|
+
return
|
1468
|
+
end
|
1469
|
+
history = Reline::HISTORY.slice((@history_pointer + 1)..-1)
|
1470
|
+
h_pointer = nil
|
1471
|
+
line_no = nil
|
1472
|
+
if @is_multiline
|
1473
|
+
h_pointer = history.index { |h|
|
1474
|
+
h.split("\n").each_with_index { |l, i|
|
1475
|
+
if l.start_with?(substr)
|
1476
|
+
line_no = i
|
1477
|
+
break
|
1478
|
+
end
|
1479
|
+
}
|
1480
|
+
not line_no.nil?
|
1481
|
+
}
|
1482
|
+
else
|
1483
|
+
h_pointer = history.index { |l|
|
1484
|
+
l.start_with?(substr)
|
1485
|
+
}
|
1486
|
+
end
|
1487
|
+
h_pointer += @history_pointer + 1 if h_pointer and @history_pointer
|
1488
|
+
return if h_pointer.nil? and not substr.empty?
|
1489
|
+
@history_pointer = h_pointer
|
1490
|
+
if @is_multiline
|
1491
|
+
if @history_pointer.nil? and substr.empty?
|
1492
|
+
@buffer_of_lines = []
|
1493
|
+
@line_index = 0
|
1494
|
+
else
|
1495
|
+
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
|
1496
|
+
@line_index = line_no
|
1497
|
+
end
|
1498
|
+
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
1499
|
+
@line = @buffer_of_lines.last
|
1500
|
+
@rerender_all = true
|
1501
|
+
else
|
1502
|
+
if @history_pointer.nil? and substr.empty?
|
1503
|
+
@line = ''
|
1504
|
+
else
|
1505
|
+
@line = Reline::HISTORY[@history_pointer]
|
1506
|
+
end
|
1507
|
+
end
|
1508
|
+
@cursor_max = calculate_width(@line)
|
1509
|
+
arg -= 1
|
1510
|
+
ed_search_next_history(key, arg: arg) if arg > 0
|
1398
1511
|
end
|
1399
|
-
alias_method :
|
1512
|
+
alias_method :history_search_forward, :ed_search_next_history
|
1400
1513
|
|
1401
1514
|
private def ed_prev_history(key, arg: 1)
|
1402
1515
|
if @is_multiline and @line_index > 0
|
@@ -1875,6 +1988,16 @@ class Reline::LineEditor
|
|
1875
1988
|
end
|
1876
1989
|
end
|
1877
1990
|
|
1991
|
+
private def vi_insert_at_bol(key)
|
1992
|
+
ed_move_to_beg(key)
|
1993
|
+
@config.editing_mode = :vi_insert
|
1994
|
+
end
|
1995
|
+
|
1996
|
+
private def vi_add_at_eol(key)
|
1997
|
+
ed_move_to_end(key)
|
1998
|
+
@config.editing_mode = :vi_insert
|
1999
|
+
end
|
2000
|
+
|
1878
2001
|
private def ed_delete_prev_char(key, arg: 1)
|
1879
2002
|
deleted = ''
|
1880
2003
|
arg.times do
|
@@ -1897,6 +2020,18 @@ class Reline::LineEditor
|
|
1897
2020
|
end
|
1898
2021
|
|
1899
2022
|
private def vi_change_meta(key)
|
2023
|
+
@waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
|
2024
|
+
if byte_pointer_diff > 0
|
2025
|
+
@line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
|
2026
|
+
elsif byte_pointer_diff < 0
|
2027
|
+
@line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
|
2028
|
+
end
|
2029
|
+
copy_for_vi(cut)
|
2030
|
+
@cursor += cursor_diff if cursor_diff < 0
|
2031
|
+
@cursor_max -= cursor_diff.abs
|
2032
|
+
@byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
|
2033
|
+
@config.editing_mode = :vi_insert
|
2034
|
+
}
|
1900
2035
|
end
|
1901
2036
|
|
1902
2037
|
private def vi_delete_meta(key)
|
@@ -1977,7 +2112,7 @@ class Reline::LineEditor
|
|
1977
2112
|
fp.path
|
1978
2113
|
}
|
1979
2114
|
system("#{ENV['EDITOR']} #{path}")
|
1980
|
-
@line =
|
2115
|
+
@line = File.read(path)
|
1981
2116
|
finish
|
1982
2117
|
end
|
1983
2118
|
|
@@ -2062,12 +2197,17 @@ class Reline::LineEditor
|
|
2062
2197
|
@waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg) }
|
2063
2198
|
end
|
2064
2199
|
|
2065
|
-
private def
|
2200
|
+
private def vi_to_next_char(key, arg: 1)
|
2201
|
+
@waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, true) }
|
2202
|
+
end
|
2203
|
+
|
2204
|
+
private def search_next_char(key, arg, need_prev_char = false)
|
2066
2205
|
if key.instance_of?(String)
|
2067
2206
|
inputed_char = key
|
2068
2207
|
else
|
2069
2208
|
inputed_char = key.chr
|
2070
2209
|
end
|
2210
|
+
prev_total = nil
|
2071
2211
|
total = nil
|
2072
2212
|
found = false
|
2073
2213
|
@line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
|
@@ -2085,13 +2225,66 @@ class Reline::LineEditor
|
|
2085
2225
|
end
|
2086
2226
|
end
|
2087
2227
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
2228
|
+
prev_total = total
|
2088
2229
|
total = [total.first + mbchar.bytesize, total.last + width]
|
2089
2230
|
end
|
2090
2231
|
end
|
2091
|
-
if found and total
|
2232
|
+
if not need_prev_char and found and total
|
2092
2233
|
byte_size, width = total
|
2093
2234
|
@byte_pointer += byte_size
|
2094
2235
|
@cursor += width
|
2236
|
+
elsif need_prev_char and found and prev_total
|
2237
|
+
byte_size, width = prev_total
|
2238
|
+
@byte_pointer += byte_size
|
2239
|
+
@cursor += width
|
2240
|
+
end
|
2241
|
+
@waiting_proc = nil
|
2242
|
+
end
|
2243
|
+
|
2244
|
+
private def vi_prev_char(key, arg: 1)
|
2245
|
+
@waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg) }
|
2246
|
+
end
|
2247
|
+
|
2248
|
+
private def vi_to_prev_char(key, arg: 1)
|
2249
|
+
@waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg, true) }
|
2250
|
+
end
|
2251
|
+
|
2252
|
+
private def search_prev_char(key, arg, need_next_char = false)
|
2253
|
+
if key.instance_of?(String)
|
2254
|
+
inputed_char = key
|
2255
|
+
else
|
2256
|
+
inputed_char = key.chr
|
2257
|
+
end
|
2258
|
+
prev_total = nil
|
2259
|
+
total = nil
|
2260
|
+
found = false
|
2261
|
+
@line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
|
2262
|
+
# total has [byte_size, cursor]
|
2263
|
+
unless total
|
2264
|
+
# skip cursor point
|
2265
|
+
width = Reline::Unicode.get_mbchar_width(mbchar)
|
2266
|
+
total = [mbchar.bytesize, width]
|
2267
|
+
else
|
2268
|
+
if inputed_char == mbchar
|
2269
|
+
arg -= 1
|
2270
|
+
if arg.zero?
|
2271
|
+
found = true
|
2272
|
+
break
|
2273
|
+
end
|
2274
|
+
end
|
2275
|
+
width = Reline::Unicode.get_mbchar_width(mbchar)
|
2276
|
+
prev_total = total
|
2277
|
+
total = [total.first + mbchar.bytesize, total.last + width]
|
2278
|
+
end
|
2279
|
+
end
|
2280
|
+
if not need_next_char and found and total
|
2281
|
+
byte_size, width = total
|
2282
|
+
@byte_pointer -= byte_size
|
2283
|
+
@cursor -= width
|
2284
|
+
elsif need_next_char and found and prev_total
|
2285
|
+
byte_size, width = prev_total
|
2286
|
+
@byte_pointer -= byte_size
|
2287
|
+
@cursor -= width
|
2095
2288
|
end
|
2096
2289
|
@waiting_proc = nil
|
2097
2290
|
end
|
@@ -2119,7 +2312,6 @@ class Reline::LineEditor
|
|
2119
2312
|
new_pointer = [@byte_pointer, @line_index]
|
2120
2313
|
@previous_line_index = @line_index
|
2121
2314
|
@byte_pointer, @line_index = @mark_pointer
|
2122
|
-
@byte_pointer, @line_index = @mark_pointer
|
2123
2315
|
@cursor = calculate_width(@line.byteslice(0, @byte_pointer))
|
2124
2316
|
@cursor_max = calculate_width(@line)
|
2125
2317
|
@mark_pointer = new_pointer
|