reline 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/reline.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
|