reline 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/reline.rb +313 -94
- data/lib/reline/ansi.rb +60 -20
- data/lib/reline/config.rb +137 -85
- data/lib/reline/general_io.rb +64 -0
- data/lib/reline/history.rb +56 -0
- data/lib/reline/key_actor/emacs.rb +37 -38
- data/lib/reline/key_actor/vi_command.rb +34 -35
- data/lib/reline/key_actor/vi_insert.rb +160 -161
- data/lib/reline/key_stroke.rb +3 -24
- data/lib/reline/line_editor.rb +736 -217
- data/lib/reline/unicode.rb +113 -1
- data/lib/reline/version.rb +1 -1
- data/lib/reline/windows.rb +72 -12
- metadata +5 -3
data/lib/reline/key_stroke.rb
CHANGED
@@ -13,27 +13,8 @@ class Reline::KeyStroke
|
|
13
13
|
|
14
14
|
def initialize(config)
|
15
15
|
@config = config
|
16
|
-
@buffer = []
|
17
16
|
end
|
18
17
|
|
19
|
-
def input_to(bytes)
|
20
|
-
case match_status(bytes)
|
21
|
-
when :matching
|
22
|
-
nil
|
23
|
-
when :matched
|
24
|
-
expand(bytes)
|
25
|
-
when :unmatched
|
26
|
-
bytes
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def input_to!(bytes)
|
31
|
-
@buffer.concat Array(bytes)
|
32
|
-
input_to(@buffer)&.tap { clear }
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
18
|
def match_status(input)
|
38
19
|
key_mapping.keys.select { |lhs|
|
39
20
|
lhs.start_with? input
|
@@ -64,11 +45,9 @@ class Reline::KeyStroke
|
|
64
45
|
end
|
65
46
|
end
|
66
47
|
|
67
|
-
|
68
|
-
@config[:key_mapping].transform_keys(&:bytes)
|
69
|
-
end
|
48
|
+
private
|
70
49
|
|
71
|
-
def
|
72
|
-
@
|
50
|
+
def key_mapping
|
51
|
+
@config.key_bindings
|
73
52
|
end
|
74
53
|
end
|
data/lib/reline/line_editor.rb
CHANGED
@@ -7,42 +7,15 @@ require 'pathname'
|
|
7
7
|
class Reline::LineEditor
|
8
8
|
# TODO: undo
|
9
9
|
attr_reader :line
|
10
|
+
attr_reader :byte_pointer
|
10
11
|
attr_accessor :confirm_multiline_termination_proc
|
11
12
|
attr_accessor :completion_proc
|
13
|
+
attr_accessor :output_modifier_proc
|
14
|
+
attr_accessor :prompt_proc
|
15
|
+
attr_accessor :auto_indent_proc
|
16
|
+
attr_accessor :pre_input_hook
|
12
17
|
attr_accessor :dig_perfect_match_proc
|
13
|
-
attr_writer :
|
14
|
-
|
15
|
-
ARGUMENTABLE = %i{
|
16
|
-
ed_delete_next_char
|
17
|
-
ed_delete_prev_char
|
18
|
-
ed_delete_prev_word
|
19
|
-
ed_next_char
|
20
|
-
ed_next_history
|
21
|
-
ed_next_line#
|
22
|
-
ed_prev_char
|
23
|
-
ed_prev_history
|
24
|
-
ed_prev_line#
|
25
|
-
ed_prev_word
|
26
|
-
ed_quoted_insert
|
27
|
-
vi_to_column
|
28
|
-
vi_next_word
|
29
|
-
vi_prev_word
|
30
|
-
vi_end_word
|
31
|
-
vi_next_big_word
|
32
|
-
vi_prev_big_word
|
33
|
-
vi_end_big_word
|
34
|
-
vi_next_char
|
35
|
-
vi_delete_meta
|
36
|
-
vi_paste_prev
|
37
|
-
vi_paste_next
|
38
|
-
vi_replace_char
|
39
|
-
}
|
40
|
-
|
41
|
-
VI_OPERATORS = %i{
|
42
|
-
vi_change_meta
|
43
|
-
vi_delete_meta
|
44
|
-
vi_yank
|
45
|
-
}
|
18
|
+
attr_writer :output
|
46
19
|
|
47
20
|
VI_MOTIONS = %i{
|
48
21
|
ed_prev_char
|
@@ -76,42 +49,76 @@ class Reline::LineEditor
|
|
76
49
|
CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
|
77
50
|
MenuInfo = Struct.new('MenuInfo', :target, :list)
|
78
51
|
|
79
|
-
|
52
|
+
CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/
|
53
|
+
OSC_REGEXP = /\e\]\d+(?:;[^;]+)*\a/
|
54
|
+
NON_PRINTING_START = "\1"
|
55
|
+
NON_PRINTING_END = "\2"
|
56
|
+
WIDTH_SCANNER = /\G(?:#{NON_PRINTING_START}|#{NON_PRINTING_END}|#{CSI_REGEXP}|#{OSC_REGEXP}|\X)/
|
57
|
+
|
58
|
+
def initialize(config)
|
80
59
|
@config = config
|
60
|
+
reset_variables
|
61
|
+
end
|
62
|
+
|
63
|
+
def reset(prompt = '', encoding = Encoding.default_external)
|
64
|
+
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
|
65
|
+
@screen_size = Reline::IOGate.get_screen_size
|
66
|
+
reset_variables(prompt, encoding)
|
67
|
+
@old_trap = Signal.trap('SIGINT') {
|
68
|
+
scroll_down(@highest_in_all - @first_line_started_from)
|
69
|
+
Reline::IOGate.move_cursor_column(0)
|
70
|
+
@old_trap.call if @old_trap.respond_to?(:call) # can also be string, ex: "DEFAULT"
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
def finalize
|
75
|
+
Signal.trap('SIGINT', @old_trap)
|
76
|
+
end
|
77
|
+
|
78
|
+
def eof?
|
79
|
+
@eof
|
80
|
+
end
|
81
|
+
|
82
|
+
def reset_variables(prompt = '', encoding = Encoding.default_external)
|
81
83
|
@prompt = prompt
|
82
|
-
@prompt_width = calculate_width(@prompt)
|
83
|
-
@cursor = 0
|
84
|
-
@cursor_max = 0
|
85
|
-
@byte_pointer = 0
|
86
84
|
@encoding = encoding
|
87
|
-
@buffer_of_lines = [String.new(encoding: @encoding)]
|
88
|
-
@line_index = 0
|
89
|
-
@previous_line_index = nil
|
90
|
-
@line = @buffer_of_lines[0]
|
91
85
|
@is_multiline = false
|
92
86
|
@finished = false
|
93
87
|
@cleared = false
|
94
88
|
@rerender_all = false
|
95
|
-
@is_confirm_multiline_termination = false
|
96
89
|
@history_pointer = nil
|
97
|
-
@line_backup_in_history = nil
|
98
90
|
@kill_ring = Reline::KillRing.new
|
99
91
|
@vi_clipboard = ''
|
100
92
|
@vi_arg = nil
|
101
|
-
@multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
|
102
|
-
@meta_prefix = false
|
103
93
|
@waiting_proc = nil
|
104
94
|
@waiting_operator_proc = nil
|
105
95
|
@completion_journey_data = nil
|
106
96
|
@completion_state = CompletionState::NORMAL
|
107
97
|
@perfect_matched = nil
|
108
|
-
@
|
98
|
+
@menu_info = nil
|
99
|
+
@first_prompt = true
|
100
|
+
@searching_prompt = nil
|
101
|
+
@first_char = true
|
102
|
+
@eof = false
|
103
|
+
reset_line
|
104
|
+
end
|
105
|
+
|
106
|
+
def reset_line
|
107
|
+
@cursor = 0
|
108
|
+
@cursor_max = 0
|
109
|
+
@byte_pointer = 0
|
110
|
+
@buffer_of_lines = [String.new(encoding: @encoding)]
|
111
|
+
@line_index = 0
|
112
|
+
@previous_line_index = nil
|
113
|
+
@line = @buffer_of_lines[0]
|
109
114
|
@first_line_started_from = 0
|
110
115
|
@move_up = 0
|
111
116
|
@started_from = 0
|
112
117
|
@highest_in_this = 1
|
113
118
|
@highest_in_all = 1
|
114
|
-
@
|
119
|
+
@line_backup_in_history = nil
|
120
|
+
@multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
|
121
|
+
@check_new_auto_indent = false
|
115
122
|
end
|
116
123
|
|
117
124
|
def multiline_on
|
@@ -130,47 +137,59 @@ class Reline::LineEditor
|
|
130
137
|
end
|
131
138
|
|
132
139
|
private def calculate_height_by_width(width)
|
133
|
-
|
134
|
-
height = 1
|
135
|
-
max_width = @screen_size.last
|
136
|
-
while width > max_width * height
|
137
|
-
height += 1
|
138
|
-
end
|
139
|
-
height += 1 if (width % max_width).zero?
|
140
|
-
height
|
140
|
+
width.div(@screen_size.last) + 1
|
141
141
|
end
|
142
142
|
|
143
|
-
private def split_by_width(str, max_width)
|
143
|
+
private def split_by_width(prompt, str, max_width)
|
144
144
|
lines = [String.new(encoding: @encoding)]
|
145
|
+
height = 1
|
145
146
|
width = 0
|
146
|
-
str.encode(Encoding::UTF_8)
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
147
|
+
rest = "#{prompt}#{str}".encode(Encoding::UTF_8)
|
148
|
+
in_zero_width = false
|
149
|
+
rest.scan(WIDTH_SCANNER) do |gc|
|
150
|
+
case gc
|
151
|
+
when NON_PRINTING_START
|
152
|
+
in_zero_width = true
|
153
|
+
when NON_PRINTING_END
|
154
|
+
in_zero_width = false
|
155
|
+
when CSI_REGEXP, OSC_REGEXP
|
156
|
+
lines.last << gc
|
157
|
+
else
|
158
|
+
unless in_zero_width
|
159
|
+
mbchar_width = Reline::Unicode.get_mbchar_width(gc)
|
160
|
+
if (width += mbchar_width) > max_width
|
161
|
+
width = mbchar_width
|
162
|
+
lines << nil
|
163
|
+
lines << String.new(encoding: @encoding)
|
164
|
+
height += 1
|
165
|
+
end
|
166
|
+
end
|
167
|
+
lines.last << gc
|
152
168
|
end
|
153
|
-
lines.last << gc
|
154
169
|
end
|
155
170
|
# The cursor moves to next line in first
|
156
|
-
|
157
|
-
|
171
|
+
if width == max_width
|
172
|
+
lines << nil
|
173
|
+
lines << String.new(encoding: @encoding)
|
174
|
+
height += 1
|
175
|
+
end
|
176
|
+
[lines, height]
|
158
177
|
end
|
159
178
|
|
160
179
|
private def scroll_down(val)
|
161
180
|
if val <= @rest_height
|
162
|
-
Reline.move_cursor_down(val)
|
181
|
+
Reline::IOGate.move_cursor_down(val)
|
163
182
|
@rest_height -= val
|
164
183
|
else
|
165
|
-
Reline.move_cursor_down(@rest_height)
|
166
|
-
Reline.scroll_down(val - @rest_height)
|
184
|
+
Reline::IOGate.move_cursor_down(@rest_height)
|
185
|
+
Reline::IOGate.scroll_down(val - @rest_height)
|
167
186
|
@rest_height = 0
|
168
187
|
end
|
169
188
|
end
|
170
189
|
|
171
190
|
private def move_cursor_up(val)
|
172
191
|
if val > 0
|
173
|
-
Reline.move_cursor_up(val)
|
192
|
+
Reline::IOGate.move_cursor_up(val)
|
174
193
|
@rest_height += val
|
175
194
|
elsif val < 0
|
176
195
|
move_cursor_down(-val)
|
@@ -179,7 +198,7 @@ class Reline::LineEditor
|
|
179
198
|
|
180
199
|
private def move_cursor_down(val)
|
181
200
|
if val > 0
|
182
|
-
Reline.move_cursor_down(val)
|
201
|
+
Reline::IOGate.move_cursor_down(val)
|
183
202
|
@rest_height -= val
|
184
203
|
@rest_height = 0 if @rest_height < 0
|
185
204
|
elsif val < 0
|
@@ -193,10 +212,22 @@ class Reline::LineEditor
|
|
193
212
|
new_byte_pointer = 0
|
194
213
|
height = 1
|
195
214
|
max_width = @screen_size.last
|
215
|
+
if @config.editing_mode_is?(:vi_command)
|
216
|
+
last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @line.bytesize)
|
217
|
+
if last_byte_size > 0
|
218
|
+
last_mbchar = @line.byteslice(@line.bytesize - last_byte_size, last_byte_size)
|
219
|
+
last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
|
220
|
+
cursor_max = @cursor_max - last_width
|
221
|
+
else
|
222
|
+
cursor_max = @cursor_max
|
223
|
+
end
|
224
|
+
else
|
225
|
+
cursor_max = @cursor_max
|
226
|
+
end
|
196
227
|
@line.encode(Encoding::UTF_8).grapheme_clusters.each do |gc|
|
197
228
|
mbchar_width = Reline::Unicode.get_mbchar_width(gc)
|
198
229
|
now = new_cursor + mbchar_width
|
199
|
-
if now >
|
230
|
+
if now > cursor_max or now > @cursor
|
200
231
|
break
|
201
232
|
end
|
202
233
|
new_cursor += mbchar_width
|
@@ -211,29 +242,46 @@ class Reline::LineEditor
|
|
211
242
|
end
|
212
243
|
|
213
244
|
def rerender # TODO: support physical and logical lines
|
214
|
-
|
245
|
+
return if @line.nil?
|
215
246
|
if @menu_info
|
216
|
-
|
247
|
+
scroll_down(@highest_in_all - @first_line_started_from)
|
248
|
+
@rerender_all = true
|
217
249
|
@menu_info.list.each do |item|
|
218
|
-
|
250
|
+
Reline::IOGate.move_cursor_column(0)
|
251
|
+
@output.print item
|
252
|
+
scroll_down(1)
|
219
253
|
end
|
254
|
+
scroll_down(@highest_in_all - 1)
|
255
|
+
move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
|
220
256
|
@menu_info = nil
|
221
257
|
end
|
222
|
-
return if @line.nil?
|
223
258
|
if @vi_arg
|
224
259
|
prompt = "(arg: #{@vi_arg}) "
|
225
260
|
prompt_width = calculate_width(prompt)
|
261
|
+
elsif @searching_prompt
|
262
|
+
prompt = @searching_prompt
|
263
|
+
prompt_width = calculate_width(prompt)
|
226
264
|
else
|
227
265
|
prompt = @prompt
|
228
|
-
prompt_width =
|
266
|
+
prompt_width = calculate_width(prompt, true)
|
229
267
|
end
|
230
268
|
if @cleared
|
231
|
-
Reline.clear_screen
|
269
|
+
Reline::IOGate.clear_screen
|
232
270
|
@cleared = false
|
233
271
|
back = 0
|
234
|
-
|
235
|
-
|
236
|
-
|
272
|
+
prompt_list = nil
|
273
|
+
if @prompt_proc
|
274
|
+
prompt_list = @prompt_proc.(whole_lines)
|
275
|
+
prompt = prompt_list[@line_index]
|
276
|
+
prompt_width = calculate_width(prompt, true)
|
277
|
+
end
|
278
|
+
modify_lines(whole_lines).each_with_index do |line, index|
|
279
|
+
if @prompt_proc
|
280
|
+
pr = prompt_list[index]
|
281
|
+
height = render_partial(pr, calculate_width(pr), line, false)
|
282
|
+
else
|
283
|
+
height = render_partial(prompt, prompt_width, line, false)
|
284
|
+
end
|
237
285
|
if index < (@buffer_of_lines.size - 1)
|
238
286
|
move_cursor_down(height)
|
239
287
|
back += height
|
@@ -241,141 +289,223 @@ class Reline::LineEditor
|
|
241
289
|
end
|
242
290
|
move_cursor_up(back)
|
243
291
|
move_cursor_down(@first_line_started_from + @started_from)
|
244
|
-
Reline.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
292
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
245
293
|
return
|
246
294
|
end
|
295
|
+
new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
|
247
296
|
# FIXME: end of logical line sometimes breaks
|
248
|
-
if @previous_line_index
|
249
|
-
|
250
|
-
|
251
|
-
|
297
|
+
if @previous_line_index or new_highest_in_this != @highest_in_this
|
298
|
+
if @previous_line_index
|
299
|
+
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
300
|
+
else
|
301
|
+
new_lines = whole_lines
|
302
|
+
end
|
303
|
+
prompt_list = nil
|
304
|
+
if @prompt_proc
|
305
|
+
prompt_list = @prompt_proc.(new_lines)
|
306
|
+
prompt = prompt_list[@line_index]
|
307
|
+
prompt_width = calculate_width(prompt, true)
|
308
|
+
end
|
309
|
+
all_height = new_lines.inject(0) { |result, line|
|
310
|
+
result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
|
252
311
|
}
|
253
312
|
diff = all_height - @highest_in_all
|
313
|
+
move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
|
254
314
|
if diff > 0
|
255
|
-
@highest_in_all = all_height
|
256
315
|
scroll_down(diff)
|
257
|
-
move_cursor_up(
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
move_cursor_down(height)
|
264
|
-
back += height
|
265
|
-
end
|
316
|
+
move_cursor_up(all_height - 1)
|
317
|
+
elsif diff < 0
|
318
|
+
(-diff).times do
|
319
|
+
Reline::IOGate.move_cursor_column(0)
|
320
|
+
Reline::IOGate.erase_after_cursor
|
321
|
+
move_cursor_up(1)
|
266
322
|
end
|
267
|
-
move_cursor_up(
|
323
|
+
move_cursor_up(all_height - 1)
|
268
324
|
else
|
269
|
-
|
270
|
-
|
325
|
+
move_cursor_up(all_height - 1)
|
326
|
+
end
|
327
|
+
@highest_in_all = all_height
|
328
|
+
back = 0
|
329
|
+
modify_lines(new_lines).each_with_index do |line, index|
|
330
|
+
if @prompt_proc
|
331
|
+
prompt = prompt_list[index]
|
332
|
+
prompt_width = calculate_width(prompt, true)
|
333
|
+
end
|
334
|
+
height = render_partial(prompt, prompt_width, line, false)
|
335
|
+
if index < (new_lines.size - 1)
|
336
|
+
scroll_down(1)
|
337
|
+
back += height
|
338
|
+
else
|
339
|
+
back += height - 1
|
340
|
+
end
|
341
|
+
end
|
342
|
+
move_cursor_up(back)
|
343
|
+
if @previous_line_index
|
344
|
+
@buffer_of_lines[@previous_line_index] = @line
|
345
|
+
@line = @buffer_of_lines[@line_index]
|
271
346
|
end
|
272
|
-
@buffer_of_lines[@previous_line_index] = @line
|
273
|
-
@line = @buffer_of_lines[@line_index]
|
274
347
|
@first_line_started_from =
|
275
348
|
if @line_index.zero?
|
276
349
|
0
|
277
350
|
else
|
278
351
|
@buffer_of_lines[0..(@line_index - 1)].inject(0) { |result, line|
|
279
|
-
result + calculate_height_by_width(
|
352
|
+
result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
|
280
353
|
}
|
281
354
|
end
|
355
|
+
if @prompt_proc
|
356
|
+
prompt = prompt_list[@line_index]
|
357
|
+
prompt_width = calculate_width(prompt, true)
|
358
|
+
end
|
282
359
|
move_cursor_down(@first_line_started_from)
|
283
360
|
calculate_nearest_cursor
|
284
|
-
@
|
361
|
+
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
362
|
+
move_cursor_down(@started_from)
|
363
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
364
|
+
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
285
365
|
@previous_line_index = nil
|
366
|
+
rendered = true
|
286
367
|
elsif @rerender_all
|
287
368
|
move_cursor_up(@first_line_started_from + @started_from)
|
288
|
-
Reline.move_cursor_column(0)
|
369
|
+
Reline::IOGate.move_cursor_column(0)
|
289
370
|
back = 0
|
290
|
-
|
371
|
+
new_buffer = whole_lines
|
372
|
+
prompt_list = nil
|
373
|
+
if @prompt_proc
|
374
|
+
prompt_list = @prompt_proc.(new_buffer)
|
375
|
+
prompt = prompt_list[@line_index]
|
376
|
+
prompt_width = calculate_width(prompt, true)
|
377
|
+
end
|
378
|
+
new_buffer.each_with_index do |line, index|
|
379
|
+
prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
|
291
380
|
width = prompt_width + calculate_width(line)
|
292
381
|
height = calculate_height_by_width(width)
|
293
382
|
back += height
|
294
383
|
end
|
295
384
|
if back > @highest_in_all
|
296
|
-
scroll_down(back)
|
297
|
-
move_cursor_up(back)
|
385
|
+
scroll_down(back - 1)
|
386
|
+
move_cursor_up(back - 1)
|
298
387
|
elsif back < @highest_in_all
|
299
388
|
scroll_down(back)
|
300
|
-
Reline.erase_after_cursor
|
301
|
-
(@highest_in_all - back).times do
|
389
|
+
Reline::IOGate.erase_after_cursor
|
390
|
+
(@highest_in_all - back - 1).times do
|
302
391
|
scroll_down(1)
|
303
|
-
Reline.erase_after_cursor
|
392
|
+
Reline::IOGate.erase_after_cursor
|
304
393
|
end
|
305
|
-
move_cursor_up(@highest_in_all)
|
394
|
+
move_cursor_up(@highest_in_all - 1)
|
306
395
|
end
|
307
|
-
|
308
|
-
|
309
|
-
|
396
|
+
modify_lines(new_buffer).each_with_index do |line, index|
|
397
|
+
if @prompt_proc
|
398
|
+
prompt = prompt_list[index]
|
399
|
+
prompt_width = calculate_width(prompt, true)
|
400
|
+
end
|
401
|
+
render_partial(prompt, prompt_width, line, false)
|
402
|
+
if index < (new_buffer.size - 1)
|
310
403
|
move_cursor_down(1)
|
311
404
|
end
|
312
405
|
end
|
313
406
|
move_cursor_up(back - 1)
|
407
|
+
if @prompt_proc
|
408
|
+
prompt = prompt_list[@line_index]
|
409
|
+
prompt_width = calculate_width(prompt, true)
|
410
|
+
end
|
314
411
|
@highest_in_all = back
|
315
|
-
@highest_in_this = calculate_height_by_width(
|
412
|
+
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
|
316
413
|
@first_line_started_from =
|
317
414
|
if @line_index.zero?
|
318
415
|
0
|
319
416
|
else
|
320
|
-
|
321
|
-
result + calculate_height_by_width(
|
417
|
+
new_buffer[0..(@line_index - 1)].inject(0) { |result, line|
|
418
|
+
result + calculate_height_by_width(prompt_width + calculate_width(line)) # TODO prompt_list
|
322
419
|
}
|
323
420
|
end
|
324
|
-
|
421
|
+
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
422
|
+
move_cursor_down(@first_line_started_from + @started_from)
|
423
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
325
424
|
@rerender_all = false
|
425
|
+
rendered = true
|
326
426
|
end
|
327
|
-
|
328
|
-
if @is_multiline
|
329
|
-
|
330
|
-
|
331
|
-
|
427
|
+
line = modify_lines(whole_lines)[@line_index]
|
428
|
+
if @is_multiline
|
429
|
+
prompt_list = nil
|
430
|
+
if @prompt_proc
|
431
|
+
prompt_list = @prompt_proc.(whole_lines)
|
432
|
+
prompt = prompt_list[@line_index]
|
433
|
+
prompt_width = calculate_width(prompt, true)
|
434
|
+
end
|
435
|
+
if finished?
|
436
|
+
# Always rerender on finish because output_modifier_proc may return a different output.
|
437
|
+
render_partial(prompt, prompt_width, line)
|
438
|
+
scroll_down(1)
|
439
|
+
Reline::IOGate.move_cursor_column(0)
|
440
|
+
Reline::IOGate.erase_after_cursor
|
441
|
+
elsif not rendered
|
442
|
+
render_partial(prompt, prompt_width, line)
|
443
|
+
end
|
444
|
+
else
|
445
|
+
render_partial(prompt, prompt_width, line)
|
446
|
+
if finished?
|
447
|
+
scroll_down(1)
|
448
|
+
Reline::IOGate.move_cursor_column(0)
|
449
|
+
Reline::IOGate.erase_after_cursor
|
450
|
+
end
|
332
451
|
end
|
333
452
|
end
|
334
453
|
|
335
454
|
private def render_partial(prompt, prompt_width, line_to_render, with_control = true)
|
336
|
-
|
337
|
-
visual_lines = split_by_width(whole_line, @screen_size.last)
|
455
|
+
visual_lines, height = split_by_width(prompt, line_to_render.nil? ? '' : line_to_render, @screen_size.last)
|
338
456
|
if with_control
|
339
|
-
if
|
340
|
-
diff =
|
457
|
+
if height > @highest_in_this
|
458
|
+
diff = height - @highest_in_this
|
341
459
|
scroll_down(diff)
|
342
460
|
@highest_in_all += diff
|
343
|
-
@highest_in_this =
|
344
|
-
move_cursor_up(
|
461
|
+
@highest_in_this = height
|
462
|
+
move_cursor_up(diff)
|
463
|
+
elsif height < @highest_in_this
|
464
|
+
diff = @highest_in_this - height
|
465
|
+
@highest_in_all -= diff
|
466
|
+
@highest_in_this = height
|
345
467
|
end
|
346
468
|
move_cursor_up(@started_from)
|
347
469
|
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
|
348
470
|
end
|
471
|
+
Reline::IOGate.move_cursor_column(0)
|
349
472
|
visual_lines.each_with_index do |line, index|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
473
|
+
if line.nil?
|
474
|
+
Reline::IOGate.erase_after_cursor
|
475
|
+
move_cursor_down(1)
|
476
|
+
Reline::IOGate.move_cursor_column(0)
|
477
|
+
next
|
478
|
+
end
|
479
|
+
@output.print line
|
480
|
+
if @first_prompt
|
481
|
+
@first_prompt = false
|
482
|
+
@pre_input_hook&.call
|
483
|
+
end
|
354
484
|
end
|
485
|
+
Reline::IOGate.erase_after_cursor
|
355
486
|
if with_control
|
487
|
+
move_cursor_up(height - 1)
|
356
488
|
if finished?
|
357
|
-
|
358
|
-
else
|
359
|
-
move_cursor_up((visual_lines.size - 1) - @started_from)
|
360
|
-
Reline.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
489
|
+
move_cursor_down(@started_from)
|
361
490
|
end
|
491
|
+
move_cursor_down(@started_from)
|
492
|
+
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
|
362
493
|
end
|
363
|
-
|
494
|
+
height
|
364
495
|
end
|
365
496
|
|
366
|
-
def
|
367
|
-
|
497
|
+
private def modify_lines(before)
|
498
|
+
return before if before.nil? || before.empty?
|
499
|
+
|
500
|
+
if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
|
501
|
+
after.lines(chomp: true)
|
502
|
+
else
|
503
|
+
before
|
504
|
+
end
|
368
505
|
end
|
369
506
|
|
370
|
-
|
371
|
-
|
372
|
-
escaped = Reline::Unicode::EscapedPairs[gr.ord]
|
373
|
-
if escaped
|
374
|
-
escaped
|
375
|
-
else
|
376
|
-
gr
|
377
|
-
end
|
378
|
-
}.join
|
507
|
+
def editing_mode
|
508
|
+
@config.editing_mode
|
379
509
|
end
|
380
510
|
|
381
511
|
private def menu(target, list)
|
@@ -383,15 +513,25 @@ class Reline::LineEditor
|
|
383
513
|
end
|
384
514
|
|
385
515
|
private def complete_internal_proc(list, is_menu)
|
386
|
-
preposing, target, postposing =
|
387
|
-
list = list.select { |i|
|
516
|
+
preposing, target, postposing = retrieve_completion_block
|
517
|
+
list = list.select { |i|
|
518
|
+
if i and i.encoding != Encoding::US_ASCII and i.encoding != @encoding
|
519
|
+
raise Encoding::CompatibilityError
|
520
|
+
end
|
521
|
+
i&.start_with?(target)
|
522
|
+
}
|
388
523
|
if is_menu
|
389
524
|
menu(target, list)
|
390
525
|
return nil
|
391
526
|
end
|
392
527
|
completed = list.inject { |memo, item|
|
393
|
-
|
394
|
-
|
528
|
+
begin
|
529
|
+
memo_mbchars = memo.unicode_normalize.grapheme_clusters
|
530
|
+
item_mbchars = item.unicode_normalize.grapheme_clusters
|
531
|
+
rescue Encoding::CompatibilityError
|
532
|
+
memo_mbchars = memo.grapheme_clusters
|
533
|
+
item_mbchars = item.grapheme_clusters
|
534
|
+
end
|
395
535
|
size = [memo_mbchars.size, item_mbchars.size].min
|
396
536
|
result = ''
|
397
537
|
size.times do |i|
|
@@ -438,7 +578,7 @@ class Reline::LineEditor
|
|
438
578
|
case @completion_state
|
439
579
|
when CompletionState::NORMAL, CompletionState::COMPLETION, CompletionState::MENU
|
440
580
|
@completion_state = CompletionState::JOURNEY
|
441
|
-
result =
|
581
|
+
result = retrieve_completion_block
|
442
582
|
return if result.nil?
|
443
583
|
preposing, target, postposing = result
|
444
584
|
@completion_journey_data = CompletionJourneyData.new(
|
@@ -485,6 +625,7 @@ class Reline::LineEditor
|
|
485
625
|
cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
|
486
626
|
@cursor, @byte_pointer = old_cursor, old_byte_pointer
|
487
627
|
@waiting_operator_proc.(cursor_diff, byte_pointer_diff)
|
628
|
+
@waiting_operator_proc = old_waiting_operator_proc
|
488
629
|
}
|
489
630
|
end
|
490
631
|
else
|
@@ -497,12 +638,31 @@ class Reline::LineEditor
|
|
497
638
|
end
|
498
639
|
end
|
499
640
|
|
500
|
-
private def
|
501
|
-
|
641
|
+
private def argumentable?(method_obj)
|
642
|
+
method_obj and method_obj.parameters.length != 1
|
643
|
+
end
|
644
|
+
|
645
|
+
private def process_key(key, method_symbol)
|
646
|
+
if method_symbol and respond_to?(method_symbol, true)
|
647
|
+
method_obj = method(method_symbol)
|
648
|
+
else
|
649
|
+
method_obj = nil
|
650
|
+
end
|
651
|
+
if method_symbol and key.is_a?(Symbol)
|
652
|
+
if @vi_arg and argumentable?(method_obj)
|
653
|
+
run_for_operators(key, method_symbol) do
|
654
|
+
method_obj.(key, arg: @vi_arg)
|
655
|
+
end
|
656
|
+
else
|
657
|
+
method_obj&.(key)
|
658
|
+
end
|
659
|
+
@kill_ring.process
|
660
|
+
@vi_arg = nil
|
661
|
+
elsif @vi_arg
|
502
662
|
if key.chr =~ /[0-9]/
|
503
663
|
ed_argument_digit(key)
|
504
664
|
else
|
505
|
-
if
|
665
|
+
if argumentable?(method_obj)
|
506
666
|
run_for_operators(key, method_symbol) do
|
507
667
|
method_obj.(key, arg: @vi_arg)
|
508
668
|
end
|
@@ -535,31 +695,33 @@ class Reline::LineEditor
|
|
535
695
|
|
536
696
|
private def normal_char(key)
|
537
697
|
method_symbol = method_obj = nil
|
538
|
-
|
698
|
+
if key.combined_char.is_a?(Symbol)
|
699
|
+
process_key(key.combined_char, key.combined_char)
|
700
|
+
return
|
701
|
+
end
|
702
|
+
@multibyte_buffer << key.combined_char
|
539
703
|
if @multibyte_buffer.size > 1
|
540
704
|
if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
|
541
|
-
|
705
|
+
process_key(@multibyte_buffer.dup.force_encoding(@encoding), nil)
|
542
706
|
@multibyte_buffer.clear
|
543
707
|
else
|
544
708
|
# invalid
|
545
709
|
return
|
546
710
|
end
|
547
711
|
else # single byte
|
548
|
-
return if key >= 128 # maybe, first byte of multi byte
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
method_obj = method(method_symbol)
|
712
|
+
return if key.char >= 128 # maybe, first byte of multi byte
|
713
|
+
method_symbol = @config.editing_mode.get_method(key.combined_char)
|
714
|
+
if key.with_meta and method_symbol == :ed_unassigned
|
715
|
+
# split ESC + key
|
716
|
+
method_symbol = @config.editing_mode.get_method("\e".ord)
|
717
|
+
process_key("\e".ord, method_symbol)
|
718
|
+
method_symbol = @config.editing_mode.get_method(key.char)
|
719
|
+
process_key(key.char, method_symbol)
|
720
|
+
else
|
721
|
+
process_key(key.combined_char, method_symbol)
|
559
722
|
end
|
560
723
|
@multibyte_buffer.clear
|
561
724
|
end
|
562
|
-
process_key(key, method_symbol, method_obj)
|
563
725
|
if @config.editing_mode_is?(:vi_command) and @cursor > 0 and @cursor == @cursor_max
|
564
726
|
byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
|
565
727
|
@byte_pointer -= byte_size
|
@@ -570,54 +732,188 @@ class Reline::LineEditor
|
|
570
732
|
end
|
571
733
|
|
572
734
|
def input_key(key)
|
735
|
+
if key.nil? or key.char.nil?
|
736
|
+
if @first_char
|
737
|
+
@line = nil
|
738
|
+
end
|
739
|
+
finish
|
740
|
+
return
|
741
|
+
end
|
742
|
+
@first_char = false
|
573
743
|
completion_occurs = false
|
574
|
-
if @config.editing_mode_is?(:emacs, :vi_insert) and key == "\C-i".ord
|
575
|
-
result =
|
744
|
+
if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
|
745
|
+
result = retrieve_completion_block
|
746
|
+
slice = result[1]
|
747
|
+
result = @completion_proc.(slice) if @completion_proc and slice
|
576
748
|
if result.is_a?(Array)
|
577
749
|
completion_occurs = true
|
578
750
|
complete(result)
|
579
751
|
end
|
580
|
-
elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key)
|
581
|
-
result =
|
752
|
+
elsif @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
|
753
|
+
result = retrieve_completion_block
|
754
|
+
slice = result[1]
|
755
|
+
result = @completion_proc.(slice) if @completion_proc and slice
|
582
756
|
if result.is_a?(Array)
|
583
757
|
completion_occurs = true
|
584
|
-
move_completed_list(result, "\C-p".ord == key ? :up : :down)
|
758
|
+
move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
|
585
759
|
end
|
586
|
-
elsif
|
587
|
-
|
588
|
-
# escape twice
|
589
|
-
@meta_prefix = false
|
590
|
-
@kill_ring.process
|
591
|
-
else
|
592
|
-
@meta_prefix = true
|
593
|
-
end
|
594
|
-
elsif @config.editing_mode_is?(:vi_command) and key == "\e".ord
|
595
|
-
# suppress ^[ when command_mode
|
596
|
-
elsif Symbol === key and respond_to?(key, true)
|
597
|
-
process_key(key, key, method(key))
|
760
|
+
elsif Symbol === key.char and respond_to?(key.char, true)
|
761
|
+
process_key(key.char, key.char)
|
598
762
|
else
|
599
763
|
normal_char(key)
|
600
764
|
end
|
601
765
|
unless completion_occurs
|
602
766
|
@completion_state = CompletionState::NORMAL
|
603
767
|
end
|
604
|
-
if @
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
768
|
+
if @is_multiline and @auto_indent_proc
|
769
|
+
process_auto_indent
|
770
|
+
end
|
771
|
+
end
|
772
|
+
|
773
|
+
private def process_auto_indent
|
774
|
+
return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
|
775
|
+
if @previous_line_index
|
776
|
+
new_lines = whole_lines(index: @previous_line_index, line: @line)
|
777
|
+
else
|
778
|
+
new_lines = whole_lines
|
779
|
+
end
|
780
|
+
new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
|
781
|
+
if new_indent&.>= 0
|
782
|
+
md = new_lines[@line_index].match(/\A */)
|
783
|
+
prev_indent = md[0].count(' ')
|
784
|
+
if @check_new_auto_indent
|
785
|
+
@buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
|
786
|
+
@cursor = new_indent
|
787
|
+
@byte_pointer = new_indent
|
788
|
+
else
|
789
|
+
@line = ' ' * new_indent + @line.lstrip
|
790
|
+
@cursor += new_indent - prev_indent
|
791
|
+
@byte_pointer += new_indent - prev_indent
|
609
792
|
end
|
610
|
-
finish if @confirm_multiline_termination_proc.(temp_buffer.join("\n"))
|
611
793
|
end
|
794
|
+
@check_new_auto_indent = false
|
795
|
+
end
|
796
|
+
|
797
|
+
def retrieve_completion_block
|
798
|
+
word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
|
799
|
+
quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
|
800
|
+
before = @line.byteslice(0, @byte_pointer)
|
801
|
+
rest = nil
|
802
|
+
break_pointer = nil
|
803
|
+
quote = nil
|
804
|
+
i = 0
|
805
|
+
while i < @byte_pointer do
|
806
|
+
slice = @line.byteslice(i, @byte_pointer - i)
|
807
|
+
if quote and slice.start_with?(/(?!\\)#{Regexp.escape(quote)}/) # closing "
|
808
|
+
quote = nil
|
809
|
+
i += 1
|
810
|
+
elsif quote and slice.start_with?(/\\#{Regexp.escape(quote)}/) # escaped \"
|
811
|
+
# skip
|
812
|
+
i += 2
|
813
|
+
elsif slice =~ quote_characters_regexp # find new "
|
814
|
+
quote = $&
|
815
|
+
i += 1
|
816
|
+
elsif not quote and slice =~ word_break_regexp
|
817
|
+
rest = $'
|
818
|
+
i += 1
|
819
|
+
break_pointer = i
|
820
|
+
else
|
821
|
+
i += 1
|
822
|
+
end
|
823
|
+
end
|
824
|
+
if rest
|
825
|
+
preposing = @line.byteslice(0, break_pointer)
|
826
|
+
target = rest
|
827
|
+
else
|
828
|
+
preposing = ''
|
829
|
+
target = before
|
830
|
+
end
|
831
|
+
postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
|
832
|
+
[preposing, target, postposing]
|
612
833
|
end
|
613
834
|
|
614
|
-
def
|
835
|
+
def confirm_multiline_termination
|
836
|
+
temp_buffer = @buffer_of_lines.dup
|
837
|
+
if @previous_line_index and @line_index == (@buffer_of_lines.size - 1)
|
838
|
+
temp_buffer[@previous_line_index] = @line
|
839
|
+
else
|
840
|
+
temp_buffer[@line_index] = @line
|
841
|
+
end
|
842
|
+
if temp_buffer.any?{ |l| l.chomp != '' }
|
843
|
+
@confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
|
844
|
+
else
|
845
|
+
false
|
846
|
+
end
|
847
|
+
end
|
848
|
+
|
849
|
+
def insert_text(text)
|
850
|
+
width = calculate_width(text)
|
851
|
+
if @cursor == @cursor_max
|
852
|
+
@line += text
|
853
|
+
else
|
854
|
+
@line = byteinsert(@line, @byte_pointer, text)
|
855
|
+
end
|
856
|
+
@byte_pointer += text.bytesize
|
857
|
+
@cursor += width
|
858
|
+
@cursor_max += width
|
859
|
+
end
|
860
|
+
|
861
|
+
def delete_text(start = nil, length = nil)
|
862
|
+
if start.nil? and length.nil?
|
863
|
+
@line&.clear
|
864
|
+
@byte_pointer = 0
|
865
|
+
@cursor = 0
|
866
|
+
@cursor_max = 0
|
867
|
+
elsif not start.nil? and not length.nil?
|
868
|
+
if @line
|
869
|
+
before = @line.byteslice(0, start)
|
870
|
+
after = @line.byteslice(start + length, @line.bytesize)
|
871
|
+
@line = before + after
|
872
|
+
@byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
|
873
|
+
str = @line.byteslice(0, @byte_pointer)
|
874
|
+
@cursor = calculate_width(str)
|
875
|
+
@cursor_max = calculate_width(@line)
|
876
|
+
end
|
877
|
+
elsif start.is_a?(Range)
|
878
|
+
range = start
|
879
|
+
first = range.first
|
880
|
+
last = range.last
|
881
|
+
last = @line.bytesize - 1 if last > @line.bytesize
|
882
|
+
last += @line.bytesize if last < 0
|
883
|
+
first += @line.bytesize if first < 0
|
884
|
+
range = range.exclude_end? ? first...last : first..last
|
885
|
+
@line = @line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
|
886
|
+
@byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
|
887
|
+
str = @line.byteslice(0, @byte_pointer)
|
888
|
+
@cursor = calculate_width(str)
|
889
|
+
@cursor_max = calculate_width(@line)
|
890
|
+
else
|
891
|
+
@line = @line.byteslice(0, start)
|
892
|
+
@byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
|
893
|
+
str = @line.byteslice(0, @byte_pointer)
|
894
|
+
@cursor = calculate_width(str)
|
895
|
+
@cursor_max = calculate_width(@line)
|
896
|
+
end
|
897
|
+
end
|
898
|
+
|
899
|
+
def byte_pointer=(val)
|
900
|
+
@byte_pointer = val
|
901
|
+
str = @line.byteslice(0, @byte_pointer)
|
902
|
+
@cursor = calculate_width(str)
|
903
|
+
@cursor_max = calculate_width(@line)
|
904
|
+
end
|
905
|
+
|
906
|
+
def whole_lines(index: @line_index, line: @line)
|
615
907
|
temp_lines = @buffer_of_lines.dup
|
616
|
-
temp_lines[
|
908
|
+
temp_lines[index] = line
|
909
|
+
temp_lines
|
910
|
+
end
|
911
|
+
|
912
|
+
def whole_buffer
|
617
913
|
if @buffer_of_lines.size == 1 and @line.nil?
|
618
914
|
nil
|
619
915
|
else
|
620
|
-
|
916
|
+
whole_lines.join("\n")
|
621
917
|
end
|
622
918
|
end
|
623
919
|
|
@@ -643,10 +939,46 @@ class Reline::LineEditor
|
|
643
939
|
new_str
|
644
940
|
end
|
645
941
|
|
646
|
-
private def calculate_width(str)
|
647
|
-
|
648
|
-
width
|
649
|
-
|
942
|
+
private def calculate_width(str, allow_escape_code = false)
|
943
|
+
if allow_escape_code
|
944
|
+
width = 0
|
945
|
+
rest = str.encode(Encoding::UTF_8)
|
946
|
+
in_zero_width = false
|
947
|
+
rest.scan(WIDTH_SCANNER) do |gc|
|
948
|
+
case gc
|
949
|
+
when NON_PRINTING_START
|
950
|
+
in_zero_width = true
|
951
|
+
when NON_PRINTING_END
|
952
|
+
in_zero_width = false
|
953
|
+
when CSI_REGEXP, OSC_REGEXP
|
954
|
+
else
|
955
|
+
unless in_zero_width
|
956
|
+
width += Reline::Unicode.get_mbchar_width(gc)
|
957
|
+
end
|
958
|
+
end
|
959
|
+
end
|
960
|
+
width
|
961
|
+
else
|
962
|
+
str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |width, gc|
|
963
|
+
width + Reline::Unicode.get_mbchar_width(gc)
|
964
|
+
}
|
965
|
+
end
|
966
|
+
end
|
967
|
+
|
968
|
+
private def key_delete(key)
|
969
|
+
if @config.editing_mode_is?(:vi_insert, :emacs)
|
970
|
+
ed_delete_next_char(key)
|
971
|
+
end
|
972
|
+
end
|
973
|
+
|
974
|
+
private def key_newline(key)
|
975
|
+
if @is_multiline
|
976
|
+
next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
|
977
|
+
cursor_line = @line.byteslice(0, @byte_pointer)
|
978
|
+
insert_new_line(cursor_line, next_line)
|
979
|
+
@cursor = 0
|
980
|
+
@check_new_auto_indent = true
|
981
|
+
end
|
650
982
|
end
|
651
983
|
|
652
984
|
private def ed_insert(key)
|
@@ -673,15 +1005,21 @@ class Reline::LineEditor
|
|
673
1005
|
end
|
674
1006
|
end
|
675
1007
|
alias_method :ed_digit, :ed_insert
|
1008
|
+
alias_method :self_insert, :ed_insert
|
676
1009
|
|
677
1010
|
private def ed_quoted_insert(str, arg: 1)
|
678
1011
|
@waiting_proc = proc { |key|
|
679
1012
|
arg.times do
|
680
|
-
|
1013
|
+
if key == "\C-j".ord or key == "\C-m".ord
|
1014
|
+
key_newline(key)
|
1015
|
+
else
|
1016
|
+
ed_insert(key)
|
1017
|
+
end
|
681
1018
|
end
|
682
1019
|
@waiting_proc = nil
|
683
1020
|
}
|
684
1021
|
end
|
1022
|
+
alias_method :quoted_insert, :ed_quoted_insert
|
685
1023
|
|
686
1024
|
private def ed_next_char(key, arg: 1)
|
687
1025
|
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
|
@@ -690,10 +1028,18 @@ class Reline::LineEditor
|
|
690
1028
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
691
1029
|
@cursor += width if width
|
692
1030
|
@byte_pointer += byte_size
|
1031
|
+
elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == @line.bytesize and @line_index < @buffer_of_lines.size - 1
|
1032
|
+
next_line = @buffer_of_lines[@line_index + 1]
|
1033
|
+
@cursor = 0
|
1034
|
+
@byte_pointer = 0
|
1035
|
+
@cursor_max = calculate_width(next_line)
|
1036
|
+
@previous_line_index = @line_index
|
1037
|
+
@line_index += 1
|
693
1038
|
end
|
694
1039
|
arg -= 1
|
695
1040
|
ed_next_char(key, arg: arg) if arg > 0
|
696
1041
|
end
|
1042
|
+
alias_method :forward_char, :ed_next_char
|
697
1043
|
|
698
1044
|
private def ed_prev_char(key, arg: 1)
|
699
1045
|
if @cursor > 0
|
@@ -702,14 +1048,26 @@ class Reline::LineEditor
|
|
702
1048
|
mbchar = @line.byteslice(@byte_pointer, byte_size)
|
703
1049
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
704
1050
|
@cursor -= width
|
1051
|
+
elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
|
1052
|
+
prev_line = @buffer_of_lines[@line_index - 1]
|
1053
|
+
@cursor = calculate_width(prev_line)
|
1054
|
+
@byte_pointer = prev_line.bytesize
|
1055
|
+
@cursor_max = calculate_width(prev_line)
|
1056
|
+
@previous_line_index = @line_index
|
1057
|
+
@line_index -= 1
|
705
1058
|
end
|
706
1059
|
arg -= 1
|
707
1060
|
ed_prev_char(key, arg: arg) if arg > 0
|
708
1061
|
end
|
709
1062
|
|
1063
|
+
private def vi_first_print(key)
|
1064
|
+
@byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
|
1065
|
+
end
|
1066
|
+
|
710
1067
|
private def ed_move_to_beg(key)
|
711
|
-
@byte_pointer
|
1068
|
+
@byte_pointer = @cursor = 0
|
712
1069
|
end
|
1070
|
+
alias_method :beginning_of_line, :ed_move_to_beg
|
713
1071
|
|
714
1072
|
private def ed_move_to_end(key)
|
715
1073
|
@byte_pointer = 0
|
@@ -724,6 +1082,95 @@ class Reline::LineEditor
|
|
724
1082
|
@byte_pointer += byte_size
|
725
1083
|
end
|
726
1084
|
end
|
1085
|
+
alias_method :end_of_line, :ed_move_to_end
|
1086
|
+
|
1087
|
+
private def ed_search_prev_history(key)
|
1088
|
+
@line_backup_in_history = @line
|
1089
|
+
searcher = Fiber.new do
|
1090
|
+
search_word = String.new(encoding: @encoding)
|
1091
|
+
multibyte_buf = String.new(encoding: 'ASCII-8BIT')
|
1092
|
+
last_hit = nil
|
1093
|
+
loop do
|
1094
|
+
key = Fiber.yield(search_word)
|
1095
|
+
case key
|
1096
|
+
when "\C-h".ord, 127
|
1097
|
+
grapheme_clusters = search_word.grapheme_clusters
|
1098
|
+
if grapheme_clusters.size > 0
|
1099
|
+
grapheme_clusters.pop
|
1100
|
+
search_word = grapheme_clusters.join
|
1101
|
+
end
|
1102
|
+
else
|
1103
|
+
multibyte_buf << key
|
1104
|
+
if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
|
1105
|
+
search_word << multibyte_buf.dup.force_encoding(@encoding)
|
1106
|
+
multibyte_buf.clear
|
1107
|
+
end
|
1108
|
+
end
|
1109
|
+
hit = nil
|
1110
|
+
if @line_backup_in_history.include?(search_word)
|
1111
|
+
@history_pointer = nil
|
1112
|
+
hit = @line_backup_in_history
|
1113
|
+
else
|
1114
|
+
hit_index = Reline::HISTORY.rindex { |item|
|
1115
|
+
item.include?(search_word)
|
1116
|
+
}
|
1117
|
+
if hit_index
|
1118
|
+
@history_pointer = hit_index
|
1119
|
+
hit = Reline::HISTORY[@history_pointer]
|
1120
|
+
end
|
1121
|
+
end
|
1122
|
+
if hit
|
1123
|
+
@searching_prompt = "(reverse-i-search)`%s': %s" % [search_word, hit]
|
1124
|
+
@line = hit
|
1125
|
+
last_hit = hit
|
1126
|
+
else
|
1127
|
+
@searching_prompt = "(failed reverse-i-search)`%s': %s" % [search_word, last_hit]
|
1128
|
+
end
|
1129
|
+
end
|
1130
|
+
end
|
1131
|
+
searcher.resume
|
1132
|
+
@searching_prompt = "(reverse-i-search)`': "
|
1133
|
+
@waiting_proc = ->(key) {
|
1134
|
+
case key
|
1135
|
+
when "\C-j".ord, "\C-?".ord
|
1136
|
+
if @history_pointer
|
1137
|
+
@line = Reline::HISTORY[@history_pointer]
|
1138
|
+
else
|
1139
|
+
@line = @line_backup_in_history
|
1140
|
+
end
|
1141
|
+
@searching_prompt = nil
|
1142
|
+
@waiting_proc = nil
|
1143
|
+
@cursor_max = calculate_width(@line)
|
1144
|
+
@cursor = @byte_pointer = 0
|
1145
|
+
when "\C-g".ord
|
1146
|
+
@line = @line_backup_in_history
|
1147
|
+
@history_pointer = nil
|
1148
|
+
@searching_prompt = nil
|
1149
|
+
@waiting_proc = nil
|
1150
|
+
@line_backup_in_history = nil
|
1151
|
+
@cursor_max = calculate_width(@line)
|
1152
|
+
@cursor = @byte_pointer = 0
|
1153
|
+
else
|
1154
|
+
chr = key.is_a?(String) ? key : key.chr(Encoding::ASCII_8BIT)
|
1155
|
+
if chr.match?(/[[:print:]]/)
|
1156
|
+
searcher.resume(key)
|
1157
|
+
else
|
1158
|
+
if @history_pointer
|
1159
|
+
@line = Reline::HISTORY[@history_pointer]
|
1160
|
+
else
|
1161
|
+
@line = @line_backup_in_history
|
1162
|
+
end
|
1163
|
+
@searching_prompt = nil
|
1164
|
+
@waiting_proc = nil
|
1165
|
+
@cursor_max = calculate_width(@line)
|
1166
|
+
@cursor = @byte_pointer = 0
|
1167
|
+
end
|
1168
|
+
end
|
1169
|
+
}
|
1170
|
+
end
|
1171
|
+
|
1172
|
+
private def ed_search_next_history(key)
|
1173
|
+
end
|
727
1174
|
|
728
1175
|
private def ed_prev_history(key, arg: 1)
|
729
1176
|
if @is_multiline and @line_index > 0
|
@@ -739,6 +1186,7 @@ class Reline::LineEditor
|
|
739
1186
|
if @is_multiline
|
740
1187
|
@line_backup_in_history = whole_buffer
|
741
1188
|
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
|
1189
|
+
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
742
1190
|
@line_index = @buffer_of_lines.size - 1
|
743
1191
|
@line = @buffer_of_lines.last
|
744
1192
|
@rerender_all = true
|
@@ -753,6 +1201,7 @@ class Reline::LineEditor
|
|
753
1201
|
Reline::HISTORY[@history_pointer] = whole_buffer
|
754
1202
|
@history_pointer -= 1
|
755
1203
|
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
|
1204
|
+
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
756
1205
|
@line_index = @buffer_of_lines.size - 1
|
757
1206
|
@line = @buffer_of_lines.last
|
758
1207
|
@rerender_all = true
|
@@ -798,6 +1247,7 @@ class Reline::LineEditor
|
|
798
1247
|
Reline::HISTORY[@history_pointer] = whole_buffer
|
799
1248
|
@history_pointer += 1
|
800
1249
|
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
|
1250
|
+
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
|
801
1251
|
@line_index = 0
|
802
1252
|
@line = @buffer_of_lines.first
|
803
1253
|
@rerender_all = true
|
@@ -823,25 +1273,32 @@ class Reline::LineEditor
|
|
823
1273
|
if @is_multiline
|
824
1274
|
if @config.editing_mode_is?(:vi_command)
|
825
1275
|
if @line_index < (@buffer_of_lines.size - 1)
|
826
|
-
ed_next_history(key)
|
1276
|
+
ed_next_history(key) # means cursor down
|
827
1277
|
else
|
828
|
-
|
1278
|
+
# should check confirm_multiline_termination to finish?
|
1279
|
+
finish
|
829
1280
|
end
|
830
1281
|
else
|
831
|
-
next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
|
832
|
-
cursor_line = @line.byteslice(0, @byte_pointer)
|
833
|
-
insert_new_line(cursor_line, next_line)
|
834
1282
|
if @line_index == (@buffer_of_lines.size - 1)
|
835
|
-
|
1283
|
+
if confirm_multiline_termination
|
1284
|
+
finish
|
1285
|
+
else
|
1286
|
+
key_newline(key)
|
1287
|
+
end
|
1288
|
+
else
|
1289
|
+
# should check confirm_multiline_termination to finish?
|
1290
|
+
@previous_line_index = @line_index
|
1291
|
+
@line_index = @buffer_of_lines.size - 1
|
1292
|
+
finish
|
836
1293
|
end
|
837
1294
|
end
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
1295
|
+
else
|
1296
|
+
if @history_pointer
|
1297
|
+
Reline::HISTORY[@history_pointer] = @line
|
1298
|
+
@history_pointer = nil
|
1299
|
+
end
|
1300
|
+
finish
|
843
1301
|
end
|
844
|
-
finish
|
845
1302
|
end
|
846
1303
|
|
847
1304
|
private def em_delete_prev_char(key)
|
@@ -863,6 +1320,7 @@ class Reline::LineEditor
|
|
863
1320
|
@cursor_max -= width
|
864
1321
|
end
|
865
1322
|
end
|
1323
|
+
alias_method :backward_delete_char, :em_delete_prev_char
|
866
1324
|
|
867
1325
|
private def ed_kill_line(key)
|
868
1326
|
if @line.bytesize > @byte_pointer
|
@@ -884,8 +1342,13 @@ class Reline::LineEditor
|
|
884
1342
|
end
|
885
1343
|
|
886
1344
|
private def em_delete_or_list(key)
|
887
|
-
if @line.empty?
|
1345
|
+
if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
|
888
1346
|
@line = nil
|
1347
|
+
if @buffer_of_lines.size > 1
|
1348
|
+
scroll_down(@highest_in_all - @first_line_started_from)
|
1349
|
+
end
|
1350
|
+
Reline::IOGate.move_cursor_column(0)
|
1351
|
+
@eof = true
|
889
1352
|
finish
|
890
1353
|
elsif @byte_pointer < @line.bytesize
|
891
1354
|
splitted_last = @line.byteslice(@byte_pointer, @line.bytesize)
|
@@ -893,8 +1356,17 @@ class Reline::LineEditor
|
|
893
1356
|
width = Reline::Unicode.get_mbchar_width(mbchar)
|
894
1357
|
@cursor_max -= width
|
895
1358
|
@line, = byteslice!(@line, @byte_pointer, mbchar.bytesize)
|
1359
|
+
elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
|
1360
|
+
@cursor = calculate_width(@line)
|
1361
|
+
@byte_pointer = @line.bytesize
|
1362
|
+
@line += @buffer_of_lines.delete_at(@line_index + 1)
|
1363
|
+
@cursor_max = calculate_width(@line)
|
1364
|
+
@buffer_of_lines[@line_index] = @line
|
1365
|
+
@rerender_all = true
|
1366
|
+
@rest_height += 1
|
896
1367
|
end
|
897
1368
|
end
|
1369
|
+
alias_method :delete_char, :em_delete_or_list
|
898
1370
|
|
899
1371
|
private def em_yank(key)
|
900
1372
|
yanked = @kill_ring.yank
|
@@ -926,6 +1398,7 @@ class Reline::LineEditor
|
|
926
1398
|
private def ed_clear_screen(key)
|
927
1399
|
@cleared = true
|
928
1400
|
end
|
1401
|
+
alias_method :clear_screen, :ed_clear_screen
|
929
1402
|
|
930
1403
|
private def em_next_word(key)
|
931
1404
|
if @line.bytesize > @byte_pointer
|
@@ -934,6 +1407,7 @@ class Reline::LineEditor
|
|
934
1407
|
@cursor += width
|
935
1408
|
end
|
936
1409
|
end
|
1410
|
+
alias_method :forward_word, :em_next_word
|
937
1411
|
|
938
1412
|
private def ed_prev_word(key)
|
939
1413
|
if @byte_pointer > 0
|
@@ -942,6 +1416,7 @@ class Reline::LineEditor
|
|
942
1416
|
@cursor -= width
|
943
1417
|
end
|
944
1418
|
end
|
1419
|
+
alias_method :backward_word, :ed_prev_word
|
945
1420
|
|
946
1421
|
private def em_delete_next_word(key)
|
947
1422
|
if @line.bytesize > @byte_pointer
|
@@ -981,6 +1456,22 @@ class Reline::LineEditor
|
|
981
1456
|
end
|
982
1457
|
end
|
983
1458
|
end
|
1459
|
+
alias_method :transpose_chars, :ed_transpose_chars
|
1460
|
+
|
1461
|
+
private def ed_transpose_words(key)
|
1462
|
+
left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(@line, @byte_pointer)
|
1463
|
+
before = @line.byteslice(0, left_word_start)
|
1464
|
+
left_word = @line.byteslice(left_word_start, middle_start - left_word_start)
|
1465
|
+
middle = @line.byteslice(middle_start, right_word_start - middle_start)
|
1466
|
+
right_word = @line.byteslice(right_word_start, after_start - right_word_start)
|
1467
|
+
after = @line.byteslice(after_start, @line.bytesize - after_start)
|
1468
|
+
return if left_word.empty? or right_word.empty?
|
1469
|
+
@line = before + right_word + middle + left_word + after
|
1470
|
+
from_head_to_left_word = before + right_word + middle + left_word
|
1471
|
+
@byte_pointer = from_head_to_left_word.bytesize
|
1472
|
+
@cursor = calculate_width(from_head_to_left_word)
|
1473
|
+
end
|
1474
|
+
alias_method :transpose_words, :ed_transpose_words
|
984
1475
|
|
985
1476
|
private def em_capitol_case(key)
|
986
1477
|
if @line.bytesize > @byte_pointer
|
@@ -992,6 +1483,7 @@ class Reline::LineEditor
|
|
992
1483
|
@cursor += calculate_width(new_str)
|
993
1484
|
end
|
994
1485
|
end
|
1486
|
+
alias_method :capitalize_word, :em_capitol_case
|
995
1487
|
|
996
1488
|
private def em_lower_case(key)
|
997
1489
|
if @line.bytesize > @byte_pointer
|
@@ -1007,6 +1499,7 @@ class Reline::LineEditor
|
|
1007
1499
|
@line += rest
|
1008
1500
|
end
|
1009
1501
|
end
|
1502
|
+
alias_method :downcase_word, :em_lower_case
|
1010
1503
|
|
1011
1504
|
private def em_upper_case(key)
|
1012
1505
|
if @line.bytesize > @byte_pointer
|
@@ -1022,6 +1515,7 @@ class Reline::LineEditor
|
|
1022
1515
|
@line += rest
|
1023
1516
|
end
|
1024
1517
|
end
|
1518
|
+
alias_method :upcase_word, :em_upper_case
|
1025
1519
|
|
1026
1520
|
private def em_kill_region(key)
|
1027
1521
|
if @byte_pointer > 0
|
@@ -1053,6 +1547,7 @@ class Reline::LineEditor
|
|
1053
1547
|
ed_prev_char(key)
|
1054
1548
|
@config.editing_mode = :vi_command
|
1055
1549
|
end
|
1550
|
+
alias_method :backward_char, :ed_prev_char
|
1056
1551
|
|
1057
1552
|
private def vi_next_word(key, arg: 1)
|
1058
1553
|
if @line.bytesize > @byte_pointer
|
@@ -1178,13 +1673,23 @@ class Reline::LineEditor
|
|
1178
1673
|
private def vi_end_of_transmission(key)
|
1179
1674
|
if @line.empty?
|
1180
1675
|
@line = nil
|
1676
|
+
if @buffer_of_lines.size > 1
|
1677
|
+
scroll_down(@highest_in_all - @first_line_started_from)
|
1678
|
+
end
|
1679
|
+
Reline::IOGate.move_cursor_column(0)
|
1680
|
+
@eof = true
|
1181
1681
|
finish
|
1182
1682
|
end
|
1183
1683
|
end
|
1184
1684
|
|
1185
1685
|
private def vi_list_or_eof(key)
|
1186
|
-
if @line.empty?
|
1686
|
+
if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
|
1187
1687
|
@line = nil
|
1688
|
+
if @buffer_of_lines.size > 1
|
1689
|
+
scroll_down(@highest_in_all - @first_line_started_from)
|
1690
|
+
end
|
1691
|
+
Reline::IOGate.move_cursor_column(0)
|
1692
|
+
@eof = true
|
1188
1693
|
finish
|
1189
1694
|
else
|
1190
1695
|
# TODO: list
|
@@ -1354,4 +1859,18 @@ class Reline::LineEditor
|
|
1354
1859
|
end
|
1355
1860
|
@waiting_proc = nil
|
1356
1861
|
end
|
1862
|
+
|
1863
|
+
private def vi_join_lines(key, arg: 1)
|
1864
|
+
if @is_multiline and @buffer_of_lines.size > @line_index + 1
|
1865
|
+
@cursor = calculate_width(@line)
|
1866
|
+
@byte_pointer = @line.bytesize
|
1867
|
+
@line += ' ' + @buffer_of_lines.delete_at(@line_index + 1).lstrip
|
1868
|
+
@cursor_max = calculate_width(@line)
|
1869
|
+
@buffer_of_lines[@line_index] = @line
|
1870
|
+
@rerender_all = true
|
1871
|
+
@rest_height += 1
|
1872
|
+
end
|
1873
|
+
arg -= 1
|
1874
|
+
vi_join_lines(key, arg: arg) if arg > 0
|
1875
|
+
end
|
1357
1876
|
end
|